electrum-bitcoinprivate/gui/kivy/uix/screens.py

469 lines
16 KiB
Python
Raw Normal View History

2015-10-07 04:06:28 -07:00
from weakref import ref
from decimal import Decimal
import re
import datetime
2015-10-13 10:09:12 -07:00
import traceback, sys
2015-10-07 04:06:28 -07:00
from kivy.app import App
from kivy.cache import Cache
from kivy.clock import Clock
from kivy.compat import string_types
from kivy.properties import (ObjectProperty, DictProperty, NumericProperty,
2015-12-04 02:47:46 -08:00
ListProperty, StringProperty)
2016-01-16 07:47:48 -08:00
from kivy.uix.label import Label
2015-12-04 02:47:46 -08:00
from kivy.lang import Builder
from kivy.factory import Factory
2016-02-06 07:58:31 -08:00
from kivy.utils import platform
2019-02-28 13:26:15 -08:00
from electrum_zclassic.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
from electrum_zclassic import bitcoin
from electrum_zclassic.util import timestamp_to_datetime
from electrum_zclassic.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from .context_menu import ContextMenu
2015-12-12 07:54:32 -08:00
2016-02-04 01:57:09 -08:00
2019-02-28 13:26:15 -08:00
from electrum_zclassic_gui.kivy.i18n import _
2016-01-19 03:57:18 -08:00
class CScreen(Factory.Screen):
__events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
action_view = ObjectProperty(None)
loaded = False
kvname = None
2015-12-12 07:54:32 -08:00
context_menu = None
menu_actions = []
app = App.get_running_app()
def _change_action_view(self):
app = App.get_running_app()
action_bar = app.root.manager.current_screen.ids.action_bar
_action_view = self.action_view
if (not _action_view) or _action_view.parent:
return
action_bar.clear_widgets()
action_bar.add_widget(_action_view)
def on_enter(self):
# FIXME: use a proper event don't use animation time of screen
Clock.schedule_once(lambda dt: self.dispatch('on_activate'), .25)
pass
def update(self):
pass
2015-10-06 05:30:44 -07:00
@profiler
def load_screen(self):
self.screen = Builder.load_file('gui/kivy/uix/ui_screens/' + self.kvname + '.kv')
self.add_widget(self.screen)
self.loaded = True
self.update()
setattr(self.app, self.kvname + '_screen', self)
def on_activate(self):
if self.kvname and not self.loaded:
2015-10-06 05:30:44 -07:00
self.load_screen()
#Clock.schedule_once(lambda dt: self._change_action_view())
def on_leave(self):
self.dispatch('on_deactivate')
def on_deactivate(self):
2015-12-12 07:54:32 -08:00
self.hide_menu()
def hide_menu(self):
if self.context_menu is not None:
self.remove_widget(self.context_menu)
2015-12-12 07:54:32 -08:00
self.context_menu = None
def show_menu(self, obj):
self.hide_menu()
self.context_menu = ContextMenu(obj, self.menu_actions)
self.add_widget(self.context_menu)
2015-12-12 07:54:32 -08:00
# note: this list needs to be kept in sync with another in qt
TX_ICONS = [
"unconfirmed",
"close",
"unconfirmed",
"close",
"clock1",
"clock2",
"clock3",
"clock4",
"clock5",
"confirmed",
]
class HistoryScreen(CScreen):
tab = ObjectProperty(None)
kvname = 'history'
2016-06-01 10:21:37 -07:00
cards = {}
def __init__(self, **kwargs):
self.ra_dialog = None
super(HistoryScreen, self).__init__(**kwargs)
2016-02-12 07:09:16 -08:00
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.transactions.get(tx_hash)
if not tx:
return
self.app.tx_dialog(tx)
2015-12-14 03:08:11 -08:00
def label_dialog(self, obj):
2017-10-21 02:03:47 -07:00
from .dialogs.label_dialog import LabelDialog
2015-12-14 03:08:11 -08:00
key = obj.tx_hash
2015-12-15 03:52:30 -08:00
text = self.app.wallet.get_label(key)
2015-12-14 03:08:11 -08:00
def callback(text):
self.app.wallet.set_label(key, text)
self.update()
d = LabelDialog(_('Enter Transaction Label'), text, callback)
d.open()
2016-06-01 10:21:37 -07:00
def get_card(self, tx_hash, height, conf, timestamp, value, balance):
status, status_str = self.app.wallet.get_tx_status(tx_hash, height, conf, timestamp)
icon = "atlas://gui/kivy/theming/light/" + TX_ICONS[status]
label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
ri = self.cards.get(tx_hash)
if ri is None:
ri = Factory.HistoryItem()
ri.screen = self
ri.tx_hash = tx_hash
self.cards[tx_hash] = ri
ri.icon = icon
ri.date = status_str
ri.message = label
ri.confirmations = conf
if value is not None:
ri.is_mine = value < 0
if value < 0: value = - value
ri.amount = self.app.format_amount_and_units(value)
if self.app.fiat_unit:
fx = self.app.fx
fiat_value = value / Decimal(bitcoin.COIN) * self.app.wallet.price_at_timestamp(tx_hash, fx.timestamp_rate)
fiat_value = Fiat(fiat_value, fx.ccy)
ri.quote_text = str(fiat_value)
2016-06-01 10:21:37 -07:00
return ri
def update(self, see_all=False):
2015-10-13 06:58:34 -07:00
if self.app.wallet is None:
return
2016-08-30 02:19:30 -07:00
history = reversed(self.app.wallet.get_history())
2016-01-16 07:47:48 -08:00
history_card = self.screen.ids.history_container
2015-12-12 07:54:32 -08:00
history_card.clear_widgets()
count = 0
2015-10-07 04:06:28 -07:00
for item in history:
2016-06-01 10:21:37 -07:00
ri = self.get_card(*item)
2016-01-16 07:47:48 -08:00
history_card.add_widget(ri)
2016-01-16 07:47:48 -08:00
class SendScreen(CScreen):
2015-12-02 06:27:23 -08:00
kvname = 'send'
2015-12-11 06:21:21 -08:00
payment_request = None
2015-12-02 06:27:23 -08:00
def set_URI(self, text):
2019-02-28 13:26:15 -08:00
import electrum_zclassic
try:
2019-02-28 13:26:15 -08:00
uri = electrum_zclassic.util.parse_URI(text, self.app.on_pr)
except:
2019-02-28 13:26:15 -08:00
self.app.show_info(_("Not a Zclassic URI"))
return
2016-02-18 06:53:52 -08:00
amount = uri.get('amount')
2015-12-04 02:47:46 -08:00
self.screen.address = uri.get('address', '')
self.screen.message = uri.get('message', '')
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
2016-02-18 06:53:52 -08:00
self.payment_request = None
self.screen.is_pr = False
2015-12-12 14:23:58 -08:00
def update(self):
2016-02-12 13:20:20 -08:00
pass
2015-12-12 14:23:58 -08:00
2015-10-14 05:18:15 -07:00
def do_clear(self):
2015-12-04 02:47:46 -08:00
self.screen.amount = ''
self.screen.message = ''
self.screen.address = ''
2015-12-11 06:21:21 -08:00
self.payment_request = None
self.screen.is_pr = False
2015-12-11 06:21:21 -08:00
def set_request(self, pr):
self.screen.address = pr.get_requestor()
2015-12-13 08:49:51 -08:00
amount = pr.get_amount()
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
2015-12-11 06:21:21 -08:00
self.screen.message = pr.get_memo()
if pr.is_pr():
self.screen.is_pr = True
self.payment_request = pr
2016-02-18 06:45:34 -08:00
else:
self.screen.is_pr = False
self.payment_request = None
2015-10-14 05:18:15 -07:00
2016-02-09 03:48:25 -08:00
def do_save(self):
if not self.screen.address:
return
2016-02-18 06:45:34 -08:00
if self.screen.is_pr:
# it should be already saved
2016-02-09 03:48:25 -08:00
return
# save address as invoice
2019-02-28 13:26:15 -08:00
from electrum_zclassic.paymentrequest import make_unsigned_request, PaymentRequest
2016-02-09 03:48:25 -08:00
req = {'address':self.screen.address, 'memo':self.screen.message}
amount = self.app.get_amount(self.screen.amount) if self.screen.amount else 0
req['amount'] = amount
pr = make_unsigned_request(req).SerializeToString()
pr = PaymentRequest(pr)
self.app.wallet.invoices.add(pr)
2016-02-09 03:48:25 -08:00
self.app.show_info(_("Invoice saved"))
2016-02-19 04:53:01 -08:00
if pr.is_pr():
self.screen.is_pr = True
self.payment_request = pr
else:
self.screen.is_pr = False
self.payment_request = None
2016-02-09 03:48:25 -08:00
2015-12-04 02:47:46 -08:00
def do_paste(self):
contents = self.app._clipboard.paste()
2016-02-09 03:48:25 -08:00
if not contents:
self.app.show_info(_("Clipboard is empty"))
return
self.set_URI(contents)
2015-10-06 05:30:44 -07:00
2015-12-04 02:47:46 -08:00
def do_send(self):
2016-02-18 06:45:34 -08:00
if self.screen.is_pr:
2015-12-11 06:21:21 -08:00
if self.payment_request.has_expired():
self.app.show_error(_('Payment request has expired'))
return
outputs = self.payment_request.get_outputs()
else:
address = str(self.screen.address)
2016-02-26 01:25:37 -08:00
if not address:
2019-02-28 13:26:15 -08:00
self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Zclassic address or a payment request'))
2016-02-26 01:25:37 -08:00
return
2015-12-11 06:21:21 -08:00
if not bitcoin.is_address(address):
2019-02-28 13:26:15 -08:00
self.app.show_error(_('Invalid Zclassic Address') + ':\n' + address)
2015-12-11 06:21:21 -08:00
return
try:
amount = self.app.get_amount(self.screen.amount)
except:
self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
return
2016-01-14 08:15:50 -08:00
outputs = [(bitcoin.TYPE_ADDRESS, address, amount)]
message = self.screen.message
2016-02-13 01:00:20 -08:00
amount = sum(map(lambda x:x[2], outputs))
self._do_send(amount, message, outputs)
2016-07-29 06:47:13 -07:00
def _do_send(self, amount, message, outputs):
2015-10-06 05:30:44 -07:00
# make unsigned transaction
2016-02-13 01:00:20 -08:00
config = self.app.electrum_config
2017-07-15 14:40:43 -07:00
coins = self.app.wallet.get_spendable_coins(None, config)
2015-10-06 05:30:44 -07:00
try:
2016-02-13 01:00:20 -08:00
tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
2016-02-18 02:24:38 -08:00
except NotEnoughFunds:
self.app.show_error(_("Not enough funds"))
return
2015-10-06 05:30:44 -07:00
except Exception as e:
traceback.print_exc(file=sys.stdout)
self.app.show_error(str(e))
2015-10-06 05:30:44 -07:00
return
2016-02-13 01:00:20 -08:00
fee = tx.get_fee()
msg = [
_("Amount to be sent") + ": " + self.app.format_amount_and_units(amount),
_("Mining fee") + ": " + self.app.format_amount_and_units(fee),
]
if fee >= config.get('confirm_fee', 100000):
msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
msg.append(_("Enter your PIN code to proceed"))
2016-02-19 05:25:01 -08:00
self.app.protected('\n'.join(msg), self.send_tx, (tx, message))
2016-02-13 01:00:20 -08:00
2016-02-19 05:25:01 -08:00
def send_tx(self, tx, message, password):
if self.app.wallet.has_password() and password is None:
return
def on_success(tx):
if tx.is_complete():
2016-02-19 04:53:01 -08:00
self.app.broadcast(tx, self.payment_request)
self.app.wallet.set_label(tx.txid(), message)
else:
self.app.tx_dialog(tx)
def on_failure(error):
self.app.show_error(error)
2016-02-13 01:33:49 -08:00
if self.app.wallet.can_sign(tx):
self.app.show_info("Signing...")
self.app.sign_tx(tx, password, on_success, on_failure)
else:
2016-02-12 23:15:06 -08:00
self.app.tx_dialog(tx)
2015-10-06 05:30:44 -07:00
class ReceiveScreen(CScreen):
2015-10-14 02:44:01 -07:00
2015-12-04 02:47:46 -08:00
kvname = 'receive'
2016-02-08 10:01:34 -08:00
2015-10-06 05:30:44 -07:00
def update(self):
2016-02-08 10:01:34 -08:00
if not self.screen.address:
self.get_new_address()
2016-02-14 19:18:58 -08:00
else:
status = self.app.wallet.get_request_status(self.screen.address)
2016-02-15 02:33:48 -08:00
self.screen.status = _('Payment received') if status == PR_PAID else ''
2016-02-08 10:01:34 -08:00
2016-03-05 00:44:28 -08:00
def clear(self):
self.screen.address = ''
self.screen.amount = ''
self.screen.message = ''
2016-02-08 10:01:34 -08:00
def get_new_address(self):
2016-03-10 07:22:19 -08:00
if not self.app.wallet:
return False
self.clear()
addr = self.app.wallet.get_unused_address()
2016-02-08 10:01:34 -08:00
if addr is None:
2017-03-08 09:41:47 -08:00
addr = self.app.wallet.get_receiving_address() or ''
b = False
else:
b = True
2015-12-12 14:23:58 -08:00
self.screen.address = addr
return b
2016-02-08 10:01:34 -08:00
def on_address(self, addr):
2016-02-14 03:24:31 -08:00
req = self.app.wallet.get_payment_request(addr, self.app.electrum_config)
self.screen.status = ''
2015-12-12 14:23:58 -08:00
if req:
self.screen.message = req.get('memo', '')
2015-12-13 08:49:51 -08:00
amount = req.get('amount')
2016-02-14 19:18:58 -08:00
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
status = req.get('status', PR_UNKNOWN)
2016-02-15 02:33:48 -08:00
self.screen.status = _('Payment received') if status == PR_PAID else ''
2016-02-08 10:01:34 -08:00
Clock.schedule_once(lambda dt: self.update_qr())
2015-10-14 02:44:01 -07:00
2015-12-04 02:47:46 -08:00
def get_URI(self):
2019-02-28 13:26:15 -08:00
from electrum_zclassic.util import create_URI
2015-12-04 02:47:46 -08:00
amount = self.screen.amount
if amount:
a, u = self.screen.amount.split()
2015-10-16 02:18:24 -07:00
assert u == self.app.base_unit
amount = Decimal(a) * pow(10, self.app.decimal_point())
2015-12-04 02:47:46 -08:00
return create_URI(self.screen.address, amount, self.screen.message)
@profiler
def update_qr(self):
uri = self.get_URI()
qr = self.screen.ids.qr
2015-10-14 02:44:01 -07:00
qr.set_data(uri)
2016-02-06 07:58:31 -08:00
def do_share(self):
uri = self.get_URI()
2019-02-28 13:26:15 -08:00
self.app.do_share(uri, _("Share Zclassic Request"))
2016-02-06 07:35:21 -08:00
2016-02-06 07:58:31 -08:00
def do_copy(self):
uri = self.get_URI()
self.app._clipboard.copy(uri)
2016-01-29 03:46:28 -08:00
self.app.show_info(_('Request copied to clipboard'))
2015-10-14 09:45:26 -07:00
def save_request(self):
addr = self.screen.address
if not addr:
return False
amount = self.screen.amount
message = self.screen.message
2016-02-08 10:01:34 -08:00
amount = self.app.get_amount(amount) if amount else 0
2015-12-11 06:48:56 -08:00
req = self.app.wallet.make_payment_request(addr, amount, message, None)
try:
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' + str(e))
added_request = False
finally:
self.app.update_tab('requests')
return added_request
def on_amount_or_message(self):
2016-02-08 10:01:34 -08:00
Clock.schedule_once(lambda dt: self.update_qr())
2015-12-11 06:48:56 -08:00
2015-12-13 06:26:08 -08:00
def do_new(self):
addr = self.get_new_address()
if not addr:
2016-02-08 10:01:34 -08:00
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.'))
class TabbedCarousel(Factory.TabbedPanel):
'''Custom TabbedPanel using a carousel used in the Main Screen
'''
carousel = ObjectProperty(None)
def animate_tab_to_center(self, value):
scrlv = self._tab_strip.parent
if not scrlv:
return
idx = self.tab_list.index(value)
2015-12-10 06:26:38 -08:00
n = len(self.tab_list)
if idx in [0, 1]:
scroll_x = 1
elif idx in [n-1, n-2]:
scroll_x = 0
else:
scroll_x = 1. * (n - idx - 1) / (n - 1)
mation = Factory.Animation(scroll_x=scroll_x, d=.25)
mation.cancel_all(scrlv)
mation.start(scrlv)
def on_current_tab(self, instance, value):
self.animate_tab_to_center(value)
def on_index(self, instance, value):
current_slide = instance.current_slide
if not hasattr(current_slide, 'tab'):
return
tab = current_slide.tab
ct = self.current_tab
try:
if ct.text != tab.text:
carousel = self.carousel
carousel.slides[ct.slide].dispatch('on_leave')
self.switch_to(tab)
carousel.slides[tab.slide].dispatch('on_enter')
except AttributeError:
current_slide.dispatch('on_enter')
def switch_to(self, header):
# we have to replace the functionality of the original switch_to
if not header:
return
if not hasattr(header, 'slide'):
header.content = self.carousel
super(TabbedCarousel, self).switch_to(header)
try:
tab = self.tab_list[-1]
except IndexError:
return
self._current_tab = tab
tab.state = 'down'
return
carousel = self.carousel
self.current_tab.state = "normal"
header.state = 'down'
self._current_tab = header
# set the carousel to load the appropriate slide
# saved in the screen attribute of the tab head
slide = carousel.slides[header.slide]
if carousel.current_slide != slide:
carousel.current_slide.dispatch('on_leave')
carousel.load_slide(slide)
slide.dispatch('on_enter')
def add_widget(self, widget, index=0):
if isinstance(widget, Factory.CScreen):
self.carousel.add_widget(widget)
return
super(TabbedCarousel, self).add_widget(widget, index=index)