From 9938316400ad263ab9bec40e93f7e0b10c2ed9cd Mon Sep 17 00:00:00 2001 From: qua-non Date: Wed, 19 Mar 2014 10:01:15 +0530 Subject: [PATCH] merge dashboard back into ui --- gui/kivy/Makefile | 4 +- gui/kivy/carousel.py | 12 +- gui/kivy/drawer.py | 2 +- gui/kivy/main.kv | 26 +- gui/kivy/main_window.py | 30 +- gui/kivy/screens.py | 998 +----------------------------- gui/kivy/ui_screens/mainscreen.kv | 286 +++++++++ 7 files changed, 324 insertions(+), 1034 deletions(-) create mode 100644 gui/kivy/ui_screens/mainscreen.kv diff --git a/gui/kivy/Makefile b/gui/kivy/Makefile index fb11c3194..0bbb1bff0 100644 --- a/gui/kivy/Makefile +++ b/gui/kivy/Makefile @@ -7,14 +7,14 @@ theming: $(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png apk: # running pre build setup - @cp build/buildozer.spec ../../buildozer.spec + @cp tools/buildozer.spec ../../buildozer.spec # get aes.py @cd ../..; wget -4 https://raw.github.com/devrandom/slowaes/master/python/aes.py # rename electrum to main.py @mv ../../electrum ../../main.py @-if [ ! -d "../../.buildozer" ];then \ cd ../..; buildozer android debug;\ - cp -f gui/kivy/build/blacklist.txt .buildozer/android/platform/python-for-android/src/blacklist.txt;\ + cp -f gui/kivy/tools/blacklist.txt .buildozer/android/platform/python-for-android/src/blacklist.txt;\ rm -rf ./.buildozer/android/platform/python-for-android/dist;\ fi @-cd ../..; buildozer android debug deploy run diff --git a/gui/kivy/carousel.py b/gui/kivy/carousel.py index ed8f2e00c..239a28cdd 100644 --- a/gui/kivy/carousel.py +++ b/gui/kivy/carousel.py @@ -1,7 +1,7 @@ from kivy.uix.carousel import Carousel from kivy.clock import Clock -class CCarousel(Carousel): +class Carousel(Carousel): def on_touch_move(self, touch): if self._get_uid('cavoid') in touch.ud: @@ -29,12 +29,4 @@ class CCarousel(Carousel): diff = touch.dy self._offset += diff * 1.27 - return True - -if __name__ == "__main__": - from kivy.app import runTouchApp - from kivy.uix.button import Button - cc = CCarousel() - for i in range(10): - cc.add_widget(Button(text=str(i))) - runTouchApp(cc) \ No newline at end of file + return True \ No newline at end of file diff --git a/gui/kivy/drawer.py b/gui/kivy/drawer.py index bcf580d0f..5a649a096 100644 --- a/gui/kivy/drawer.py +++ b/gui/kivy/drawer.py @@ -111,7 +111,7 @@ class Drawer(StencilView): ov = self._overlay_widget ov.x=min(self._hidden_widget.width, max(ov.x + touch.dx*2, 0)) - #_anim = Animation(x=x, duration=1/60) + #_anim = Animation(x=x, duration=1/2, t='in_out_quart') #_anim.cancel_all(ov) #_anim.start(ov) diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv index 35bd64af5..d9c59e7e6 100644 --- a/gui/kivy/main.kv +++ b/gui/kivy/main.kv @@ -440,27 +440,23 @@ StencilView Rectangle: size: self.size pos: self.pos - canvas.after: - Color - rgba: 1, 1, 1, 1 - BorderImage - border: 0, 32, 0, 0 - source: 'atlas://gui/kivy/theming/light/shadow_right' - pos: self.pos - size: self.size width: (root.width * .877) if app.ui_mode[0] == 'p'\ else root.width * .35 if app.orientation[0] == 'l'\ else root.width * .10 height: root.height - ScreenManager: - id: manager + BoxLayout: x: wallet_management.width if app.ui_mode[0] == 't' else 0 - size: root.size + width: (root.width - self.x) if app.ui_mode[0] == 't' else root.width + size_hint: None, None + height: root.height canvas.before: Color rgba: 1, 1, 1, 1 - BorderImage: - border: 2, 2, 2, 23 - size: self.size - pos: self.x, self.y \ No newline at end of file + BorderImage + border: 0, 32, 0, 0 + source: 'atlas://gui/kivy/theming/light/shadow_right' + pos: root.pos + size: self.x, self.height + ScreenManager: + id: manager \ No newline at end of file diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py index 837cdc07a..3a5d2f112 100644 --- a/gui/kivy/main_window.py +++ b/gui/kivy/main_window.py @@ -7,16 +7,17 @@ from electrum.wallet import format_satoshis from kivy.app import App from kivy.core.window import Window -from kivy.metrics import inch +from kivy.lang import Builder from kivy.logger import Logger +from kivy.metrics import inch from kivy.utils import platform from kivy.properties import (OptionProperty, AliasProperty, ObjectProperty, StringProperty, ListProperty) from kivy.clock import Clock #inclusions for factory so that widgets can be used in kv -from gui.kivy.drawer import Drawer -from gui.kivy.dialog import InfoBubble +from electrum_gui.kivy.drawer import Drawer +from electrum_gui.kivy.dialog import InfoBubble # delayed imports notification = None @@ -307,7 +308,8 @@ class ElectrumWindow(App): self.completions = [] # setup UX - #self.load_dashboard + self.screens = ['mainscreen'] + self.load_screen(index=0) self.icon = "icons/electrum.png" @@ -352,8 +354,8 @@ class ElectrumWindow(App): return quote_text def set_currencies(self, quote_currencies): - #TODO remove this and just directly update a observable property self._trigger_update_status() + print quote_currencies self.currencies = sorted(quote_currencies.keys()) def update_console(self, *dt): @@ -683,19 +685,22 @@ class ElectrumWindow(App): Logger.debug('orientation: {} ui_mode: {}'.format(self._orientation, self._ui_mode)) - def load_screen(self, index=0, direction='left'): - ''' + def load_screen(self, index=0, direction='left', manager=None): + ''' Load the appropriate screen as mentioned in the parameters. ''' - screen = Builder.load_file('data/screens/' + self.screens[index]) + manager = manager or self.root.manager + screen = Builder.load_file('gui/kivy/ui_screens/'\ + + self.screens[index] + '.kv') screen.name = self.screens[index] - root.manager.switch_to(screen, direction=direction) + manager.switch_to(screen, direction=direction) def load_next_screen(self): ''' ''' manager = root.manager try: - self.load_screen(self.screens.index(manager.current_screen.name)+1) + self.load_screen(self.screens.index(manager.current_screen.name)+1, + manager=manager) except IndexError: self.load_screen() @@ -705,9 +710,10 @@ class ElectrumWindow(App): manager = root.manager try: self.load_screen(self.screens.index(manager.current_screen.name)-1, - direction='right') + direction='right', + manager=manager) except IndexError: - self.load_screen(-1, direction='right') + pass def show_error(self, error, width='200dp', diff --git a/gui/kivy/screens.py b/gui/kivy/screens.py index d96099f3b..8ce8d8643 100644 --- a/gui/kivy/screens.py +++ b/gui/kivy/screens.py @@ -1,41 +1,7 @@ -from functools import partial -import os, datetime, json, csv - from kivy.app import App -from kivy.animation import Animation -from kivy.core.clipboard import Clipboard +from kivy.uix.screenmanager import Screen +from kivy.properties import ObjectProperty from kivy.clock import Clock -from kivy.factory import Factory -from kivy.metrics import dp -from kivy.properties import (ObjectProperty, StringProperty, ListProperty, - DictProperty) - -from kivy.uix.button import Button -from kivy.uix.bubble import Bubble, BubbleButton -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.label import Label -from kivy.uix.textinput import TextInput -from kivy.uix.screenmanager import Screen as Screen, ScreenManager -from kivy.uix.tabbedpanel import TabbedPanel - - -from electrum_gui.kivy.dialog import (NewContactDialog, TakeInputDialog, - PrivateKeyDialog, SignVerifyDialog, MessageBox, MessageBoxError, - SaveDialog, LoadDialog, InfoDialog, ImportPrivateKeysDialog, Dialog, - EditLabelDialog, EditDescriptionDialog, ShowMasterPublicKeyDialog, - RecentActivityDialog) - -from electrum_gui.i18n import _, languages -from electrum_gui.kivy.menus import ContextMenu -from electrum.interface import DEFAULT_PORTS -from electrum.verifier import WalletVerifier -from electrum.wallet import Wallet, WalletSynchronizer -from electrum.bitcoin import is_valid - -DEFAULT_PATH = '/tmp/' - -# Delayed imports -encode_uri = None class CScreen(Screen): @@ -46,7 +12,7 @@ class CScreen(Screen): def _change_action_view(self): app = App.get_running_app() - action_bar = app.root.main_screen.ids.action_bar + action_bar = app.root.manager.current_screen.ids.action_bar _action_view = self.action_view if (not _action_view) or _action_view.parent: @@ -61,33 +27,6 @@ class CScreen(Screen): Clock.schedule_once(lambda dt: self._change_action_view()) -class RootManager(ScreenManager): - '''Main Root Widget of the app''' - - # initialize properties that will be updted in kv - main_screen = ObjectProperty(None) - '''Object holding the reference to main screen''' - - screen_preferences = ObjectProperty(None) - '''Object holding the reference to preferences screen''' - - screen_seed = ObjectProperty(None) - '''''' - - screen_network = ObjectProperty(None) - '''Object holding the Network screen''' - - -class MainScreen(Screen): - - pass - - -class ScreenSend(CScreen): - - pass - - class ScreenDashboard(CScreen): tab = ObjectProperty(None) @@ -154,942 +93,13 @@ class ScreenPassword(Screen): def on_release(self, *args): pass - -class SettingsScreen(Screen): - - def __init__(self, **kwargs): - super(SettingsScreen, self).__init__(**kwargs) - Clock.schedule_once(self.delayed_init) - self.app = App.get_running_app() - - def on_enter(self, *args): - self.delayed_init() - - def delayed_init(self, *dt): - app = self.app - try: - main_gui = app.gui.main_gui - except AttributeError: - # wait for main gui to start - Clock.schedule_once(self.delayed_init, 1) - return - ids = self.ids - - ids.st_unit_combo.key = main_gui.base_unit() - ids.st_fee_e.text = main_gui.format_amount(app.wallet.fee).strip() - ids.st_expert_cb.active = main_gui.expert_mode - - currencies = main_gui.exchanger.get_currencies() - currencies.insert(0, "None") - currencies = zip(currencies, currencies) - key = app.conf.get('currency', 'None') - ids.st_cur_combo.text = ids.st_cur_combo.key = key - ids.st_cur_combo.items = currencies - - ids.st_lang_combo.key = key = app.conf.get("language", '') - ids.st_lang_combo.items = languages.items() - x, y = zip(*ids.st_lang_combo.items) - ids.st_lang_combo.text = y[x.index(key)] - - def do_callback(self, instance): - ids = self.ids - app = self.app - wallet = app.wallet - main_gui = app.gui.main_gui - - if instance == ids.export_labels: - title = _("Select file to save your labels") - path = DEFAULT_PATH - filename = "electrum_labels.dat" - filters = ["*.dat"] - - def save(instance): - path = dialog.file_chooser.path - filename = dialog.text_input.text.strip() - labels = wallet.labels - try: - with open(os.path.join(path, filename), 'w+') as stream: - json.dump(labels, stream) - MessageBox(title="Labels exported", - message=_("Your labels were exported to")\ - + " '%s'" % str(filename), - size=('320dp', '320dp')).open() - except (IOError, os.error), reason: - MessageBoxError( - title="Unable to export labels", - message=_("Electrum was unable to export your labels.")+ - "\n" + str(reason), size=('320dp', '320dp')).open() - dialog.close() - - dialog = SaveDialog(title=title, - path=path, - filename=filename, - filters=filters) - dialog.save_button.bind(on_release=save) - dialog.open() - - elif instance == ids.import_labels: - title = _("Open labels file") - path = DEFAULT_PATH - filename = "" - filters = ["*.dat"] - - def load(instance): - path = dialog.file_chooser.path - filename = dialog.text_input.text.strip() - - labels = wallet.labels - try: - with open(os.path.join(path, filename), 'r') as stream: - for key, value in json.loads(stream.read()).items(): - wallet.labels[key] = value - wallet.save() - MessageBox(title="Labels imported", - message=_("Your labels were imported from") + " '%s'" % str(filename), - size=('320dp', '320dp')).open() - except (IOError, os.error), reason: - MessageBoxError(title="Unable to import labels", - message=_("Electrum was unable to import your labels.") + "\n" + str(reason), - size=('320dp', '320dp')).open() - - dialog.close() - - dialog = LoadDialog(title=title, path=path, filename=filename, filters=filters) - dialog.load_button.bind(on_press=load) - dialog.open() - - elif instance == ids.export_history: - title = _("Select file to export your wallet transactions to") - path = os.path.expanduser('~') - filename = "electrum-history.csv" - filters = ["*.csv"] - - def save(instance): - path = dialog.file_chooser.path - filename = dialog.text_input.text.strip() - # extracted from gui_lite.csv_transaction - wallet = wallet - try: - with open(os.path.join(path, filename), "w+") as stream: - transaction = csv.writer(stream) - transaction.writerow(["transaction_hash", "label", "confirmations", "value", "fee", "balance", "timestamp"]) - for item in wallet.get_tx_history(): - tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item - if confirmations: - if timestamp is not None: - try: - time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] - except [RuntimeError, TypeError, NameError] as reason: - time_string = "unknown" - pass - else: - time_string = "unknown" - else: - time_string = "pending" - - if value is not None: - value_string = format_satoshis(value, True, wallet.num_zeros) - else: - value_string = '--' - - if fee is not None: - fee_string = format_satoshis(fee, True, wallet.num_zeros) - else: - fee_string = '0' - - if tx_hash: - label, is_default_label = wallet.get_label(tx_hash) - else: - label = "" - - balance_string = format_satoshis(balance, False, wallet.num_zeros) - transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string]) - MessageBox(title="CSV Export created", - message="Your CSV export has been successfully created.", - size=('320dp', '320dp')).open() - except (IOError, os.error), reason: - export_error_label = _("Electrum was unable to produce a transaction export.") - MessageBoxError(title="Unable to create csv", - message=export_error_label + "\n" + str(reason), - size=('320dp', '320dp')).open() - dialog.close() - - dialog = SaveDialog(title=title, path=path, filename=filename, filters=filters) - dialog.save_button.bind(on_press=save) - dialog.open() - - elif instance == ids.export_privkey: - # NOTE: equivalent to @protected - def protected_save_dialog(instance=None, password=None): - def show_save_dialog(_dlg, instance): - _dlg.close() - title = _("Select file to export your private keys to") - path = DEFAULT_PATH - filename = "electrum-private-keys.csv" - filters = ["*.csv"] - - def save(instance): - path = dialog.file_chooser.path - filename = dialog.text_input.text.strip() - try: - with open(os.path.join(path, filename), "w+") as csvfile: - transaction = csv.writer(csvfile) - transaction.writerow(["address", "private_key"]) - for addr, pk in wallet.get_private_keys(wallet.addresses(True), password).items(): - transaction.writerow(["%34s" % addr, pk]) - MesageBox(message=_("Private keys exported."), - size=('320dp', '320dp')).open() - except (IOError, os.error), reason: - export_error_label = _("Electrum was unable to produce a private key-export.") - return MessageBoxError(message="Unable to create csv", content_text=export_error_label + "\n" + str(reason), - size=('320dp', '320dp')).open() - except BaseException, e: - return app.show_info_bubble(text=str(e)) - - dialog.close() - - dialog = SaveDialog(title=title, path=path, filename=filename, filters=filters) - dialog.save_button.bind(on_press=save) - dialog.open() - - mb = MessageBox(message="%s\n%s\n%s" % ( - _("[color=ff0000ff][b]WARNING[/b][/color]: ALL your private keys are secret."), - _("Exposing a single private key can compromise your entire wallet!") + '\n\n', - _("In particular, [color=ff0000ff]DO NOT[/color] use 'redeem private key' services proposed by third parties.")), - on_release=show_save_dialog, - size = ('350dp', '320dp')).open() - - if wallet.use_encryption: - return main_gui.password_required_dialog(post_ok=protected_save_dialog) - return protected_save_dialog() - - elif instance == ids.import_privkey: - # NOTE: equivalent to @protected - def protected_load_dialog(_instance=None, password=None): - def show_privkey_dialog(__instance=None): - - def on_release(_dlg, _btn): - if _btn.text == _('Cancel'): - _dlg.close() - confirm_dialog.close() - return - - text = _dlg.ids.ti.text.split() - badkeys = [] - addrlist = [] - for key in text: - try: - addr = wallet.import_key(key, password) - except BaseException as e: - badkeys.append(key) - continue - if not addr: - badkeys.append(key) - else: - addrlist.append(addr) - if addrlist: - MessageBox(title=_('Information'), - message=_("The following addresses were added") + ':\n' + '\n'.join(addrlist), - size=('320dp', '320dp')).open() - if badkeys: - MessageBoxError(title=_('Error'), - message=_("The following inputs could not be imported") + ':\n' + '\n'.join(badkeys), - size=('320dp', '320dp')).open() - main_gui.update_receive_tab() - main_gui.update_history_tab() - - if _instance is not None: # called via callback - _dlg.close() - - ImportPrivateKeysDialog(on_release=on_release).open() - - if not wallet.imported_keys: - - def on_release(_dlg, _btn): - _dlg.close - if _btn.text == _('No'): - return - show_privkey_dialog() - - confirm_dialog = MessageBoxError(title=_('Warning'), - message=_('Imported keys are not recoverable from seed.') + ' ' \ - + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \ - + _('Are you sure you understand what you are doing?'), - size=('320dp', '320dp'), - on_release=on_release) - confirm_dialog.buttons = [_('No'), _('Yes')] - confirm_dialog.open() - else: - show_privkey_dialog() - - if wallet.use_encryption: - return main_gui.password_required_dialog( - post_ok=protected_load_dialog) - return protected_load_dialog() - - elif instance == ids.show_pubkey: - # NOTE: Kivy TextInput doesn't wrap long text. So must handle it manually - pub_key = wallet.get_master_public_key() - pub_key = '%s\n%s\n%s\n%s' % (pub_key[0:31], pub_key[32:63], pub_key[64:95], pub_key[96:127]) - ShowMasterPublicKeyDialog(text=pub_key).open() - - elif instance == ids.from_file: - title = _("Select your transaction file") - path = DEFAULT_PATH - filename = "" - filters = ["*.txn"] - - def load(instance): - path = dialog.file_chooser.path - filename = dialog.text_input.text.strip() - - if not filename: - return - try: - with open(os.path.join(path, filename), "r") as f: - file_content = f.read() - except (ValueError, IOError, os.error), reason: - MessageBoxError(title="Unable to read file or no transaction found", - message=_("Electrum was unable to open your transaction file") + "\n" + str(reason), - size=('320dp', '320dp')).open() - - tx_dict = main_gui.tx_dict_from_text(file_content) - if tx_dict: - main_gui.create_process_transaction_window(tx_dict) - - dialog.close() - - dialog = LoadDialog(title=title, path=path, filename=filename, filters=filters) - dialog.load_button.bind(on_press=load) - dialog.open() - - elif instance == ids.from_text: - def load_transaction(_dlg, _btn): - if _btn.text == _('Cancel'): - _dlg.close - return - text = _dlg.ids.ti.text - if not text: - return - tx_dict = main_gui.tx_dict_from_text(text) - if tx_dict: - main_gui.create_process_transaction_window(tx_dict) - _dlg.close() - - dialog = TakeInputDialog(on_release=load_transaction) - dialog.title = title=_("Input raw transaction") - dialog.open() - - # End of do_callback() # - - def on_ok(self, instance): - ########## - app = self.app - main_gui = app.gui.main_gui - - fee = unicode(self.ids.st_fee_e.text) - try: - fee = main_gui.read_amount(fee) - except: - return MessageBoxError(message=_('Invalid value') + ': %s' % fee).open() - - app.wallet.set_fee(fee) - - ########## - nz = unicode(self.ids.st_nz_e.text) - try: - nz = int(nz) - if nz > 8: nz = 8 - except: - return MessageBoxError(message=_('Invalid value') + ':%s' % nz).open() - - if app.wallet.num_zeros != nz: - app.wallet.num_zeros = nz - app.conf.set_key('num_zeros', nz, True) - main_gui.update_history_tab() - main_gui.update_receive_tab() - - usechange_result = self.ids.st_usechange_cb.active - if app.wallet.use_change != usechange_result: - app.wallet.use_change = usechange_result - app.conf.set_key('use_change', app.wallet.use_change, True) - - unit_result = self.ids.st_unit_combo.text - if main_gui.base_unit() != unit_result: - main_gui.decimal_point = 8 if unit_result == 'BTC' else 5 - app.conf.set_key('decimal_point', main_gui.decimal_point, True) - main_gui.update_history_tab() - main_gui.update_status() - - try: - n = int(self.ids.st_gap_e.text) - except: - return MessageBoxError(message=_('Invalid value')).open() - - if app.wallet.gap_limit != n: - if app.wallet.change_gap_limit(n): - main_gui.update_receive_tab() - app.conf.set_key('gap_limit', app.wallet.gap_limit, True) - else: - MessageBoxError(Message=_('Invalid value')).open() - # TODO: no return??? - - need_restart = False - - lang_request = str(self.ids.st_lang_combo.key) - if lang_request != app.conf.get('language'): - app.conf.set_key("language", lang_request, True) # TODO: why can't save unicode - need_restart = True - - cur_request = str(self.ids.st_cur_combo.text) - if cur_request != app.conf.get('currency', "None"): - app.conf.set_key('currency', cur_request, True) # TODO: why can't save unicode - main_gui.update_wallet() - - main_gui.run_hook('close_settings_dialog') - - if need_restart: - MessageBox(message=_('Please restart Electrum to activate the new GUI settings')).open() - - # from receive_tab_set_mode() - main_gui.save_column_widths() - main_gui.expert_mode = self.ids.st_expert_cb.active - app.conf.set_key('classic_expert_mode', main_gui.expert_mode, True) - main_gui.update_receive_tab() - - # close - app.root.current = 'main_screen' - - -class NetworkScreen(Screen): - - status = StringProperty(_('Uninitialized')) - '''status message displayed on top of screen''' - - server = StringProperty('') - - #servers = ListProperty([]) - - servers_view = ObjectProperty(None) - - server_host = ObjectProperty(None) - - server_port = ObjectProperty(None) - - server_protocol = ObjectProperty(None) - - proxy_host = ObjectProperty(None) - - proxy_port = ObjectProperty(None) - - proxy_mode = ObjectProperty(None) - - protocol_names = ListProperty(['TCP', 'HTTP', 'SSL', 'HTTPS']) - - protocol_letters = StringProperty('thsg') - - proxy_names = ListProperty(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP']) - - proxy_keys = ListProperty(['none', 'socks4', 'socks5', 'http']) - - autocycle_cb = ObjectProperty(None) - - interface = ObjectProperty(None) - - def __init__(self, **kwargs): - self.initialized = True - super(NetworkScreen, self).__init__(**kwargs) - Clock.schedule_once(self._delayed_init) - - def _delayed_init(self, dt): - self.protocol = None - self.app = app = App.get_running_app() - self.conf = conf = app.conf - self.wallet = wallet = app.wallet - self.interface = interface = wallet.interface - - if not self.initialized: - if interface.is_connected: - self.status = _("Connected to") + " %s" % (interface.host) + "\n%d " % (wallet.verifier.height) + _("blocks") - else: - self.status = _("Not connected") - else: - self.status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.") - self.server = server = interface.server - - self.servers = interface.get_servers() - - self.servers_view.content_adapter.bind(on_selection_change=self.server_changed) - - ######################## - if server: - host, port, protocol = server.split(':') - self.set_protocol(protocol) - self.change_server(host, protocol) - else: - self.set_protocol('s') - - ######################## - # TODO: review it - # if not config.is_modifiable('server'): - # for w in [self.server_host, self.server_port, self.server_protocol, self.servers_list_widget]: w.setEnabled(False) - - self.check_for_disable(None, 'none') - - # if not wallet.config.is_modifiable('proxy'): - # for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False) - - proxy_config = interface.proxy\ - if interface.proxy else\ - { "mode":"none", "host":"localhost", "port":"8080"} - self.proxy_mode.key = proxy_config.get("mode") - self.proxy_host.text = proxy_config.get("host") - self.proxy_port.text = proxy_config.get("port") - - # server = unicode( server_host.text ) + ':' + unicode( server_port.text ) + ':' + (protocol_letters[server_protocol.currentIndex()]) - # if proxy_mode.currentText() != 'NONE': - # proxy = { u'mode':unicode(proxy_mode.currenttext).lower(), u'host':unicode(proxy_host.text), u'port':unicode(proxy_port.text) } - # else: - # proxy = None - - self.autocycle_cb.active = conf.get('auto_cycle', True) - if not conf.is_modifiable('auto_cycle'): - self.autocycle_cb.active = False - - def check_for_disable(self, instance, proxy_mode_key): - if proxy_mode_key != 'none': - self.proxy_host.disabled = False - self.proxy_port.disabled = False - else: - self.proxy_host.disabled = True - self.proxy_port.disabled = True - - def on_cancel(self, *args): - self.manager.current = 'main_screen' - - # TODO: RuntimeError: threads can only be started once - # interface.start(wait=False) - # interface.send([('server.peers.subscribe', [])]) - - # generate the first addresses, in case we are offline - self.wallet.synchronize() - - verifier = WalletVerifier(self.interface, self.conf) - verifier.start() - self.wallet.set_verifier(verifier) - synchronizer = WalletSynchronizer(self.wallet, self.conf) - synchronizer.start() - - if not self.initialized: - self.app.gui.main_gui.change_password_dialog() - - def on_ok(self, *args): - self.manager.current = 'main_screen' - - ################ - server = ':'.join([str(self.server_host.text), - str(self.server_port.text), - str(self.server_protocol.key)]) - - if self.proxy_mode.key != 'none': - proxy = { 'mode':str(self.proxy_mode.key), - 'host':str(self.proxy_host.text), - 'port':str(self.proxy_port.text) } - else: - proxy = None - - app = self.app - conf = self.conf - wallet = self.wallet - interface = self.interface - conf.set_key("proxy", proxy, True) - conf.set_key("server", server, True) - interface.set_server(server, proxy) - conf.set_key('auto_cycle', self.autocycle_cb.active, True) - - # generate the first addresses, in case we are offline - if app.gui.action == 'create': - app.wallet.synchronize() - app.gui.change_password_dialog() - - verifier = WalletVerifier(interface, conf) - verifier.start() - wallet.set_verifier(verifier) - synchronizer = WalletSynchronizer(wallet, conf) - synchronizer.start() - - if app.gui.action == 'restore': - initialized = self.initialized - try: - def on_complete(keep_it=False): - wallet.fill_addressbook() - #if not keep_it: - # app.stop() - # return - if not initialized: - app.gui.change_password_dialog() - - app.gui.restore_wallet(on_complete=on_complete) - except: - import traceback, sys - traceback.print_exc(file=sys.stdout) - app.stop() - if not interface.isAlive(): - interface.start(wait=False) - interface.send([('server.peers.subscribe', [])]) - - - def init_servers_list(self): - data = [] - for _host, d in self.servers.items(): - if d.get(self.protocol): - pruning_level = d.get('pruning', '') - data.append((_host, pruning_level)) - self.servers_view.content_adapter.data = data - - def set_protocol(self, protocol): - if protocol != self.protocol: - self.protocol = protocol - self.init_servers_list() - - def on_change_protocol(self, instance, protocol_key): - p = protocol_key - host = unicode(self.server_host.text) - pp = self.servers.get(host) - if not pp: - return - if p not in pp.keys(): - p = pp.keys()[0] - port = pp[p] - self.server_host.text = host - self.server_port.text = port - self.set_protocol(p) - - def server_changed(self, instance): - try: - index = instance.selection[0].index - except (AttributeError, IndexError): - return - item = instance.get_data_item(index) - self.change_server(item[0], self.protocol) - - def change_server(self, host, protocol): - pp = self.servers.get(host, DEFAULT_PORTS) - if protocol: - port = pp.get(protocol) - if not port: protocol = None - - if not protocol: - if 's' in pp.keys(): - protocol = 's' - port = pp.get(protocol) - else: - protocol = pp.keys()[0] - port = pp.get(protocol) - - self.server_host.text = host - self.server_port.text = port - self.server_protocol.text = self.protocol_names[self.protocol_letters.index(protocol)] - - if not self.servers: return - # TODO: what's this? - # for p in protocol_letters: - # i = protocol_letters.index(p) - # j = self.server_protocol.model().index(i,0) - # #if p not in pp.keys(): # and self.interface.is_connected: - # # self.server_protocol.model().setData(j, QVariant(0), Qt.UserRole-1) - # #else: - # # self.server_protocol.model().setData(j, QVariant(33), Qt.UserRole-1) - -class ScreenAddress(CScreen): - - labels = DictProperty({}) - ''' - ''' - - tab = ObjectProperty(None) - ''' The tab associated With this Carousel - ''' - -class ScreenConsole(CScreen): - +class ScreenSend(CScreen): pass - class ScreenReceive(CScreen): - pass -#TODO: move to wallet management -class ScreenReceive2(CScreen): - - receive_view = ObjectProperty(None) - - def __init__(self, **kwargs): - self.context_menu = None - super(ScreenReceive, self).__init__(**kwargs) - self.app = App.get_running_app() - - def on_receive_view(self, instance, value): - if not value: - return - value.on_context_menu = self.on_context_menu - - def on_menu_item_selected(self, instance, _menu, _btn): - '''Called when any one of the bubble menu items is selected - ''' - app = self.app - main_gui = app.gui.main_gui - - def delete_imported_key(): - def on_release(_dlg, _dlg_btn): - if _dlg_btn.text == _('Cancel'): - _dlg.close() - return - app.wallet.delete_imported_key(address) - main_gui.update_receive_tab() - main_gui.update_history_tab() - - MessageBox(title=_('Delete imported key'), - message=_("Do you want to remove") - +" %s "%addr +_("from your wallet?"), - buttons=[_('Cancel'), _('OK')], - on_release=on_release).open() - - def edit_label_dialog(): - # Show dialog to edit the label - def save_label(_dlg, _dlg_btn): - if _dlg_btn.text != _('Ok'): - return - txt = _dlg.ids.ti.text - if txt: - instance.parent.children[2].text = txt - _dlg.close() - - text = instance.parent.children[2].text - dialog = EditLabelDialog(text=text, - on_release=save_label).open() - - def show_private_key_dialog(): - # NOTE: equivalent to @protected - def protected_show_private_key(_instance=None, password=None): - try: - pk = app.wallet.get_private_key(address, password) - except BaseException, e: - app.show_info_bubble(text=str(e)) - return - - PrivateKeyDialog(address=address, - private_key=pk).open() - - if app.wallet.use_encryption: - return main_gui.password_required_dialog( - post_ok=protected_show_private_key) - protected_show_private_key() - - def show_sign_verify_dialog(): - def on_release(_dlg, _dlg_btn): - if _dlg_btn.text != _('Ok'): - return - if _dlg.ids.tabs.current_tab.text == _('Sign'): - # NOTE: equivalent to @protected - def protected_do_sign_message(instance=None, password=None): - try: - sig = app.wallet.sign_message( - _dlg.ids.sign_address.text, - _dlg.ids.sign_message.text, - password) - _dlg.ids.sign_signature.text = sig - except BaseException, e: - app.show_info_bubble(text=str(e.message)) - - if app.wallet.use_encryption: - return main_gui.password_required_dialog( - post_ok=protected_do_sign_message) - return protected_do_sign_message() - - else: # _('Verify') - if app.wallet.verify_message( - _dlg.ids.verify_address.text, - _dlg.ids.verify_signature.text, - _dlg.ids.verify_message.text): - app.show_info_bubble(text=_("Signature verified")) - else: - app.show_info_bubble( - text=_("Error: wrong signature")) - SignVerifyDialog(on_release=on_release, address=address).open() - - def toggle_freeze(): - if address in app.wallet.frozen_addresses: - app.wallet.unfreeze(address) - else: - app.wallet.freeze(address) - main_gui.update_receive_tab() - - def toggle_priority(_dlg, _dlg_btn): - if address in app.wallet.prioritized_addresses: - app.wallet.unprioritize(address) - else: - app.wallet.prioritize(address) - main_gui.update_receive_tab() - - _menu.hide() - address = instance.parent.children[3].text - - if _btn.text == _('Copy to clipboard'): - # copy data to clipboard - Clipboard.put(instance.parent.children[3].text, 'UTF8_STRING') - elif _btn.text == _('Edit label'): - edit_label_dialog() - elif _btn.text == _('Private key'): - show_private_key_dialog() - elif _btn.text == _('Sign message'): - # sign message - show_sign_verify_dialog() - elif _btn.text == _('Remove_from_wallet'): - delete_imported_key() - elif _btn.text in (_('Freeze'), _('Unfreeze')): - toggle_freeze() - elif _btn.text in (_('Prioritize'), _('Unprioritize')): - toggle_priority(_menu, _btn) - - - def on_context_menu(self, instance): - '''Called when list item is clicked. - Objective: show bubble menu - ''' - app = self.app - address = instance.parent.children[3].text - if not address or not is_valid(address): return - - context_menu = ContextMenu(size_hint=(None, None), - size=('160dp', '160dp'), - orientation='vertical', - arrow_pos='left_mid', - buttons=[_('Copy to clipboard'), - _('Edit label'), - _('Private key'), - _('Sign message')], - on_release=partial(self.on_menu_item_selected, - instance)) - if address in app.wallet.imported_keys: - context_menu.buttons = context_menu.buttons +\ - [_('Remove from wallet')] - # TODO: test more this feature - - if app.gui.main_gui.expert_mode: - # TODO: show frozen, prioritized rows in different color - # as original code - - t = _("Unfreeze")\ - if address in app.wallet.frozen_addresses else\ - _("Freeze") - context_menu.buttons = context_menu.buttons + [t] - t = _("Unprioritize")\ - if address in app.wallet.prioritized_addresses else\ - _("Prioritize") - context_menu.buttons = context_menu.buttons + [t] - context_menu.show(pos=(instance.right, instance.top)) - - class ScreenContacts(CScreen): def add_new_contact(self): NewContactDialog().open() - - - -class TabbedCarousel(TabbedPanel): - - carousel = ObjectProperty(None) - - def animate_tab_to_center(self, value): - scrlv = self._tab_strip.parent - if not scrlv: - return - self_center_x = scrlv.center_x - vcenter_x = value.center_x - diff_x = (self_center_x - vcenter_x) - try: - scroll_x = scrlv.scroll_x - (diff_x / scrlv.width) - except ZeroDivisionError: - pass - mation = Animation(scroll_x=max(0, min(scroll_x, 1)), d=.25) - mation.cancel_all(scrlv) - mation.start(scrlv) - - def on_current_tab(self, instance, value): - if value.text == 'default_tab': - return - 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_deactivate') - self.switch_to(tab) - carousel.slides[tab.slide].dispatch('on_activate') - except AttributeError: - current_slide.dispatch('on_activate') - - 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) - tab = self.tab_list[-1] - 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_deactivate') - carousel.load_slide(slide) - slide.dispatch('on_activate') - - def add_widget(self, widget, index=0): - if isinstance(widget, Screen): - self.carousel.add_widget(widget) - return - super(TabbedCarousel, self).add_widget(widget, index=index) - - -class TabbedScreens(TabbedPanel): - - manager = ObjectProperty(None) - '''Linked to the screen manager in kv''' - - def switch_to(self, header): - # we don't use default tab so skip - if not hasattr(header, 'screen'): - header.content = self.manager - super(TabbedScreens, self).switch_to(header) - return - if not header.screen: - return - panel = self - panel.current_tab.state = "normal" - header.state = 'down' - panel._current_tab = header - self.manager.current = header.screen - - def add_widget(self, widget, index=0): - if isinstance(widget, Screen): - self.manager.add_widget(widget) - return - super(TabbedScreens, self).add_widget(widget, index=index) diff --git a/gui/kivy/ui_screens/mainscreen.kv b/gui/kivy/ui_screens/mainscreen.kv new file mode 100644 index 000000000..3f7200cf2 --- /dev/null +++ b/gui/kivy/ui_screens/mainscreen.kv @@ -0,0 +1,286 @@ +#:import TabbedCarousel electrum_gui.kivy.tabbed_carousel.TabbedCarousel +#:import ScreenDashboard electrum_gui.kivy.screens.ScreenDashboard +#:import Factory kivy.factory.Factory +#:import Carousel electrum_gui.kivy.carousel.Carousel + +Screen: + canvas.before: + Color: + rgba: 0.917, 0.917, 0.917, 1 + Rectangle: + size: self.size + pos: self.pos + BoxLayout: + orientation: 'vertical' + ActionBar: + id: action_bar + size_hint: 1, None + height: '40dp' + border: 4, 4, 4, 4 + background_image: 'atlas://gui/kivy/theming/light/action_bar' + ScreenManager: + id: manager + ScreenTabs: + id: tabs + name: "tabs" + #ScreenPassword: + # id: password + # name: 'password' + + + carousel: carousel + do_default_tab: False + Carousel: + scroll_timeout: 190 + anim_type: 'out_quart' + min_move: .05 + anim_move_duration: .1 + anim_cancel_duration: .54 + scroll_distance: '10dp' + on_index: root.on_index(*args) + id: carousel + +################################ +## Cards (under Dashboard) +################################ + + + cols: 1 + padding: '12dp' , '22dp', '12dp' , '12dp' + spacing: '12dp' + size_hint: 1, None + height: max(100, self.minimum_height) + canvas.before: + Color: + rgba: 1, 1, 1, 1 + BorderImage: + border: 9, 9, 9, 9 + source: 'atlas://gui/kivy/theming/light/card' + size: self.size + pos: self.pos + + + color: 0.45, 0.45, 0.45, 1 + size_hint: 1, None + text: '' + text_size: self.width, None + height: self.texture_size[1] + halign: 'left' + valign: 'top' + + + background_normal: 'atlas://gui/kivy/theming/light/card_btn' + bold: True + font_size: '10sp' + color: 0.699, 0.699, 0.699, 1 + size_hint: None, None + size: self.texture_size[0] + dp(32), self.texture_size[1] + dp(7) + + + size_hint: 1, None + height: dp(1) + color: .909, .909, .909, 1 + canvas: + Color: + rgba: root.color if root.color else (0, 0, 0, 0) + Rectangle: + size: self.size + pos: self.pos + + + BoxLayout: + size_hint: 1, None + height: lbl.height + CardLabel: + id: lbl + text: _('RECENT ACTIVITY') + CardButton: + id: btn_see_all + text: _('SEE ALL') + font_size: '12sp' + on_release: app.gui.main_gui.update_history(see_all=True) + GridLayout: + id: content + spacing: '7dp' + cols: 1 + size_hint: 1, None + height: self.minimum_height + CardSeparator + + + CardLabel: + text: _('PAYMENT REQUEST') + CardSeparator: + + + status: app.status + base_unit: 'BTC' + quote_text: '.' + unconfirmed: '.' + BoxLayout: + size_hint: 1, None + height: '72dp' + IconButton: + mipmap: True + color: .90, .90, .90, 1 + source: 'atlas://gui/kivy/theming/light/qrcode' + size_hint: None, 1 + width: self.height + on_release: + Factory.WalletAddressesDialog().open() + GridLayout: + id: grid + cols: 1 + orientation: 'vertical' + CardLabel: + halign: 'right' + valign: 'top' + bold: True + size_hint: 1, None + font_size: '38sp' + text: + '[color=#4E4F4F]{}[/color]'\ + '[sup][color=9b948d]{}[/color][/sup]'\ + .format(unicode(root.status), root.base_unit) + CardLabel + halign: 'right' + markup: True + font_size: '15dp' + text: '[color=#c3c3c3]{}[/color]'.format(root.quote_text) + CardLabel + halign: 'right' + markup: True + text: '[color=#c3c3c3]{}[/color]'.format(root.unconfirmed) + + + ActionPrevious: + id: action_previous + app_icon: 'atlas://gui/kivy/theming/light/wallets' + with_previous: False + size_hint: None, 1 + mipmap: True + width: '77dp' + ActionButton: + id: action_logo + important: True + size_hint: 1, 1 + markup: True + mipmap: True + bold: True + font_size: '22dp' + icon: 'atlas://gui/kivy/theming/light/logo' + minimum_width: '1dp' + ActionButton: + id: action_contact + important: True + width: '25dp' + icon: 'atlas://gui/kivy/theming/light/add_contact' + text: 'Add Contact' + on_release: NewContactDialog().open() + ActionOverflow: + id: action_preferences + canvas.after: + Color: + rgba: 1, 1, 1, 1 + border: 0, 0, 0, 0 + overflow_image: 'atlas://gui/kivy/theming/light/settings' + width: '32dp' + ActionButton: + text: _('Seed') + on_release: + action_preferences._dropdown.dismiss() + if app.wallet.seed: app.gui.main_gui.protected_seed_dialog(self) + ActionButton: + text: _('Password') + ActionButton: + text: _('Network') + on_release: + app.root.current = 'screen_network' + action_preferences._dropdown.dismiss() + ActionButton: + text: _('Preferences') + on_release: + action_preferences._dropdown.dismiss() + app.gui.main_gui.show_settings_dialog(self) + + + action_view: Factory.DashboardActionView() + ScrollView: + do_scroll_x: False + RelativeLayout: + size_hint: 1, None + height: grid.height + GridLayout + id: grid + cols: 1 #if root.width < root.height else 2 + size_hint: 1, None + height: self.minimum_height + padding: '12dp' + spacing: '12dp' + GridLayout: + cols: 1 + size_hint: 1, None + height: self.minimum_height + spacing: '12dp' + orientation: 'vertical' + CardStatusInfo: + id: status_card + CardPaymentRequest: + id: payment_card + CardRecentActivity: + id: recent_activity_card + + + border: 0, 0, 4, 0 + markup: False + color: (0.191, 0.558, 0.742, 1) if self.state == 'down' else (0.636, 0.636, 0.636, 1) + text_size: self.size + halign: 'center' + valign: 'middle' + bold: True + font_size: '12sp' + background_normal: 'atlas://gui/kivy/theming/light/tab_btn' + background_disabled_normal: 'atlas://gui/kivy/theming/light/tab_btn_disabled' + background_down: 'atlas://gui/kivy/theming/light/tab_btn_pressed' + canvas.before: + Color: + rgba: 1, 1, 1, .7 + Rectangle: + size: self.size + pos: self.x + 1, self.y - 1 + texture: self.texture + + + TabbedCarousel: + id: panel + background_image: 'atlas://gui/kivy/theming/light/tab' + strip_image: 'atlas://gui/kivy/theming/light/tab_strip' + strip_border: 4, 0, 2, 0 + ScreenDashboard: + id: screen_dashboard + tab: tab_dashboard + #ScreenSend: + # id: screen_send + # tab: tab_send + #ScreenReceive: + # id: screen_receive + # tab: tab_receive + #ScreenContacts: + # id: screen_contacts + # tab: tab_contacts + CleanHeader: + id: tab_dashboard + text: _('DASHBOARD') + slide: 0 + #CleanHeader: + # id: tab_send + # text: _('SEND') + # slide: 1 + #CleanHeader: + # id: tab_receive + # text: _('RECEIVE') + # slide: 2 + #CleanHeader: + # id: tab_contacts + # text: _('CONTACTS') + # slide: 3 \ No newline at end of file