tkivy: use plugins - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit a5e94ef0e8d6a92096711218179489e24ad53f0d DIR parent c803a8ecab1c85b88fd741d5ec420e1ee3370ad5 HTML Author: ThomasV <thomasv@electrum.org> Date: Tue, 13 Oct 2015 12:12:49 +0200 kivy: use plugins Diffstat: M electrum | 8 +++----- M gui/kivy/__init__.py | 4 +++- M gui/kivy/main.kv | 10 +++++++--- M gui/kivy/main_window.py | 89 ++++++++++--------------------- D gui/kivy/plugins/__init__.py | 1 - D gui/kivy/plugins/exchange_rate.py | 376 ------------------------------- M gui/kivy/uix/screens.py | 19 +++++++++++++------ M gui/kivy/uix/ui_screens/network.kv | 1 + A gui/kivy/uix/ui_screens/plugins.kv | 16 ++++++++++++++++ M gui/kivy/uix/ui_screens/settings.kv | 33 +++++++++++++++---------------- M plugins/__init__.py | 6 +++--- M plugins/exchange_rate.py | 1 + 12 files changed, 92 insertions(+), 472 deletions(-) --- DIR diff --git a/electrum b/electrum t@@ -444,7 +444,7 @@ if __name__ == '__main__': 'verbose': True, 'cmd': 'gui', 'gui': 'kivy' if is_kivy else 'android', - 'auto_connect': True, + #'auto_connect': True, } else: config_options = args.__dict__ t@@ -474,10 +474,8 @@ if __name__ == '__main__': cmd_name = config.get('cmd') # initialize plugins. - plugins = None - if not is_android: - gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline' - plugins = Plugins(config, is_bundle or is_local or is_android, gui_name) + gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline' + plugins = Plugins(config, is_bundle or is_local or is_android, gui_name) # get password if needed if cmd_name not in ['gui', 'daemon']: DIR diff --git a/gui/kivy/__init__.py b/gui/kivy/__init__.py t@@ -61,10 +61,11 @@ from main_window import ElectrumWindow class ElectrumGui: - def __init__(self, config, network, app=None): + def __init__(self, config, network, plugins, app=None): Logger.debug('ElectrumGUI: initialising') self.network = network self.config = config + self.plugins = plugins #:TODO # implement kivy plugin mechanism that needs to be more extensible t@@ -85,5 +86,6 @@ class ElectrumGui: self.main_window = w = ElectrumWindow(config=self.config, network=self.network, + plugins = self.plugins, gui_object=self) w.run() DIR diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv t@@ -451,13 +451,17 @@ BoxLayout: on_press: ao._dropdown.dismiss() on_release: app.popup_dialog('network') ActionButton: - text: _('Wallet') + text: _('Settings') + on_press: ao._dropdown.dismiss() + on_release: app.popup_dialog('settings') + ActionButton: + text: _('Wallets') on_press: ao._dropdown.dismiss() on_release: app.popup_dialog('wallet') ActionButton: - text: _('Preferences') + text: _('Plugins') on_press: ao._dropdown.dismiss() - on_release: app.popup_dialog('settings') + on_release: app.popup_dialog('plugins') ScreenManager: id: manager DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py t@@ -1,12 +1,15 @@ +import re import sys import time import datetime import traceback +from decimal import Decimal from electrum import WalletStorage, Wallet from electrum.i18n import _, set_language from electrum.contacts import Contacts from electrum.util import profiler +from electrum.plugins import run_hook from kivy.app import App from kivy.core.window import Window t@@ -18,6 +21,7 @@ from kivy.cache import Cache from kivy.clock import Clock from kivy.factory import Factory from kivy.metrics import inch, metrics +from kivy.lang import Builder # lazy imports for factory so that widgets can be used in kv Factory.register('InstallWizard', t@@ -27,11 +31,9 @@ Factory.register('ELTextInput', module='electrum_gui.kivy.uix.screens') # delayed imports: for startup speed on android -notification = app = ref = format_satoshis = Builder = None +notification = app = ref = format_satoshis = None util = False -from decimal import Decimal -import re # register widget cache for keeping memory down timeout to forever to cache # the data t@@ -39,7 +41,8 @@ Cache.register('electrum_widgets', timeout=0) from kivy.uix.screenmanager import Screen from kivy.uix.tabbedpanel import TabbedPanel - +from kivy.uix.label import Label +from kivy.uix.checkbox import CheckBox Factory.register('TabbedCarousel', module='electrum_gui.kivy.uix.screens') t@@ -176,7 +179,6 @@ class ElectrumWindow(App): def __init__(self, **kwargs): # initialize variables self._clipboard = None - self.exchanger = None self.info_bubble = None self.qrscanner = None self.nfcscanner = None t@@ -185,8 +187,10 @@ class ElectrumWindow(App): super(ElectrumWindow, self).__init__(**kwargs) title = _('Electrum App') - self.network = network = kwargs.get('network', None) self.electrum_config = config = kwargs.get('config', None) + self.network = network = kwargs.get('network', None) + self.plugins = kwargs.get('plugins', []) + self.gui_object = kwargs.get('gui_object', None) #self.config = self.gui_object.config t@@ -206,6 +210,8 @@ class ElectrumWindow(App): self._trigger_notify_transactions = \ Clock.create_trigger(self.notify_transactions, 5) + + def set_url(self, instance, url): self.gui_object.set_url(url) t@@ -226,12 +232,23 @@ class ElectrumWindow(App): activity.bind(on_activity_result=on_qr_result) PythonActivity.mActivity.startActivityForResult(intent, 0) - def build(self): - global Builder - if not Builder: - from kivy.lang import Builder - + def show_plugins(self, plugins_list): + def on_checkbox_active(cb, value): + self.plugins.toggle_enabled(self.electrum_config, cb.name) + for item in self.plugins.descriptions: + if 'kivy' not in item.get('available_for', []): + continue + name = item.get('name') + label = Label(text=item.get('fullname')) + plugins_list.add_widget(label) + cb = CheckBox() + cb.name = name + p = self.plugins.get(name) + cb.active = (p is not None) and p.is_enabled() + cb.bind(active=on_checkbox_active) + plugins_list.add_widget(cb) + def build(self): return Builder.load_file('gui/kivy/main.kv') def _pause(self): t@@ -403,52 +420,11 @@ class ElectrumWindow(App): self.wallet = None - def create_quote_text(self, btc_balance, mode='normal'): - ''' - ''' - if not self.exchanger: - return - quote_currency = self.exchanger.currency - quote_balance = self.exchanger.exchange(btc_balance, quote_currency) - - if quote_currency and mode == 'symbol': - quote_currency = self.exchanger.symbols.get(quote_currency, - quote_currency) - - if quote_balance is None: - quote_text = u"..." - else: - quote_text = u"%s%.2f" % (quote_currency, - quote_balance) - return quote_text def set_currencies(self, quote_currencies): self.currencies = sorted(quote_currencies.keys()) self._trigger_update_status() - def get_history_rate(self, item, btc_balance, mintime): - '''Historical rates: currently only using coindesk by default. - ''' - maxtime = datetime.datetime.now().strftime('%Y-%m-%d') - rate = self.exchanger.get_history_rate(item, btc_balance, mintime, - maxtime) - - return self.set_history_rate(item, rate) - - - def set_history_rate(self, item, rate): - ''' - ''' - #TODO: fix me allow other currencies to be used for history rates - quote_currency = self.exchanger.symbols.get('USD', 'USD') - if rate is None: - quote_text = "..." - else: - quote_text = "{0}{1:.3}".format(quote_currency, rate) - item = item() - if item: - item.quote_text = quote_text - return quote_text @profiler t@@ -485,7 +461,7 @@ class ElectrumWindow(App): unconfirmed = " [%s unconfirmed]" %( self.format_amount(u, True).strip()) if x: unmatured = " [%s unmatured]"%(self.format_amount(x, True).strip()) - quote_text = self.create_quote_text(Decimal(c+u+x)/100000000, mode='symbol') or '' + #quote_text = self.create_quote_text(Decimal(c+u+x)/100000000, mode='symbol') or '' self.status = text.strip() + ' ' + self.base_unit else: self.status = _("Not connected") t@@ -510,13 +486,6 @@ class ElectrumWindow(App): @profiler def update_wallet(self, *dt): - ''' - ''' - if not self.exchanger: - from electrum_gui.kivy.plugins.exchange_rate import Exchanger - self.exchanger = Exchanger(self) - self.exchanger.start() - return self._trigger_update_status() if self.wallet.up_to_date or not self.network or not self.network.is_connected(): self.update_history_tab() DIR diff --git a/gui/kivy/plugins/__init__.py b/gui/kivy/plugins/__init__.py t@@ -1 +0,0 @@ - DIR diff --git a/gui/kivy/plugins/exchange_rate.py b/gui/kivy/plugins/exchange_rate.py t@@ -1,376 +0,0 @@ -# -*- encoding: utf8 -*- - -'''Module exchange_rate: - -This module is responsible for getting the conversion rates from different -bitcoin exchanges. -''' - -import decimal -import json - -from kivy.network.urlrequest import UrlRequest -from kivy.event import EventDispatcher -from kivy.properties import (OptionProperty, StringProperty, AliasProperty, - ListProperty) -from kivy.clock import Clock -from kivy.cache import Cache - -# Register local cache -Cache.register('history_rate', timeout=220) - -EXCHANGES = ["BitcoinAverage", - "BitcoinVenezuela", - "BitPay", - "Blockchain", - "BTCChina", - "CaVirtEx", - "Coinbase", - "CoinDesk", - "LocalBitcoins", - "Winkdex"] - -HISTORY_EXCHNAGES = ['Coindesk', - 'Winkdex', - 'BitcoinVenezuela'] - - -class Exchanger(EventDispatcher): - ''' Provide exchanges rate between crypto and different national - currencies. See Module Documentation for details. - ''' - - symbols = {'ALL': u'Lek', 'AED': u'د.إ', 'AFN':u'؋', 'ARS': u'$', - 'AMD': u'֏', 'AWG': u'ƒ', 'ANG': u'ƒ', 'AOA': u'Kz', 'BDT': u'৳', - 'BHD': u'BD', 'BIF': u'FBu', 'BTC': u'BTC', 'BTN': u'Nu', 'CDF': u'FC', - 'CHF': u'CHF', 'CLF': u'UF', 'CLP':u'$', 'CVE': u'$', 'DJF':u'Fdj', - 'DZD': u'دج', 'AUD': u'$', 'AZN': u'ман', 'BSD': u'$', 'BBD': u'$', - 'BYR': u'p', 'CRC': u'₡', 'BZD': u'BZ$', 'BMD': u'$', 'BOB': u'$b', - 'BAM': u'KM', 'BWP': u'P', 'BGN': 'uлв', 'BRL': u'R$', 'BND': u'$', - 'KHR': u'៛', 'CAD': u'$', 'ERN': u'Nfk', 'ETB': u'Br', 'KYD': u'$', - 'USD': u'$', 'CLP': u'$', 'HRK': u'kn', 'CUP': u'₱', 'CZK': u'Kč', - 'DKK': u'kr', 'DOP': u'RD$', 'XCD': u'$', 'EGP': u'£', 'SVC': u'$' , - 'EEK': u'kr', 'EUR': u'€', u'FKP': u'£', 'FJD': u'$', 'GHC': u'¢', - 'GIP': u'£', 'GTQ': u'Q', 'GBP': u'£', 'GYD': u'$', 'HNL': u'L', - 'HKD': u'$', 'HUF': u'Ft', 'ISK': u'kr', 'INR': u'₹', 'IDR': u'Rp', - 'IRR': u'﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$', 'JMD': u'J$', - 'JPY': u'¥', 'JEP': u'£', 'KZT': u'лв', 'KPW': u'₩', 'KRW': u'₩', - 'KGS': u'лв', 'LAK': u'₭', 'LVL': u'Ls', 'CNY': u'¥'} - - _use_exchange = OptionProperty('Blockchain', options=EXCHANGES) - '''This is the exchange to be used for getting the currency exchange rates - ''' - - _currency = StringProperty('EUR') - '''internal use only - ''' - - def _set_currency(self, value): - value = str(value) - if self.use_exchange == 'CoinDesk': - self._update_cd_currency(self.currency) - return - self._currency = value - self.parent.electrum_config.set_key('currency', value, True) - - def _get_currency(self): - self._currency = self.parent.electrum_config.get('currency', 'EUR') - return self._currency - - currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',)) - - currencies = ListProperty(['EUR', 'GBP', 'USD']) - '''List of currencies supported by the current exchanger plugin. - - :attr:`currencies` is a `ListProperty` default to ['Eur', 'GBP'. 'USD']. - ''' - - def _get_useex(self): - if not self.parent: - return self._use_exchange - - self._use_exchange = self.parent.electrum_config.get('use_exchange', - 'Blockchain') - return self._use_exchange - - def _set_useex(self, value): - if not self.parent: - return self._use_exchange - self.parent.electrum_config.set_key('use_exchange', value, True) - self._use_exchange = value - - use_exchange = AliasProperty(_get_useex, _set_useex, - bind=('_use_exchange', )) - - def __init__(self, parent): - super(Exchanger, self).__init__() - self.parent = parent - self.quote_currencies = None - self.exchanges = EXCHANGES - self.history_exchanges = HISTORY_EXCHNAGES - - def exchange(self, btc_amount, quote_currency): - if self.quote_currencies is None: - return None - - quote_currencies = self.quote_currencies.copy() - if quote_currency not in quote_currencies: - return None - - return btc_amount * decimal.Decimal(quote_currencies[quote_currency]) - - def get_history_rate(self, item, btc_amt, mintime, maxtime): - def on_success(request, response): - response = json.loads(response) - - try: - hrate = response['bpi'][mintime] - hrate = abs(btc_amt) * decimal.Decimal(hrate) - Cache.append('history_rate', uid, hrate) - except KeyError: - hrate = 'not found' - - self.parent.set_history_rate(item, hrate) - - # Check local cache before getting data from remote - exchange = 'coindesk' - uid = '{}:{}'.format(exchange, mintime) - hrate = Cache.get('history_rate', uid) - - if hrate: - return hrate - - req = UrlRequest(url='https://api.coindesk.com/v1/bpi/historical' - '/close.json?start={}&end={}' - .format(mintime, maxtime) - ,on_success=on_success, timeout=15) - return None - - def update_rate(self, dt): - ''' This is called from :method:`start` every X seconds; to update the - rates for currencies for the currently selected exchange. - ''' - if not self.parent.network or not self.parent.network.is_connected(): - return - - # temporarily disabled - return - - update_rates = { - "BitcoinAverage": self.update_ba, - "BitcoinVenezuela": self.update_bv, - "BitPay": self.update_bp, - "Blockchain": self.update_bc, - "BTCChina": self.update_CNY, - "CaVirtEx": self.update_cv, - "CoinDesk": self.update_cd, - "Coinbase": self.update_cb, - "LocalBitcoins": self.update_lb, - "Winkdex": self.update_wd, - } - try: - update_rates[self.use_exchange]() - except KeyError: - return - - def update_wd(self): - - def on_success(request, response): - response = json.loads(response) - quote_currencies = {'USD': 0.0} - lenprices = len(response["prices"]) - usdprice = response['prices'][lenprices-1]['y'] - - try: - quote_currencies["USD"] = decimal.Decimal(usdprice) - except KeyError: - pass - - self.quote_currencies = quote_currencies - self.parent.set_currencies(quote_currencies) - - req = UrlRequest( - url='https://winkdex.com/static/data/0_600_288.json', - on_success=on_success, - timeout=5) - - def update_cd_currency(self, currency): - - def on_success(request, response): - response = json.loads(response) - quote_currencies = self.quote_currencies - quote_currencies[currency] =\ - str(response['bpi'][str(currency)]['rate_float']) - self.parent.set_currencies(quote_currencies) - - req = UrlRequest( - url='https://api.coindesk.com/v1/bpi/currentprice/'\ - + str(currency) + '.json',on_success=on_success, timeout=5) - - def update_cd(self): - - def on_success(request, response): - quote_currencies = {} - response = json.loads(response) - - for cur in response: - quote_currencies[str(cur["currency"])] = 0.0 - - self.quote_currencies = quote_currencies - self.update_cd_currency(self.currency) - - req = UrlRequest( - url='https://api.coindesk.com/v1/bpi/supported-currencies.json', - on_success=on_success, - timeout=5) - - def update_cv(self): - def on_success(request, response): - response = json.loads(response) - quote_currencies = {"CAD": 0.0} - cadprice = response["last"] - try: - quote_currencies["CAD"] = decimal.Decimal(cadprice) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://www.cavirtex.com/api/CAD/ticker.json', - on_success=on_success, - timeout=5) - - def update_CNY(self): - - def on_success(request, response): - quote_currencies = {"CNY": 0.0} - cnyprice = response["ticker"]["last"] - try: - quote_currencies["CNY"] = decimal.Decimal(cnyprice) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://data.btcchina.com/data/ticker', - on_success=on_success, - timeout=5) - - def update_bp(self): - - def on_success(request, response): - quote_currencies = {} - try: - for r in response: - quote_currencies[str(r['code'])] = decimal.Decimal(r['rate']) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://bitpay.com/api/rates', - on_success=on_success, - timeout=5) - - def update_cb(self): - - def _lookup_rate(response, quote_id): - return decimal.Decimal(str(response[str(quote_id)])) - - def on_success(request, response): - quote_currencies = {} - try: - for r in response: - if r[:7] == "btc_to_": - quote_currencies[r[7:].upper()] =\ - _lookup_rate(response, r) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest( - url='https://coinbase.com/api/v1/currencies/exchange_rates', - on_success=on_success, - timeout=5) - - def update_bc(self): - - def _lookup_rate(response, quote_id): - return decimal.Decimal(str(response[str(quote_id)]["15m"])) - - def on_success(request, response): - quote_currencies = {} - try: - for r in response: - quote_currencies[r] = _lookup_rate(response, r) - self.quote_currencies = quote_currencies - except KeyError, TypeError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://blockchain.info/ticker', - on_success=on_success, - timeout=5) - - def update_lb(self): - def _lookup_rate(response, quote_id): - return decimal.Decimal(response[str(quote_id)]["rates"]["last"]) - - def on_success(request, response): - quote_currencies = {} - try: - for r in response: - quote_currencies[r] = _lookup_rate(response, r) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest( - url='https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/', - on_success=on_success, - timeout=5) - - - def update_ba(self): - - def on_success(request, response): - quote_currencies = {} - try: - for r in response: - quote_currencies[r] = decimal.Decimal(response[r][u'last']) - self.quote_currencies = quote_currencies - except TypeError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://api.bitcoinaverage.com/ticker/global/all', - on_success=on_success, - timeout=5) - - def update_bv(self): - - def on_success(request, response): - quote_currencies = {} - try: - for r in response["BTC"]: - quote_currencies[r] = decimal.Decimal(response['BTC'][r]) - self.quote_currencies = quote_currencies - except KeyError: - pass - self.parent.set_currencies(quote_currencies) - - req = UrlRequest(url='https://api.bitcoinvenezuela.com/', - on_success=on_success, - timeout=5) - - def start(self): - self.update_rate(0) - # check every 20 seconds - Clock.unschedule(self.update_rate) - Clock.schedule_interval(self.update_rate, 20) - - def stop(self): - Clock.unschedule(self.update_rate) - DIR diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py t@@ -15,6 +15,8 @@ from kivy.factory import Factory from electrum.i18n import _ from electrum.util import profiler from electrum import bitcoin +from electrum.util import timestamp_to_datetime +from electrum.plugins import run_hook class CScreen(Factory.Screen): t@@ -84,6 +86,10 @@ class HistoryScreen(CScreen): ra_dialog.item = item ra_dialog.open() + def get_history_rate(self, btc_balance, timestamp): + date = timestamp_to_datetime(timestamp) + return run_hook('historical_value_str', btc_balance, date) + def parse_history(self, items): for item in items: tx_hash, conf, value, timestamp, balance = item t@@ -121,7 +127,11 @@ class HistoryScreen(CScreen): label = _('Pruned transaction outputs') is_default_label = False - yield (conf, icon, time_str, label, v_str, balance_str, tx_hash) + quote_currency = 'USD' + rate = self.get_history_rate(value, timestamp) + quote_text = "..." if rate is None else "{0:.3} {1}".format(rate, quote_currency) + + yield (conf, icon, time_str, label, v_str, balance_str, tx_hash, quote_text) def update(self, see_all=False): t@@ -134,20 +144,17 @@ class HistoryScreen(CScreen): history_add = history_card.ids.content.add_widget history_add(last_widget) RecentActivityItem = Factory.RecentActivityItem - get_history_rate = self.app.get_history_rate count = 0 for item in history: count += 1 - conf, icon, date_time, address, amount, balance, tx = item + conf, icon, date_time, address, amount, balance, tx, quote_text = item ri = RecentActivityItem() ri.icon = icon ri.date = date_time mintimestr = date_time.split()[0] ri.address = address ri.amount = amount - ri.quote_text = get_history_rate(ref(ri), - Decimal(amount), - mintimestr) + ri.quote_text = quote_text ri.balance = balance ri.confirmations = conf ri.tx_hash = tx DIR diff --git a/gui/kivy/uix/ui_screens/network.kv b/gui/kivy/uix/ui_screens/network.kv t@@ -9,6 +9,7 @@ Popup: app.network.set_parameters(host.text, nd.port, nd.protocol, nd.proxy, auto_connect.active) BoxLayout: + orientation: 'vertical' GridLayout: DIR diff --git a/gui/kivy/uix/ui_screens/plugins.kv b/gui/kivy/uix/ui_screens/plugins.kv t@@ -0,0 +1,16 @@ +Popup: + title: _('Plugins') + id: popup + BoxLayout: + orientation: 'vertical' + GridLayout: + size_hint_y: None + cols: 2 + id: plugins_list + on_parent: + app.show_plugins(plugins_list) + Button: + size_hint_y: None + height: '48dp' + text: _('Close') + on_release: popup.dismiss() DIR diff --git a/gui/kivy/uix/ui_screens/settings.kv b/gui/kivy/uix/ui_screens/settings.kv t@@ -1,27 +1,26 @@ Popup: id: settings title: _('Settings') + BoxLayout: - Button: - size_hint_y: None - height: '48dp' - text: 'Button normal' - - Button: - size_hint_y: None - height: '48dp' - text: 'Button down' - state: 'down' + orientation: 'vertical' + size_hint_y: None - Button: - size_hint_y: None - height: '48dp' - text: 'Button disabled' - disabled: True + GridLayout: + cols: 2 + Label: + text: _('Base unit') + height: '48dp' + Spinner: + text: 'BTC' + values: ('BTC', 'mBTC') + height: '48dp' Button: - size_hint_y: None + #size_hint_y: None height: '48dp' - text: 'close' + text: _('Close') on_release: settings.dismiss() + #Widget: + # size_hint_y: None DIR diff --git a/plugins/__init__.py b/plugins/__init__.py t@@ -57,7 +57,7 @@ descriptions = [ 'name': 'exchange_rate', 'fullname': _("Exchange rates"), 'description': _("Exchange rates and currency conversion tools."), - 'available_for': ['qt'], + 'available_for': ['qt','kivy'], }, { 'name': 'greenaddress_instant', t@@ -78,10 +78,10 @@ descriptions = [ 'name': 'labels', 'fullname': _('LabelSync'), 'description': '\n'.join([ - _("The new and improved LabelSync plugin. This can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), + _("Synchronize your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), _("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server") ]), - 'available_for': ['qt'] + 'available_for': ['qt','kivy'] }, { 'name': 'plot', DIR diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py t@@ -423,6 +423,7 @@ class Plugin(BasePlugin, ThreadJob): return "%s" % (self.ccy_amount_str(value, True)) return _("No data") + @hook def historical_value_str(self, satoshis, d_t): rate = self.exchange.historical_rate(self.ccy, d_t) # Frequently there is no rate for today, until tomorrow :)