Kivy GUI improvements:
- create unique instances of channels_dialog and addresses_dialog - display and refresh balances in channels_dialog - improve formatting of tx history - repurpose left button in receive_tab
This commit is contained in:
@@ -333,6 +333,8 @@ class ElectrumWindow(App):
|
||||
# cached dialogs
|
||||
self._settings_dialog = None
|
||||
self._password_dialog = None
|
||||
self._channels_dialog = None
|
||||
self._addresses_dialog = None
|
||||
self.fee_status = self.electrum_config.get_fee_status()
|
||||
self.request_popup = None
|
||||
|
||||
@@ -666,8 +668,9 @@ class ElectrumWindow(App):
|
||||
d.open()
|
||||
|
||||
def lightning_channels_dialog(self):
|
||||
d = LightningChannelsDialog(self)
|
||||
d.open()
|
||||
if self._channels_dialog is None:
|
||||
self._channels_dialog = LightningChannelsDialog(self)
|
||||
self._channels_dialog.open()
|
||||
|
||||
def popup_dialog(self, name):
|
||||
if name == 'settings':
|
||||
@@ -1054,20 +1057,12 @@ class ElectrumWindow(App):
|
||||
popup.update()
|
||||
popup.open()
|
||||
|
||||
def requests_dialog(self, screen):
|
||||
from .uix.dialogs.requests import RequestsDialog
|
||||
if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0:
|
||||
self.show_info(_('No saved requests.'))
|
||||
return
|
||||
popup = RequestsDialog(self, screen, None)
|
||||
popup.update()
|
||||
popup.open()
|
||||
|
||||
def addresses_dialog(self):
|
||||
from .uix.dialogs.addresses import AddressesDialog
|
||||
popup = AddressesDialog(self)
|
||||
popup.update()
|
||||
popup.open()
|
||||
if self._addresses_dialog is None:
|
||||
self._addresses_dialog = AddressesDialog(self)
|
||||
self._addresses_dialog.update()
|
||||
self._addresses_dialog.open()
|
||||
|
||||
def fee_dialog(self, label, dt):
|
||||
from .uix.dialogs.fee_dialog import FeeDialog
|
||||
|
||||
@@ -6,26 +6,52 @@ from kivy.uix.popup import Popup
|
||||
from kivy.clock import Clock
|
||||
from electrum.gui.kivy.uix.context_menu import ContextMenu
|
||||
from electrum.util import bh2u
|
||||
from electrum.lnutil import LOCAL, REMOTE
|
||||
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id
|
||||
from electrum.gui.kivy.i18n import _
|
||||
|
||||
Builder.load_string(r'''
|
||||
<LightningChannelItem@CardItem>
|
||||
details: {}
|
||||
active: False
|
||||
channelId: '<channelId not set>'
|
||||
id: card
|
||||
short_channel_id: '<channelId not set>'
|
||||
status: ''
|
||||
local_balance: ''
|
||||
remote_balance: ''
|
||||
_chan: None
|
||||
Label:
|
||||
color: (.5,.5,.5,1) if not card.active else (1,1,1,1)
|
||||
text: root.channelId
|
||||
Label:
|
||||
text: (card._chan.get_state() if card._chan else 'n/a')
|
||||
|
||||
BoxLayout:
|
||||
spacing: '8dp'
|
||||
height: '32dp'
|
||||
orientation: 'vertical'
|
||||
Widget
|
||||
CardLabel:
|
||||
color: (.5,.5,.5,1) if not root.active else (1,1,1,1)
|
||||
text: root.short_channel_id
|
||||
font_size: '15sp'
|
||||
Widget
|
||||
CardLabel:
|
||||
font_size: '13sp'
|
||||
shorten: True
|
||||
text: root.status
|
||||
Widget
|
||||
BoxLayout:
|
||||
spacing: '8dp'
|
||||
height: '32dp'
|
||||
orientation: 'vertical'
|
||||
Widget
|
||||
CardLabel:
|
||||
text: root.local_balance
|
||||
font_size: '13sp'
|
||||
halign: 'right'
|
||||
Widget
|
||||
CardLabel:
|
||||
text: root.remote_balance
|
||||
font_size: '13sp'
|
||||
halign: 'right'
|
||||
Widget
|
||||
|
||||
<LightningChannelsDialog@Popup>:
|
||||
name: 'lightning_channels'
|
||||
title: _('Lightning channels. Tap for options.')
|
||||
title: _('Lightning channels.')
|
||||
id: popup
|
||||
BoxLayout:
|
||||
id: box
|
||||
@@ -103,12 +129,13 @@ class LightningChannelsDialog(Factory.Popup):
|
||||
self.clocks = []
|
||||
self.app = app
|
||||
self.context_menu = None
|
||||
self.app.wallet.network.register_callback(self.channels_update, ['channels'])
|
||||
self.channels_update('bogus evt')
|
||||
self.app.wallet.network.register_callback(self.on_channels, ['channels'])
|
||||
self.app.wallet.network.register_callback(self.on_channel, ['channel'])
|
||||
self.update()
|
||||
|
||||
def show_channel_details(self, obj):
|
||||
p = Factory.ChannelDetailsPopup()
|
||||
p.title = _('Details for channel ') + self.presentable_chan_id(obj._chan)
|
||||
p.title = _('Details for channel ') + format_short_channel_id(obj.chan.short_channel_id)
|
||||
p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()]
|
||||
p.open()
|
||||
|
||||
@@ -146,10 +173,37 @@ class LightningChannelsDialog(Factory.Popup):
|
||||
self.ids.box.remove_widget(self.context_menu)
|
||||
self.context_menu = None
|
||||
|
||||
def presentable_chan_id(self, i):
|
||||
return bh2u(i.short_channel_id) if i.short_channel_id else bh2u(i.channel_id)[:16]
|
||||
def format_fields(self, chan):
|
||||
labels = {}
|
||||
for subject in (REMOTE, LOCAL):
|
||||
bal_minus_htlcs = chan.balance_minus_outgoing_htlcs(subject)//1000
|
||||
label = self.app.format_amount(bal_minus_htlcs)
|
||||
other = subject.inverted()
|
||||
bal_other = chan.balance(other)//1000
|
||||
bal_minus_htlcs_other = chan.balance_minus_outgoing_htlcs(other)//1000
|
||||
if bal_other != bal_minus_htlcs_other:
|
||||
label += ' (+' + self.app.format_amount(bal_other - bal_minus_htlcs_other) + ')'
|
||||
labels[subject] = label
|
||||
return [
|
||||
labels[LOCAL],
|
||||
labels[REMOTE],
|
||||
]
|
||||
|
||||
def channels_update(self, evt):
|
||||
def on_channel(self, evt, chan):
|
||||
Clock.schedule_once(lambda dt: self.update())
|
||||
|
||||
def on_channels(self, evt):
|
||||
Clock.schedule_once(lambda dt: self.update())
|
||||
|
||||
def update_item(self, item):
|
||||
chan = item._chan
|
||||
item.status = chan.get_state()
|
||||
item.short_channel_id = format_short_channel_id(chan.short_channel_id)
|
||||
l, r = self.format_fields(chan)
|
||||
item.local_balance = _('Local') + ':' + l
|
||||
item.remote_balance = _('Remote') + ': ' + r
|
||||
|
||||
def update(self):
|
||||
channel_cards = self.ids.lightning_channels_container
|
||||
channel_cards.clear_widgets()
|
||||
if not self.app.wallet:
|
||||
@@ -158,10 +212,10 @@ class LightningChannelsDialog(Factory.Popup):
|
||||
for i in lnworker.channels.values():
|
||||
item = Factory.LightningChannelItem()
|
||||
item.screen = self
|
||||
item.channelId = self.presentable_chan_id(i)
|
||||
item.active = i.node_id in lnworker.peers
|
||||
item.details = self.channel_details(i)
|
||||
item._chan = i
|
||||
self.update_item(item)
|
||||
channel_cards.add_widget(item)
|
||||
|
||||
def channel_details(self, chan):
|
||||
|
||||
@@ -34,6 +34,7 @@ from electrum.lnaddr import lndecode
|
||||
from electrum.lnutil import RECEIVED, SENT, PaymentFailure
|
||||
|
||||
from .context_menu import ContextMenu
|
||||
from .dialogs.question import Question
|
||||
from .dialogs.lightning_open_channel import LightningOpenChannelDialog
|
||||
|
||||
from electrum.gui.kivy.i18n import _
|
||||
@@ -132,15 +133,15 @@ class HistoryScreen(CScreen):
|
||||
self.menu_actions = [ ('Label', self.label_dialog), ('Details', self.show_tx)]
|
||||
|
||||
def show_tx(self, obj):
|
||||
tx_hash = obj.tx_hash
|
||||
tx = self.app.wallet.db.get_transaction(tx_hash)
|
||||
key = obj.key
|
||||
tx = self.app.wallet.db.get_transaction(key)
|
||||
if not tx:
|
||||
return
|
||||
self.app.tx_dialog(tx)
|
||||
|
||||
def label_dialog(self, obj):
|
||||
from .dialogs.label_dialog import LabelDialog
|
||||
key = obj.tx_hash
|
||||
key = obj.key
|
||||
text = self.app.wallet.get_label(key)
|
||||
def callback(text):
|
||||
self.app.wallet.set_label(key, text)
|
||||
@@ -151,14 +152,13 @@ class HistoryScreen(CScreen):
|
||||
def get_card(self, tx_item): #tx_hash, tx_mined_status, value, balance):
|
||||
is_lightning = tx_item.get('lightning', False)
|
||||
timestamp = tx_item['timestamp']
|
||||
key = tx_item.get('txid') or tx_item['payment_hash']
|
||||
if is_lightning:
|
||||
status = 0
|
||||
txpos = tx_item['txpos']
|
||||
if timestamp is None:
|
||||
status_str = 'unconfirmed'
|
||||
else:
|
||||
status_str = format_time(int(timestamp))
|
||||
status_str = 'unconfirmed' if timestamp is None else format_time(int(timestamp))
|
||||
icon = "atlas://electrum/gui/kivy/theming/light/lightning"
|
||||
message = tx_item['label']
|
||||
else:
|
||||
tx_hash = tx_item['txid']
|
||||
conf = tx_item['confirmations']
|
||||
@@ -169,18 +169,19 @@ class HistoryScreen(CScreen):
|
||||
timestamp=tx_item['timestamp'])
|
||||
status, status_str = self.app.wallet.get_tx_status(tx_hash, tx_mined_info)
|
||||
icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
|
||||
message = tx_item['label'] or tx_hash
|
||||
ri = {}
|
||||
ri['screen'] = self
|
||||
ri['key'] = key
|
||||
ri['icon'] = icon
|
||||
ri['date'] = status_str
|
||||
ri['message'] = tx_item['label']
|
||||
ri['message'] = message
|
||||
value = tx_item['value'].value
|
||||
if value is not None:
|
||||
ri['is_mine'] = value < 0
|
||||
if value < 0: value = - value
|
||||
ri['amount'] = self.app.format_amount_and_units(value)
|
||||
ri['amount'] = self.app.format_amount(value, is_diff = True)
|
||||
if 'fiat_value' in tx_item:
|
||||
ri['quote_text'] = tx_item['fiat_value'].to_ui_string()
|
||||
ri['quote_text'] = str(tx_item['fiat_value'])
|
||||
return ri
|
||||
|
||||
def update(self, see_all=False):
|
||||
@@ -344,7 +345,6 @@ class SendScreen(CScreen):
|
||||
message = self.screen.message
|
||||
amount = sum(map(lambda x:x[2], outputs))
|
||||
if self.app.electrum_config.get('use_rbf'):
|
||||
from .dialogs.question import Question
|
||||
d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))
|
||||
d.open()
|
||||
else:
|
||||
@@ -406,7 +406,7 @@ class ReceiveScreen(CScreen):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(ReceiveScreen, self).__init__(**kwargs)
|
||||
self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
|
||||
self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.delete_request_dialog)]
|
||||
Clock.schedule_interval(lambda dt: self.update(), 5)
|
||||
|
||||
def expiry(self):
|
||||
@@ -509,17 +509,27 @@ class ReceiveScreen(CScreen):
|
||||
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
|
||||
d.open()
|
||||
|
||||
def do_delete(self, req):
|
||||
from .dialogs.question import Question
|
||||
def clear_requests_dialog(self):
|
||||
expired = [req for req in self.app.wallet.get_sorted_requests(self.app.electrum_config) if req['status'] == PR_EXPIRED]
|
||||
if len(expired) == 0:
|
||||
return
|
||||
def callback(c):
|
||||
if c:
|
||||
for req in expired:
|
||||
is_lightning = req.get('lightning', False)
|
||||
key = req['rhash'] if is_lightning else req['address']
|
||||
self.app.wallet.delete_request(key)
|
||||
self.update()
|
||||
d = Question(_('Delete expired requests?'), callback)
|
||||
d.open()
|
||||
|
||||
def delete_request_dialog(self, req):
|
||||
def cb(result):
|
||||
if result:
|
||||
if req.is_lightning:
|
||||
self.app.wallet.lnworker.delete_invoice(req.key)
|
||||
else:
|
||||
self.app.wallet.remove_payment_request(req.key, self.app.electrum_config)
|
||||
self.app.wallet.delete_request(req.key)
|
||||
self.hide_menu()
|
||||
self.update()
|
||||
d = Question(_('Delete request'), cb)
|
||||
d = Question(_('Delete request?'), cb)
|
||||
d.open()
|
||||
|
||||
def show_menu(self, obj):
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
|
||||
|
||||
<CardLabel@Label>
|
||||
color: 0.95, 0.95, 0.95, 1
|
||||
size_hint: 1, None
|
||||
text: ''
|
||||
color: .7, .7, .7, 1
|
||||
text_size: self.width, None
|
||||
height: self.texture_size[1]
|
||||
#height: self.texture_size[1]
|
||||
halign: 'left'
|
||||
valign: 'top'
|
||||
|
||||
@@ -21,11 +19,12 @@
|
||||
message: ''
|
||||
is_mine: True
|
||||
amount: '--'
|
||||
action: _('Sent') if self.is_mine else _('Received')
|
||||
amount_color: '#FF6657' if self.is_mine else '#2EA442'
|
||||
confirmations: 0
|
||||
date: ''
|
||||
quote_text: ''
|
||||
amount_str: self.quote_text if app.is_fiat else self.amount
|
||||
unit_str: app.fx.ccy if app.is_fiat else app.base_unit
|
||||
Image:
|
||||
id: icon
|
||||
source: root.icon
|
||||
@@ -34,18 +33,36 @@
|
||||
width: self.height*1.5
|
||||
mipmap: True
|
||||
BoxLayout:
|
||||
spacing: '8dp'
|
||||
height: '32dp'
|
||||
orientation: 'vertical'
|
||||
Widget
|
||||
CardLabel:
|
||||
text:
|
||||
u'[color={color}]{s}[/color]'.format(s='<<' if root.is_mine else '>>', color=root.amount_color)\
|
||||
+ ' ' + root.action + ' ' + (root.quote_text if app.is_fiat else root.amount)
|
||||
font_size: '15sp'
|
||||
CardLabel:
|
||||
color: .699, .699, .699, 1
|
||||
font_size: '14sp'
|
||||
color: 0.95, 0.95, 0.95, 1
|
||||
text: root.message
|
||||
shorten: True
|
||||
text: root.date + ' ' + root.message
|
||||
shorten_from: 'right'
|
||||
font_size: '15sp'
|
||||
Widget
|
||||
CardLabel:
|
||||
font_size: '12sp'
|
||||
shorten: True
|
||||
text: root.date
|
||||
Widget
|
||||
BoxLayout:
|
||||
spacing: '8dp'
|
||||
height: '32dp'
|
||||
orientation: 'vertical'
|
||||
Widget
|
||||
CardLabel:
|
||||
text: u'[color={color}]{s}[/color]'.format(s=root.amount_str, color=root.amount_color) + ' ' + '[size=12sp]' + root.unit_str + '[/size]'
|
||||
halign: 'right'
|
||||
font_size: '15sp'
|
||||
Widget
|
||||
CardLabel:
|
||||
text: ''
|
||||
halign: 'right'
|
||||
font_size: '12sp'
|
||||
Widget
|
||||
|
||||
<HistoryRecycleView>:
|
||||
|
||||
@@ -135,7 +135,7 @@ ReceiveScreen:
|
||||
icon: 'atlas://electrum/gui/kivy/theming/light/list'
|
||||
size_hint: 0.5, None
|
||||
height: '48dp'
|
||||
on_release: Clock.schedule_once(lambda dt: app.addresses_dialog())
|
||||
on_release: Clock.schedule_once(lambda dt: s.clear_requests_dialog())
|
||||
IconButton:
|
||||
icon: 'atlas://electrum/gui/kivy/theming/light/clock1'
|
||||
size_hint: 0.5, None
|
||||
|
||||
@@ -1356,6 +1356,13 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||
f.write(json.dumps(req))
|
||||
return req
|
||||
|
||||
def delete_request(self, key):
|
||||
""" lightning or on-chain """
|
||||
if key in self.receive_requests:
|
||||
self.remove_payment_request(key, {})
|
||||
elif self.lnworker:
|
||||
self.lnworker.delete_invoice(key)
|
||||
|
||||
def remove_payment_request(self, addr, config):
|
||||
if addr not in self.receive_requests:
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user