URI: 
       tGUI refactoring for Kivy and lightning. This also touches Qt and wallet code. - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 70cd29f9e1d442863492aef69faf191e6ba40a30
   DIR parent 1a23dcb8d5c394b74648ab6c4a8db47a9d258f37
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 10 Jun 2019 14:05:02 +0200
       
       GUI refactoring for Kivy and lightning.
       This also touches Qt and wallet code.
       
       Diffstat:
         M electrum/gui/kivy/Makefile          |       4 +---
         M electrum/gui/kivy/main.kv           |       3 +++
         M electrum/gui/kivy/main_window.py    |      26 +++++++++++++++++++++-----
         A electrum/gui/kivy/theming/light/co… |       0 
         D electrum/gui/kivy/theming/light/li… |     288 -------------------------------
         A electrum/gui/kivy/theming/light/li… |       0 
         M electrum/gui/kivy/uix/dialogs/addr… |       7 +++----
         M electrum/gui/kivy/uix/dialogs/qr_d… |      12 +++++++++++-
         A electrum/gui/kivy/uix/dialogs/requ… |      82 +++++++++++++++++++++++++++++++
         M electrum/gui/kivy/uix/dialogs/requ… |      44 ++++++++++++++++++++++---------
         M electrum/gui/kivy/uix/screens.py    |     158 ++++++++-----------------------
         M electrum/gui/kivy/uix/ui_screens/r… |      93 +++++++++----------------------
         M electrum/gui/kivy/uix/ui_screens/s… |      36 ++++++++++++++++----------------
         M electrum/gui/qt/main_window.py      |      30 ++++++------------------------
         M electrum/gui/qt/request_list.py     |      58 ++++++++++++-------------------
         M electrum/lnpeer.py                  |       2 +-
         M electrum/lnworker.py                |      25 ++++++++++++++++++++++++-
         M electrum/wallet.py                  |      59 +++++++++++++++++++++----------
       
       18 files changed, 329 insertions(+), 598 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/Makefile b/electrum/gui/kivy/Makefile
       t@@ -5,9 +5,7 @@ PYTHON = python3
        .PHONY: theming apk clean
        
        theming:
       -        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
       +        #bash -c 'for i in network lightning; do convert -background none theming/light/$$i.{svg,png}; done'
                $(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png
        prepare:
                # running pre build setup
   DIR diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv
       t@@ -450,6 +450,9 @@ BoxLayout:
                            name: 'network'
                            text: _('Network')
                        ActionOvrButton:
       +                    name: 'addresses_dialog'
       +                    text: _('Addresses')
       +                ActionOvrButton:
                            name: 'lightning_channels_dialog'
                            text: _('Channels')
                        ActionOvrButton:
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -195,6 +195,12 @@ class ElectrumWindow(App):
            def on_fee_histogram(self, *args):
                self._trigger_update_history()
        
       +    def on_payment_received(self, event, wallet, key, status):
       +        if self.request_popup and self.request_popup.key == key:
       +            self.request_popup.set_status(status)
       +        if status == PR_PAID:
       +            self.show_info(_('Payment Received') + '\n' + key)
       +
            def _get_bu(self):
                decimal_point = self.electrum_config.get('decimal_point', DECIMAL_POINT_DEFAULT)
                try:
       t@@ -328,6 +334,7 @@ class ElectrumWindow(App):
                self._settings_dialog = None
                self._password_dialog = None
                self.fee_status = self.electrum_config.get_fee_status()
       +        self.request_popup = None
        
            def on_pr(self, pr):
                if not self.wallet:
       t@@ -397,9 +404,17 @@ class ElectrumWindow(App):
                tab = self.tabs.ids[name + '_tab']
                panel.switch_to(tab)
        
       -    def show_request(self, addr):
       -        self.switch_to('receive')
       -        self.receive_screen.screen.address = addr
       +    def show_request(self, is_lightning, key):
       +        from .uix.dialogs.request_dialog import RequestDialog
       +        if is_lightning:
       +            request, direction, is_paid = self.wallet.lnworker.invoices.get(key) or (None, None, None)
       +            status = self.wallet.lnworker.get_invoice_status(key)
       +        else:
       +            request = self.wallet.get_request_URI(key)
       +            status, conf = self.wallet.get_request_status(key)
       +        self.request_popup = RequestDialog('Request', request, key)
       +        self.request_popup.set_status(status)
       +        self.request_popup.open()
        
            def show_pr_details(self, req, status, is_invoice):
                from electrum.util import format_time
       t@@ -534,6 +549,7 @@ class ElectrumWindow(App):
                    self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
                    self.network.register_callback(self.on_quotes, ['on_quotes'])
                    self.network.register_callback(self.on_history, ['on_history'])
       +            self.network.register_callback(self.on_payment_received, ['payment_received'])
                # load wallet
                self.load_wallet_by_name(self.electrum_config.get_wallet_path())
                # URI passed in config
       t@@ -1047,9 +1063,9 @@ class ElectrumWindow(App):
                popup.update()
                popup.open()
        
       -    def addresses_dialog(self, screen):
       +    def addresses_dialog(self):
                from .uix.dialogs.addresses import AddressesDialog
       -        popup = AddressesDialog(self, screen, None)
       +        popup = AddressesDialog(self)
                popup.update()
                popup.open()
        
   DIR diff --git a/electrum/gui/kivy/theming/light/copy.png b/electrum/gui/kivy/theming/light/copy.png
       Binary files differ.
   DIR diff --git a/electrum/gui/kivy/theming/light/lightning_switch.svg b/electrum/gui/kivy/theming/light/lightning_switch.svg
       t@@ -1,288 +0,0 @@
       -<?xml version="1.0" encoding="UTF-8" standalone="no"?>
       -<!-- Created with Inkscape (http://www.inkscape.org/) -->
       -
       -<svg
       -   xmlns:dc="http://purl.org/dc/elements/1.1/"
       -   xmlns:cc="http://creativecommons.org/ns#"
       -   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       -   xmlns:svg="http://www.w3.org/2000/svg"
       -   xmlns="http://www.w3.org/2000/svg"
       -   xmlns:xlink="http://www.w3.org/1999/xlink"
       -   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
       -   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
       -   width="840"
       -   height="820.04102"
       -   viewBox="0 0 222.25 216.96919"
       -   version="1.1"
       -   id="svg8"
       -   inkscape:version="0.92.3 (2405546, 2018-03-11)"
       -   sodipodi:docname="lightning_switch.svg">
       -  <defs
       -     id="defs2">
       -    <linearGradient
       -       inkscape:collect="always"
       -       id="linearGradient1019">
       -      <stop
       -         style="stop-color:#6464f6;stop-opacity:1;"
       -         offset="0"
       -         id="stop1015" />
       -      <stop
       -         style="stop-color:#76acff;stop-opacity:1"
       -         offset="1"
       -         id="stop1017" />
       -    </linearGradient>
       -    <filter
       -       inkscape:collect="always"
       -       style="color-interpolation-filters:sRGB"
       -       id="filter2183"
       -       x="-0.023532996"
       -       width="1.047066"
       -       y="-0.030062485"
       -       height="1.060125">
       -      <feGaussianBlur
       -         inkscape:collect="always"
       -         stdDeviation="0.92777831"
       -         id="feGaussianBlur2185" />
       -    </filter>
       -    <linearGradient
       -       id="linearGradient980"
       -       x1="94.415001"
       -       x2="166.42999"
       -       y1="48.271999"
       -       y2="-6.3376999"
       -       gradientTransform="matrix(0.90487595,0,0,0.90487595,-32.116675,75.52401)"
       -       gradientUnits="userSpaceOnUse">
       -      <stop
       -         id="stop973"
       -         stop-color="#fff"
       -         offset="0" />
       -      <stop
       -         id="stop975"
       -         stop-color="#fff"
       -         stop-opacity="0"
       -         offset="1" />
       -    </linearGradient>
       -    <filter
       -       inkscape:collect="always"
       -       style="color-interpolation-filters:sRGB"
       -       id="filter3047"
       -       x="-0.055550463"
       -       width="1.1111009"
       -       y="-0.068128757"
       -       height="1.1362575">
       -      <feGaussianBlur
       -         inkscape:collect="always"
       -         stdDeviation="2.1025669"
       -         id="feGaussianBlur3049" />
       -    </filter>
       -    <filter
       -       inkscape:collect="always"
       -       style="color-interpolation-filters:sRGB"
       -       id="filter3047-6"
       -       x="-0.055550463"
       -       width="1.1111009"
       -       y="-0.068128757"
       -       height="1.1362575">
       -      <feGaussianBlur
       -         inkscape:collect="always"
       -         stdDeviation="2.1025669"
       -         id="feGaussianBlur3049-1" />
       -    </filter>
       -    <filter
       -       style="color-interpolation-filters:sRGB"
       -       inkscape:label="Color Shift"
       -       id="filter3759">
       -      <feColorMatrix
       -         type="hueRotate"
       -         values="330"
       -         result="color1"
       -         id="feColorMatrix3755" />
       -      <feColorMatrix
       -         type="saturate"
       -         values="0"
       -         result="color2"
       -         id="feColorMatrix3757" />
       -    </filter>
       -    <filter
       -       inkscape:collect="always"
       -       style="color-interpolation-filters:sRGB"
       -       id="filter7464"
       -       x="-0.085763194"
       -       width="1.1715264"
       -       y="-0.19973423"
       -       height="1.3994684">
       -      <feGaussianBlur
       -         inkscape:collect="always"
       -         stdDeviation="6.6951018"
       -         id="feGaussianBlur7466" />
       -    </filter>
       -    <filter
       -       inkscape:collect="always"
       -       style="color-interpolation-filters:sRGB"
       -       id="filter7532"
       -       x="-0.042373311"
       -       width="1.0847466"
       -       y="-0.098647438"
       -       height="1.197295">
       -      <feGaussianBlur
       -         inkscape:collect="always"
       -         stdDeviation="3.3066669"
       -         id="feGaussianBlur7534" />
       -    </filter>
       -    <linearGradient
       -       inkscape:collect="always"
       -       xlink:href="#linearGradient1019"
       -       id="linearGradient861"
       -       gradientUnits="userSpaceOnUse"
       -       x1="68.955536"
       -       y1="108.44135"
       -       x2="68.688263"
       -       y2="66.212761" />
       -    <linearGradient
       -       inkscape:collect="always"
       -       xlink:href="#linearGradient980"
       -       id="linearGradient863"
       -       gradientUnits="userSpaceOnUse"
       -       gradientTransform="matrix(0.90487595,0,0,0.90487595,-32.116675,75.52401)"
       -       x1="94.415001"
       -       y1="48.271999"
       -       x2="166.42999"
       -       y2="-6.3376999" />
       -  </defs>
       -  <sodipodi:namedview
       -     id="base"
       -     pagecolor="#000000"
       -     bordercolor="#666666"
       -     borderopacity="1.0"
       -     inkscape:pageopacity="0"
       -     inkscape:pageshadow="2"
       -     inkscape:zoom="1"
       -     inkscape:cx="256.4408"
       -     inkscape:cy="683.69642"
       -     inkscape:document-units="mm"
       -     inkscape:current-layer="layer1"
       -     showgrid="false"
       -     inkscape:lockguides="true"
       -     inkscape:window-width="3066"
       -     inkscape:window-height="1689"
       -     inkscape:window-x="134"
       -     inkscape:window-y="55"
       -     inkscape:window-maximized="1"
       -     fit-margin-top="0"
       -     fit-margin-left="0"
       -     fit-margin-right="0"
       -     fit-margin-bottom="0"
       -     units="px"
       -     inkscape:pagecheckerboard="false" />
       -  <metadata
       -     id="metadata5">
       -    <rdf:RDF>
       -      <cc:Work
       -         rdf:about="">
       -        <dc:format>image/svg+xml</dc:format>
       -        <dc:type
       -           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
       -        <dc:title></dc:title>
       -      </cc:Work>
       -    </rdf:RDF>
       -  </metadata>
       -  <g
       -     inkscape:label="Layer 1"
       -     inkscape:groupmode="layer"
       -     id="layer1"
       -     transform="translate(-5.9067634,-55.147908)"
       -     style="display:inline">
       -    <use
       -       x="0"
       -       y="0"
       -       xlink:href="#g6758"
       -       id="use6798"
       -       transform="translate(0,108.47917)"
       -       width="100%"
       -       height="100%" />
       -    <g
       -       id="g6758"
       -       transform="matrix(1.0279896,0,0,1,-0.39555549,0)">
       -      <rect
       -         y="68.48455"
       -         x="19.243406"
       -         height="80.448128"
       -         width="187.35594"
       -         id="rect815"
       -         style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#646464;fill-opacity:1;stroke:#555555;stroke-width:5;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" />
       -      <path
       -         inkscape:connector-curvature="0"
       -         id="path817"
       -         d="M 19.243406,68.484551 H 206.59935 V 148.93268 H 19.243406 Z"
       -         style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter7464)" />
       -      <path
       -         transform="matrix(1.0553762,0,0,1.123304,-2.8259824,-10.045808)"
       -         inkscape:connector-curvature="0"
       -         id="path817-5"
       -         d="M 16.068404,65.838715 H 203.35612 V 146.28683 H 16.068404 Z"
       -         style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:11.48041821;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.19002374;filter:url(#filter7532)"
       -         sodipodi:nodetypes="ccccc" />
       -    </g>
       -    <g
       -       id="g3255">
       -      <rect
       -         y="71.281387"
       -         x="21.910538"
       -         height="74.067993"
       -         width="90.839211"
       -         id="rect1013"
       -         style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient861);fill-opacity:1;stroke:none;stroke-width:5;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;filter:url(#filter2183);enable-background:accumulate" />
       -      <path
       -         d="M 38.12696,110.58365 78.846174,76.421035 c 1.883303,-1.403703 4.668394,-4.204849 2.34658,0.828194 l -13.527082,25.467191 23.120205,0.34508 c 1.057575,0.11762 2.815437,-0.14879 1.173278,1.44929 L 51.377359,139.985 c -2.604817,2.07419 -6.255505,5.67223 -2.69162,-1.2423 l 13.251022,-25.39781 -22.913402,-0.55213 c -2.156371,0.0996 -2.643184,-0.5521 -0.897201,-2.20849 z"
       -         id="path817-3"
       -         style="fill:url(#linearGradient863);fill-rule:evenodd;stroke-width:0.13605724"
       -         inkscape:connector-curvature="0" />
       -      <g
       -         transform="rotate(180,67.330143,108.31538)"
       -         id="g3148">
       -        <path
       -           style="fill:none;fill-rule:evenodd;stroke:#000976;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3047)"
       -           d="M 112.74975,71.281387 V 145.34938 H 21.910537"
       -           id="path2289"
       -           inkscape:connector-curvature="0"
       -           transform="rotate(-180,67.330143,108.31538)" />
       -        <path
       -           style="fill:none;fill-rule:evenodd;stroke:#91c5ff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.50831353;filter:url(#filter3047-6)"
       -           d="M 112.74975,71.281389 V 145.34938 H 21.910538"
       -           id="path2289-8"
       -           inkscape:connector-curvature="0" />
       -      </g>
       -    </g>
       -    <text
       -       xml:space="preserve"
       -       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.21456909px;line-height:100%;font-family:FreeSans;-inkscape-font-specification:'Sans Bold';text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.48097134px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       -       x="163.34431"
       -       y="121.71754"
       -       id="text3053"><tspan
       -         sodipodi:role="line"
       -         id="tspan3051"
       -         x="163.34431"
       -         y="121.71754"
       -         style="fill:#ffffff;fill-opacity:1;stroke-width:2.48097134px">ON</tspan></text>
       -    <text
       -       xml:space="preserve"
       -       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.21456909px;line-height:100%;font-family:FreeSans;-inkscape-font-specification:'Sans Bold';text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.48097134px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       -       x="70.730072"
       -       y="229.30695"
       -       id="text3053-9"><tspan
       -         sodipodi:role="line"
       -         id="tspan3051-3"
       -         x="70.730072"
       -         y="229.30695"
       -         style="fill:#ffffff;fill-opacity:1;stroke-width:2.48097134px">OFF</tspan></text>
       -    <use
       -       x="0"
       -       y="0"
       -       xlink:href="#g3255"
       -       id="use3263"
       -       transform="translate(96.165992,108.52486)"
       -       width="100%"
       -       height="100%"
       -       style="filter:url(#filter3759)" />
       -  </g>
       -</svg>
   DIR diff --git a/electrum/gui/kivy/theming/light/list.png b/electrum/gui/kivy/theming/light/list.png
       Binary files differ.
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/addresses.py b/electrum/gui/kivy/uix/dialogs/addresses.py
       t@@ -104,11 +104,9 @@ from electrum.gui.kivy.uix.context_menu import ContextMenu
        
        class AddressesDialog(Factory.Popup):
        
       -    def __init__(self, app, screen, callback):
       +    def __init__(self, app):
                Factory.Popup.__init__(self)
                self.app = app
       -        self.screen = screen
       -        self.callback = callback
                self.context_menu = None
        
            def get_card(self, addr, balance, is_used, label):
       t@@ -155,7 +153,8 @@ class AddressesDialog(Factory.Popup):
            def do_use(self, obj):
                self.hide_menu()
                self.dismiss()
       -        self.app.show_request(obj.address)
       +        self.app.switch_to('receive')
       +        self.app.receive_screen.set_address(obj.address)
        
            def do_view(self, obj):
                req = { 'address': obj.address, 'status' : obj.status }
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/qr_dialog.py b/electrum/gui/kivy/uix/dialogs/qr_dialog.py
       t@@ -23,6 +23,11 @@ Builder.load_string('''
                    spacing: '10dp'
                    QRCodeWidget:
                        id: qr
       +                shaded: False
       +                foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)
       +                on_touch_down:
       +                    touch = args[1]
       +                    if self.collide_point(*touch.pos): self.shaded = not self.shaded
                    TopLabel:
                        text: root.data if root.show_text else ''
                    Widget:
       t@@ -33,9 +38,14 @@ Builder.load_string('''
                        Button:
                            size_hint: 1, None
                            height: '48dp'
       -                    text: _('Copy to clipboard')
       +                    text: _('Copy')
                            on_release:
                                root.copy_to_clipboard()
       +                IconButton:
       +                    icon: 'atlas://electrum/gui/kivy/theming/light/share'
       +                    size_hint: 0.6, None
       +                    height: '48dp'
       +                    on_release: s.parent.do_share()
                        Button:
                            size_hint: 1, None
                            height: '48dp'
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/request_dialog.py b/electrum/gui/kivy/uix/dialogs/request_dialog.py
       t@@ -0,0 +1,82 @@
       +from kivy.factory import Factory
       +from kivy.lang import Builder
       +from kivy.core.clipboard import Clipboard
       +from kivy.app import App
       +from kivy.clock import Clock
       +
       +from electrum.gui.kivy.i18n import _
       +from electrum.util import pr_tooltips
       +
       +
       +Builder.load_string('''
       +<RequestDialog@Popup>
       +    id: popup
       +    title: ''
       +    data: ''
       +    status: 'unknown'
       +    shaded: False
       +    show_text: False
       +    AnchorLayout:
       +        anchor_x: 'center'
       +        BoxLayout:
       +            orientation: 'vertical'
       +            size_hint: 1, 1
       +            padding: '10dp'
       +            spacing: '10dp'
       +            QRCodeWidget:
       +                id: qr
       +                shaded: False
       +                foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)
       +                on_touch_down:
       +                    touch = args[1]
       +                    if self.collide_point(*touch.pos): self.shaded = not self.shaded
       +            TopLabel:
       +                text: root.data
       +            TopLabel:
       +                text: _('Status') + ': ' + root.status
       +            Widget:
       +                size_hint: 1, 0.2
       +            BoxLayout:
       +                size_hint: 1, None
       +                height: '48dp'
       +                Button:
       +                    size_hint: 1, None
       +                    height: '48dp'
       +                    text: _('Copy')
       +                    on_release:
       +                        root.copy_to_clipboard()
       +                IconButton:
       +                    icon: 'atlas://electrum/gui/kivy/theming/light/share'
       +                    size_hint: 0.6, None
       +                    height: '48dp'
       +                    on_release: s.parent.do_share()
       +                Button:
       +                    size_hint: 1, None
       +                    height: '48dp'
       +                    text: _('Close')
       +                    on_release:
       +                        popup.dismiss()
       +''')
       +
       +class RequestDialog(Factory.Popup):
       +    def __init__(self, title, data, key):
       +        Factory.Popup.__init__(self)
       +        self.app = App.get_running_app()
       +        self.title = title
       +        self.data = data
       +        self.key = key
       +        #self.text_for_clipboard = text_for_clipboard if text_for_clipboard else data
       +
       +    def on_open(self):
       +        self.ids.qr.set_data(self.data)
       +
       +    def set_status(self, status):
       +        self.status = pr_tooltips[status]
       +
       +    def on_dismiss(self):
       +        self.app.request_popup = None
       +
       +    def copy_to_clipboard(self):
       +        Clipboard.copy(self.data)
       +        msg = _('Text copied to clipboard.')
       +        Clock.schedule_once(lambda dt: self.app.show_info(msg))
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/requests.py b/electrum/gui/kivy/uix/dialogs/requests.py
       t@@ -4,6 +4,12 @@ from kivy.properties import ObjectProperty
        from kivy.lang import Builder
        from decimal import Decimal
        
       +from electrum.util import age, PR_UNPAID
       +from electrum.lnutil import SENT, RECEIVED
       +from electrum.lnaddr import lndecode
       +import electrum.constants as constants
       +from electrum.bitcoin import COIN
       +
        Builder.load_string('''
        <RequestLabel@Label>
            #color: .305, .309, .309, 1
       t@@ -58,7 +64,7 @@ Builder.load_string('''
        
        <RequestsDialog@Popup>
            id: popup
       -    title: _('Requests')
       +    title: _('Pending requests')
            BoxLayout:
                id:box
                orientation: 'vertical'
       t@@ -103,21 +109,20 @@ class RequestsDialog(Factory.Popup):
                self.cards = {}
                self.context_menu = None
        
       -    def get_card(self, req):
       -        address = req['address']
       -        ci = self.cards.get(address)
       +    def get_card(self, is_lightning, key, address, amount, memo, timestamp):
       +        ci = self.cards.get(key)
                if ci is None:
                    ci = Factory.RequestItem()
                    ci.address = address
                    ci.screen = self
       -            self.cards[address] = ci
       +            ci.is_lightning = is_lightning
       +            ci.key = key
       +            self.cards[key] = ci
        
       -        amount = req.get('amount')
                ci.amount = self.app.format_amount_and_units(amount) if amount else ''
       -        ci.memo = req.get('memo', '')
       -        status, conf = self.app.wallet.get_request_status(address)
       -        ci.status = request_text[status]
       -        ci.icon = pr_icon[status]
       +        ci.memo = memo
       +        ci.status = age(timestamp)
       +        #ci.icon = pr_icon[status]
                #exp = pr.get_expiration_date()
                #ci.date = format_time(exp) if exp else _('Never')
                return ci
       t@@ -127,14 +132,27 @@ class RequestsDialog(Factory.Popup):
                requests_list = self.ids.requests_container
                requests_list.clear_widgets()
                _list = self.app.wallet.get_sorted_requests(self.app.electrum_config)
       -        for pr in _list:
       -            ci = self.get_card(pr)
       +        for req in _list[::-1]:
       +            is_lightning = req.get('lightning', False)
       +            status = req['status']
       +            if status != PR_UNPAID:
       +                continue
       +            if not is_lightning:
       +                address = req['address']
       +                key = address
       +            else:
       +                key = req['rhash']
       +                address = req['invoice']
       +            timestamp = req.get('time', 0)
       +            amount = req.get('amount')
       +            description = req.get('memo', '')
       +            ci = self.get_card(is_lightning, key, address, amount, description, timestamp)
                    requests_list.add_widget(ci)
        
            def do_show(self, obj):
                self.hide_menu()
                self.dismiss()
       -        self.app.show_request(obj.address)
       +        self.app.show_request(obj.is_lightning, obj.key)
        
            def do_delete(self, req):
                from .question import Question
   DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
       t@@ -31,7 +31,7 @@ from electrum.plugin import run_hook
        from electrum.wallet import InternalAddressCorruption
        from electrum import simple_config
        from electrum.lnaddr import lndecode
       -from electrum.lnutil import RECEIVED, SENT
       +from electrum.lnutil import RECEIVED, SENT, PaymentFailure
        
        from .context_menu import ContextMenu
        from .dialogs.lightning_open_channel import LightningOpenChannelDialog
       t@@ -233,7 +233,7 @@ class SendScreen(CScreen):
                    self.screen.destinationtype = Destination.Address
                    self.payment_request = None
        
       -    def do_save(self):
       +    def save_invoice(self):
                if not self.screen.address:
                    return
                if self.screen.destinationtype == Destination.PR:
       t@@ -247,7 +247,7 @@ class SendScreen(CScreen):
                pr = make_unsigned_request(req).SerializeToString()
                pr = PaymentRequest(pr)
                self.app.wallet.invoices.add(pr)
       -        self.app.show_info(_("Invoice saved"))
       +        #self.app.show_info(_("Invoice saved"))
                if pr.is_pr():
                    self.screen.destinationtype = Destination.PR
                    self.payment_request = pr
       t@@ -275,6 +275,8 @@ class SendScreen(CScreen):
                    self.set_ln_invoice(data.rstrip())
                else:
                    self.set_URI(data)
       +            # save automatically
       +            self.save_invoice()
        
            def _do_send_lightning(self):
                if not self.screen.amount:
       t@@ -282,27 +284,15 @@ class SendScreen(CScreen):
                    return
                invoice = self.screen.address
                amount_sat = self.app.get_amount(self.screen.amount)
       -        addr = self.app.wallet.lnworker._check_invoice(invoice, amount_sat)
                try:
       -            route = self.app.wallet.lnworker._create_route_from_invoice(decoded_invoice=addr)
       -        except Exception as e:
       -            dia = LightningOpenChannelDialog(self.app, addr, str(e) + _(':\nYou can open a channel.'))
       -            dia.open()
       +            success = self.app.wallet.lnworker.pay(invoice, attempts=10, amount_sat=amount_sat, timeout=60)
       +        except PaymentFailure as e:
       +            self.app.show_error(_('Payment failure') + '\n' + str(e))
                    return
       -        self.app.network.register_callback(self.payment_completed_async_thread, ['ln_payment_completed'])
       -        _addr, _peer, coro = self.app.wallet.lnworker._pay(invoice, amount_sat)
       -        fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop)
       -        fut.add_done_callback(self.ln_payment_result)
       -
       -    def payment_completed_async_thread(self, event, date, direction, htlc, preimage, chan_id):
       -        Clock.schedule_once(lambda dt: self.payment_completed(direction, htlc, preimage))
       -
       -    def payment_completed(self, direction, htlc, preimage):
       -        self.app.show_info(_('Payment received') if direction == RECEIVED else _('Payment sent'))
       -
       -    def ln_payment_result(self, fut):
       -        if fut.exception():
       -            self.app.show_error(_('Lightning payment failed:') + '\n' + repr(fut.exception()))
       +        if success:
       +            self.app.show_info(_('Payment was sent'))
       +        else:
       +            self.app.show_error(_('Payment failed'))
        
            def do_send(self):
                if self.screen.destinationtype == Destination.LN:
       t@@ -389,37 +379,14 @@ class ReceiveScreen(CScreen):
        
            kvname = 'receive'
        
       -    def update(self):
       -        if not self.screen.address:
       -            self.get_new_address()
       -        else:
       -            status = self.app.wallet.get_request_status(self.screen.address)
       -            self.screen.status = _('Payment received') if status == PR_PAID else ''
       -
            def clear(self):
                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
       -        is unused."""
       -        if not self.app.wallet:
       -            return False
       -        self.clear()
       -        unused = True
       -        try:
       -            addr = self.app.wallet.get_unused_address()
       -            if addr is None:
       -                addr = self.app.wallet.get_receiving_address() or ''
       -                unused = False
       -        except InternalAddressCorruption as e:
       -            addr = ''
       -            self.app.show_error(str(e))
       -            send_exception_to_crash_reporter(e)
       +    def set_address(self, addr):
                self.screen.address = addr
       -        return unused
        
            def on_address(self, addr):
                req = self.app.wallet.get_payment_request(addr, self.app.electrum_config)
       t@@ -430,7 +397,6 @@ class ReceiveScreen(CScreen):
                    self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
                    status = req.get('status', PR_UNKNOWN)
                    self.screen.status = _('Payment received') if status == PR_PAID else ''
       -        Clock.schedule_once(lambda dt: self.update_qr())
        
            def get_URI(self):
                from electrum.util import create_bip21_uri
       t@@ -441,73 +407,37 @@ class ReceiveScreen(CScreen):
                    amount = Decimal(a) * pow(10, self.app.decimal_point())
                return create_bip21_uri(self.screen.address, amount, self.screen.message)
        
       -    @profiler
       -    def update_qr(self):
       -        qr = self.screen.ids.qr
       -        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):
       -        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"))
       +        uri = self.get_URI()
       +        self.app.do_share(uri, _("Share Bitcoin Request"))
        
            def do_copy(self):
       -        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
       -        if not addr:
       -            return False
       +        uri = self.get_URI()
       +        self.app._clipboard.copy(uri)
       +        self.app.show_info(_('Request copied to clipboard'))
       +
       +    def new_request(self, lightning):
                amount = self.screen.amount
       -        message = self.screen.message
                amount = self.app.get_amount(amount) if amount else 0
       -        req = self.app.wallet.make_payment_request(addr, amount, message, None)
       -        try:
       +        message = self.screen.message
       +        expiration = 3600 # 1 hour
       +        if lightning:
       +            payment_hash = self.app.wallet.lnworker.add_invoice(amount, message)
       +            request, direction, is_paid = self.app.wallet.lnworker.invoices.get(payment_hash.hex())
       +            key = payment_hash.hex()
       +        else:
       +            addr = self.screen.address or self.app.wallet.get_unused_address()
       +            if not addr:
       +                self.app.show_info(_('No address available. Please remove some of your pending requests.'))
       +                return
       +            self.screen.address = addr
       +            req = self.app.wallet.make_payment_request(addr, amount, message, expiration)
                    self.app.wallet.add_payment_request(req, self.app.electrum_config)
       -            added_request = True
       -        except Exception as e:
       -            self.app.show_error(_('Error adding payment request') + ':\n' + repr(e))
       -            added_request = False
       -        finally:
       -            self.app.update_tab('requests')
       -        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):
       -        is_unused = self.get_new_address()
       -        if not is_unused:
       -            self.app.show_info(_('Please use the existing requests first.'))
       -
       -    def do_save(self):
       -        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()
       +            #request = self.get_URI()
       +            key = addr
       +        self.app.show_request(lightning, key)
       +
       +
        
        class TabbedCarousel(Factory.TabbedPanel):
            '''Custom TabbedPanel using a carousel used in the Main Screen
       t@@ -581,15 +511,3 @@ 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'
   DIR diff --git a/electrum/gui/kivy/uix/ui_screens/receive.kv b/electrum/gui/kivy/uix/ui_screens/receive.kv
       t@@ -9,51 +9,16 @@
        ReceiveScreen:
            id: s
            name: 'receive'
       -
            address: ''
            amount: ''
            message: ''
            status: ''
       -    lnaddr: ''
       -
       -    on_address:
       -        self.parent.on_address(self.address)
       -    on_amount:
       -        self.parent.on_amount_or_message()
       -    on_message:
       -        self.parent.on_amount_or_message()
       +    is_lightning: False
        
            BoxLayout
                padding: '12dp', '12dp', '12dp', '12dp'
                spacing: '12dp'
                orientation: 'vertical'
       -        size_hint: 1, 1
       -        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)
       -                pos_hint: {'center': (.5, .5)}
       -                shaded: False
       -                foreground_color: (0, 0, 0, 0.5) if self.shaded else (0, 0, 0, 0)
       -                on_touch_down:
       -                    touch = args[1]
       -                    if self.collide_point(*touch.pos): self.shaded = not self.shaded
       -            Label:
       -                text: root.status
       -                opacity: 1 if root.status else 0
       -                pos_hint: {'center': (.5, .5)}
       -                size_hint: None, 1
       -                width: min(self.height, bl.width)
       -                bcolor: 0.3, 0.3, 0.3, 0.9
       -                canvas.before:
       -                    Color:
       -                        rgba: self.bcolor
       -                    Rectangle:
       -                        pos: self.pos
       -                        size: self.size
        
                SendReceiveBlueBottom:
                    id: blue_bottom
       t@@ -64,15 +29,17 @@ ReceiveScreen:
                        height: blue_bottom.item_height
                        spacing: '5dp'
                        Image:
       -                    source: 'atlas://electrum/gui/kivy/theming/light/lightning' if lnbutton.state == 'down' else 'atlas://electrum/gui/kivy/theming/light/globe'
       +                    source: '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')) if lnbutton.state != 'down' else (s.lnaddr if s.lnaddr else _('Please enter amount'))
       +                    text: _('Lightning') if root.is_lightning else (s.address if s.address else _('Bitcoin Address'))
                            shorten: True
       -                    on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s) if lnbutton.state != 'down' else s.parent.do_copy())
       +                    #on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
       +                    on_release:
       +                        root.is_lightning = not root.is_lightning
                    CardSeparator:
                        opacity: message_selection.opacity
                        color: blue_bottom.foreground_color
       t@@ -113,37 +80,31 @@ ReceiveScreen:
                    size_hint: 1, None
                    height: '48dp'
                    IconButton:
       -                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() if lnbutton.state != 'down' else None
       -                width: (0 if lnbutton.state == 'down' else 100)
       -            Button:
       -                text: _('Requests') if lnbutton.state != 'down' else _('Lightning Invoices')
       -                size_hint: 1 + (.6 if lnbutton.state == 'down' else 0), None
       +                icon: 'atlas://electrum/gui/kivy/theming/light/list'
       +                size_hint: 1, None
                        height: '48dp'
       -                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))
       +                on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
       +            #Widget:
       +            #    size_hint: 0.5, 1
                    Button:
       -                text: _('Copy')
       +                text: _('Clear')
                        size_hint: 1, None
                        height: '48dp'
       -                on_release: s.parent.do_copy()
       -            IconButton:
       -                icon: 'atlas://electrum/gui/kivy/theming/light/share'
       -                size_hint: 0.6, None
       -                height: '48dp'
       -                on_release: s.parent.do_share()
       -        BoxLayout:
       -            size_hint: 1, None
       -            height: '48dp'
       -            LightningButton
       -                id: lnbutton
       -                on_state: s.parent.on_amount_or_message()
       -            Widget
       -                size_hint: 1, 1
       +                on_release: Clock.schedule_once(lambda dt: s.parent.clear())
                    Button:
       -                text: _('New')
       +                text: _('Request')
                        size_hint: 1, None
                        height: '48dp'
       -                on_release: Clock.schedule_once(lambda dt: s.parent.do_new())
       +                on_release: Clock.schedule_once(lambda dt: s.parent.new_request(root.is_lightning))
       +        Widget:
       +            size_hint: 1, 1
       +        #BoxLayout:
       +        #    size_hint: 1, None
       +        #    height: '48dp'
       +        #    IconButton:
       +        #        icon: 'atlas://electrum/gui/kivy/theming/light/list'
       +        #        size_hint: 0.5, None
       +        #        height: '48dp'
       +        #        on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
       +        #    Widget:
       +        #        size_hint: 2.5, 1
   DIR diff --git a/electrum/gui/kivy/uix/ui_screens/send.kv b/electrum/gui/kivy/uix/ui_screens/send.kv
       t@@ -70,7 +70,7 @@ SendScreen:
                            pos_hint: {'center_y': .5}
                        BlueButton:
                            id: description
       -                    text: s.message if s.message else ({Destination.LN: _('Lightning invoice contains no description'), Destination.Address: _('Description'), Destination.PR: _('No Description')}[root.destinationtype])
       +                    text: s.message if s.message else ({Destination.LN: _('No description'), Destination.Address: _('Description'), Destination.PR: _('No Description')}[root.destinationtype])
                            disabled: root.destinationtype != Destination.Address
                            on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
                    CardSeparator:
       t@@ -95,34 +95,34 @@ SendScreen:
                    size_hint: 1, None
                    height: '48dp'
                    IconButton:
       -                size_hint: 0.6, 1
       -                on_release: s.parent.do_save()
       -                icon: 'atlas://electrum/gui/kivy/theming/light/save'
       -            Button:
       -                text: _('Invoices')
       -                size_hint: 1, 1
       -                on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s))
       -            Button:
       -                text: _('Paste')
       +                size_hint: 0.5, 1
       +                icon: 'atlas://electrum/gui/kivy/theming/light/copy'
                        on_release: s.parent.do_paste()
                    IconButton:
                        id: qr
       -                size_hint: 0.6, 1
       +                size_hint: 0.5, 1
                        on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=app.on_qr))
                        icon: 'atlas://electrum/gui/kivy/theming/light/camera'
       -        BoxLayout:
       -            size_hint: 1, None
       -            height: '48dp'
                    Button:
                        text: _('Clear')
       -                on_release: s.parent.do_clear()
       -            Widget:
                        size_hint: 1, 1
       +                on_release: s.parent.do_clear()
                    Button:
                        text: _('Pay')
                        size_hint: 1, 1
                        on_release: s.parent.do_send()
                Widget:
                    size_hint: 1, 1
       -
       -
       +        #BoxLayout:
       +        #    size_hint: 1, None
       +        #    height: '48dp'
       +            #IconButton:
       +            #    size_hint: 0.5, 1
       +            #    on_release: s.parent.do_save()
       +            #    icon: 'atlas://electrum/gui/kivy/theming/light/save'
       +            #IconButton:
       +            #    size_hint: 0.5, 1
       +            #    icon: 'atlas://electrum/gui/kivy/theming/light/list'
       +            #    on_release: Clock.schedule_once(lambda dt: app.invoices_dialog(s))
       +            #Widget:
       +            #    size_hint: 2.5, 1
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -224,7 +224,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
                                 'new_transaction', 'status',
                                 'banner', 'verified', 'fee', 'fee_histogram', 'on_quotes',
       -                         'on_history', 'channel', 'channels', 'ln_message',
       +                         'on_history', 'channel', 'channels', 'payment_received',
                                 'ln_payment_completed', 'ln_payment_attempt']
                    # To avoid leaking references to "self" that prevent the
                    # window from being GC-ed when closed, callbacks should be
       t@@ -362,7 +362,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    wallet, tx = args
                    if wallet == self.wallet:
                        self.tx_notification_queue.put(tx)
       -        elif event in ['status', 'banner', 'verified', 'fee', 'fee_histogram', 'ln_message']:
       +        elif event in ['status', 'banner', 'verified', 'fee', 'fee_histogram', 'payment_received']:
                    # Handle in GUI thread
                    self.network_signal.emit(event, args)
                elif event == 'on_quotes':
       t@@ -404,10 +404,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                        self.fee_slider.update()
                        self.require_fee_update = True
                    self.history_model.on_fee_histogram()
       -        elif event == 'ln_message':
       -            lnworker, message, htlc_id = args
       -            if lnworker == self.wallet.lnworker:
       -                self.notify(message)
       +        elif event == 'payment_received':
       +            wallet, key, status = args
       +            if wallet == self.wallet:
       +                self.notify(_('Payment received') + '\n' + key)
                else:
                    self.logger.info(f"unexpected network_qt signal: {event} {args}")
        
       t@@ -1039,24 +1039,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.invoice_list.update()
                self.clear_receive_tab()
        
       -    def get_request_URI(self, addr):
       -        req = self.wallet.receive_requests[addr]
       -        message = self.wallet.labels.get(addr, '')
       -        amount = req['amount']
       -        extra_query_params = {}
       -        if req.get('time'):
       -            extra_query_params['time'] = str(int(req.get('time')))
       -        if req.get('exp'):
       -            extra_query_params['exp'] = str(int(req.get('exp')))
       -        if req.get('name') and req.get('sig'):
       -            sig = bfh(req.get('sig'))
       -            sig = bitcoin.base_encode(sig, base=58)
       -            extra_query_params['name'] = req['name']
       -            extra_query_params['sig'] = sig
       -        uri = util.create_bip21_uri(addr, amount, message, extra_query_params=extra_query_params)
       -        return str(uri)
       -
       -
            def sign_payment_request(self, addr):
                alias = self.config.get('alias')
                alias_privkey = None
   DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -90,9 +90,9 @@ class RequestList(MyTreeView):
                    if req is None:
                        self.update()
                        return
       -            req = self.parent.get_request_URI(key)
       +            req = self.wallet.get_request_URI(key)
                elif request_type == REQUEST_TYPE_LN:
       -            req, direction, is_paid = self.wallet.lnworker.invoices.get(key) or (None, None)
       +            req, direction, is_paid = self.wallet.lnworker.invoices.get(key) or (None, None, None)
                    if req is None:
                        self.update()
                        return
       t@@ -107,51 +107,37 @@ class RequestList(MyTreeView):
                self.model().clear()
                self.update_headers(self.__class__.headers)
                for req in self.wallet.get_sorted_requests(self.config):
       -            address = req['address']
       -            if address not in domain:
       -                continue
       +            request_type = REQUEST_TYPE_LN if req.get('lightning', False) else REQUEST_TYPE_BITCOIN
                    timestamp = req.get('time', 0)
                    amount = req.get('amount')
       -            expiration = req.get('exp', None)
                    message = req['memo']
                    date = format_time(timestamp)
                    status = req.get('status')
       -            signature = req.get('sig')
       -            requestor = req.get('name', '')
                    amount_str = self.parent.format_amount(amount) if amount else ""
                    labels = [date, message, amount_str, pr_tooltips.get(status,'')]
                    items = [QStandardItem(e) for e in labels]
                    self.set_editability(items)
       -            if signature is not None:
       -                items[self.Columns.DATE].setIcon(read_QIcon("seal.png"))
       -                items[self.Columns.DATE].setToolTip(f'signed by {requestor}')
       -            else:
       -                items[self.Columns.DATE].setIcon(read_QIcon("bitcoin.png"))
       +            items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
                    items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
       +            if request_type == REQUEST_TYPE_LN:
       +                items[self.Columns.DATE].setData(req['rhash'], ROLE_RHASH_OR_ADDR)
       +                items[self.Columns.DATE].setIcon(read_QIcon("lightning.png"))
       +                items[self.Columns.DATE].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
       +            else:
       +                address = req['address']
       +                if address not in domain:
       +                    continue
       +                expiration = req.get('exp', None)
       +                signature = req.get('sig')
       +                requestor = req.get('name', '')
       +                items[self.Columns.DATE].setData(address, ROLE_RHASH_OR_ADDR)
       +                if signature is not None:
       +                    items[self.Columns.DATE].setIcon(read_QIcon("seal.png"))
       +                    items[self.Columns.DATE].setToolTip(f'signed by {requestor}')
       +                else:
       +                    items[self.Columns.DATE].setIcon(read_QIcon("bitcoin.png"))
                    self.model().insertRow(self.model().rowCount(), items)
       -            items[self.Columns.DATE].setData(REQUEST_TYPE_BITCOIN, ROLE_REQUEST_TYPE)
       -            items[self.Columns.DATE].setData(address, ROLE_RHASH_OR_ADDR)
                self.filter()
       -        # lightning
       -        lnworker = self.wallet.lnworker
       -        items = lnworker.invoices.items() if lnworker else []
       -        for key, (invoice, direction, is_paid) in items:
       -            if direction == SENT:
       -                continue
       -            status = lnworker.get_invoice_status(key)
       -            lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
       -            amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
       -            amount_str = self.parent.format_amount(amount_sat) if amount_sat else ''
       -            description = lnaddr.get_description()
       -            date = format_time(lnaddr.date)
       -            labels = [date, description, amount_str, pr_tooltips.get(status,'')]
       -            items = [QStandardItem(e) for e in labels]
       -            self.set_editability(items)
       -            items[self.Columns.DATE].setIcon(read_QIcon("lightning.png"))
       -            items[self.Columns.DATE].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
       -            items[self.Columns.DATE].setData(key, ROLE_RHASH_OR_ADDR)
       -            items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
       -            self.model().insertRow(self.model().rowCount(), items)
                # sort requests by date
                self.model().sort(self.Columns.DATE)
                # hide list if empty
       t@@ -192,7 +178,7 @@ class RequestList(MyTreeView):
        
            def create_menu_bitcoin_payreq(self, menu, addr):
                menu.addAction(_("Copy Address"), lambda: self.parent.do_copy('Address', addr))
       -        menu.addAction(_("Copy URI"), lambda: self.parent.do_copy('URI', self.parent.get_request_URI(addr)))
       +        menu.addAction(_("Copy URI"), lambda: self.parent.do_copy('URI', self.wallet.get_request_URI(addr)))
                menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
                menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
                run_hook('receive_list_menu', menu, addr)
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -1249,7 +1249,7 @@ class Peer(Logger):
                                  id=htlc_id,
                                  payment_preimage=preimage)
                await self.await_remote(chan, remote_ctn)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment received', htlc_id)
       +        #self.lnworker.payment_received(htlc_id)
        
            async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket,
                                reason: OnionRoutingFailureMessage):
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -668,7 +668,6 @@ class LNWallet(LNWorker):
                except concurrent.futures.TimeoutError:
                    raise PaymentFailure(_("Payment timed out"))
        
       -
            def get_channel_by_short_id(self, short_channel_id):
                with self.lock:
                    for chan in self.channels.values():
       t@@ -812,6 +811,8 @@ class LNWallet(LNWorker):
                    return
                invoice, direction, _ = self.invoices[key]
                self.save_invoice(payment_hash, invoice, direction, is_paid=True)
       +        if direction == RECEIVED:
       +            self.network.trigger_callback('payment_received', self.wallet, key, PR_PAID)
        
            def get_invoice(self, payment_hash: bytes) -> LnAddr:
                try:
       t@@ -820,6 +821,28 @@ class LNWallet(LNWorker):
                except KeyError as e:
                    raise UnknownPaymentHash(payment_hash) from e
        
       +    def get_invoices(self):
       +        items = self.invoices.items()
       +        out = []
       +        for key, (invoice, direction, is_paid) in items:
       +            if direction == SENT:
       +                continue
       +            status = self.get_invoice_status(key)
       +            lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
       +            amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
       +            description = lnaddr.get_description()
       +            timestamp = lnaddr.date
       +            out.append({
       +                'lightning':True,
       +                'status':status,
       +                'amount':amount_sat,
       +                'time':timestamp,
       +                'memo':description,
       +                'rhash':key,
       +                'invoice': invoice
       +            })
       +        return out
       +
            def _calc_routing_hints_for_invoice(self, amount_sat):
                """calculate routing hints (BOLT-11 'r' field)"""
                self.channel_db.load_data()
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -34,6 +34,7 @@ import json
        import copy
        import errno
        import traceback
       +import operator
        from functools import partial
        from numbers import Number
        from decimal import Decimal
       t@@ -44,7 +45,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler,
                           format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
                           WalletFileException, BitcoinException,
                           InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
       -                   Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate)
       +                   Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri)
        from .simple_config import get_config
        from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script,
                              is_minikey, relayfee, dust_threshold)
       t@@ -1134,10 +1135,10 @@ class Abstract_Wallet(AddressSynchronizer):
                return wrapper
        
            def get_unused_addresses(self):
       -        # fixme: use slots from expired requests
                domain = self.get_receiving_addresses()
       +        in_use = [k for k in self.receive_requests.keys() if self.get_request_status(k)[0] != PR_EXPIRED]
                return [addr for addr in domain if not self.db.get_addr_history(addr)
       -                and addr not in self.receive_requests.keys()]
       +                and addr not in in_use]
        
            @check_returned_address
            def get_unused_address(self):
       t@@ -1218,31 +1219,50 @@ class Abstract_Wallet(AddressSynchronizer):
                            out['websocket_port'] = config.get('websocket_port', 9999)
                return out
        
       +    def get_request_URI(self, addr):
       +        req = self.receive_requests[addr]
       +        message = self.labels.get(addr, '')
       +        amount = req['amount']
       +        extra_query_params = {}
       +        if req.get('time'):
       +            extra_query_params['time'] = str(int(req.get('time')))
       +        if req.get('exp'):
       +            extra_query_params['exp'] = str(int(req.get('exp')))
       +        if req.get('name') and req.get('sig'):
       +            sig = bfh(req.get('sig'))
       +            sig = bitcoin.base_encode(sig, base=58)
       +            extra_query_params['name'] = req['name']
       +            extra_query_params['sig'] = sig
       +        uri = create_bip21_uri(addr, amount, message, extra_query_params=extra_query_params)
       +        return str(uri)
       +
            def get_request_status(self, key):
                r = self.receive_requests.get(key)
                if r is None:
                    return PR_UNKNOWN
                address = r['address']
       -        amount = r.get('amount')
       +        amount = r.get('amount', 0)
                timestamp = r.get('time', 0)
                if timestamp and type(timestamp) != int:
                    timestamp = 0
                expiration = r.get('exp')
                if expiration and type(expiration) != int:
                    expiration = 0
       -        conf = None
       -        if amount:
       -            if self.is_up_to_date():
       -                paid, conf = self.get_payment_status(address, amount)
       -                status = PR_PAID if paid else PR_UNPAID
       -                if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration:
       -                    status = PR_EXPIRED
       -            else:
       -                status = PR_UNKNOWN
       -        else:
       -            status = PR_UNKNOWN
       +
       +        paid, conf = self.get_payment_status(address, amount)
       +        status = PR_PAID if paid else PR_UNPAID
       +        if status == PR_UNPAID and expiration is not None and time.time() > timestamp + expiration:
       +            status = PR_EXPIRED
                return status, conf
        
       +    def receive_tx_callback(self, tx_hash, tx, tx_height):
       +        super().receive_tx_callback(tx_hash, tx, tx_height)
       +        for txo in tx.outputs():
       +            addr = self.get_txout_address(txo)
       +            if addr in self.receive_requests:
       +                status, conf = self.get_request_status(addr)
       +                self.network.trigger_callback('payment_received', self, addr, status)
       +
            def make_payment_request(self, addr, amount, message, expiration):
                timestamp = int(time.time())
                _id = bh2u(sha256d(addr + "%d"%timestamp))[0:10]
       t@@ -1306,9 +1326,12 @@ class Abstract_Wallet(AddressSynchronizer):
                return True
        
            def get_sorted_requests(self, config):
       -        keys = map(lambda x: (self.get_address_index(x), x), self.receive_requests.keys())
       -        sorted_keys = sorted(filter(lambda x: x[0] is not None, keys))
       -        return [self.get_payment_request(x[1], config) for x in sorted_keys]
       +        """ sorted by timestamp """
       +        out = [self.get_payment_request(x, config) for x in self.receive_requests.keys()]
       +        if self.lnworker:
       +            out += self.lnworker.get_invoices()
       +        out.sort(key=operator.itemgetter('time'))
       +        return out
        
            def get_fingerprint(self):
                raise NotImplementedError()