def fetch_balance(self): """ Fetches the new balance & sets accounts_balance property. """ if self.current_account is None: return address = '0x' + self.current_account.address.hex() chain_id = Settings.get_stored_network() try: balance = PyWalib.get_balance(address, chain_id) except ConnectionError: Dialog.on_balance_connection_error() Logger.warning('ConnectionError', exc_info=True) return except ValueError: # most likely the JSON object could not be decoded, refs #91 # currently logged as an error, because we want more insight # in order to eventually handle it more specifically Dialog.on_balance_value_error() Logger.error('ValueError', exc_info=True) return except UnknownEtherscanException: # also handles uknown errors, refs #112 Dialog.on_balance_unknown_error() Logger.error('UnknownEtherscanException', exc_info=True) return # triggers accounts_balance observers update self.accounts_balance[address] = balance
def test_get_balance(self): """ Checks get_balance() returns a float. """ address = ADDRESS balance_eth = PyWalib.get_balance(address) self.assertTrue(type(balance_eth), float)
class Controller(FloatLayout): current_account = ObjectProperty(None, allownone=True) # keeps track of all dialogs alive dialogs = [] def __init__(self, **kwargs): super(Controller, self).__init__(**kwargs) keystore_path = Controller.get_keystore_path() self.pywalib = PyWalib(keystore_path) Clock.schedule_once(lambda dt: self.load_landing_page()) @property def overview(self): overview_bnavigation_id = self.ids.overview_bnavigation_id return overview_bnavigation_id.ids.overview_id @property def history(self): return self.overview.ids.history_id @property def send(self): overview_bnavigation_id = self.ids.overview_bnavigation_id return overview_bnavigation_id.ids.send_id @property def toolbar(self): return self.ids.toolbar_id def set_toolbar_title(self, title): self.toolbar.title_property = title def open_account_list_helper(self, on_selected_item): title = "Select account" items = [] pywalib = self.pywalib account_list = pywalib.get_account_list() for account in account_list: address = '0x' + account.address.encode("hex") item = OneLineListItem(text=address) # makes sure the address doesn't wrap in multiple lines, # but gets shortened item.ids._lbl_primary.shorten = True item.account = account items.append(item) dialog = Controller.create_list_dialog(title, items, on_selected_item) dialog.open() def open_account_list_overview(self): def on_selected_item(instance, value): self.set_current_account(value.account) self.open_account_list_helper(on_selected_item) def set_current_account(self, account): self.current_account = account def on_current_account(self, instance, value): """ Updates Overview.current_account and History.current_account, then fetch account data. """ self.overview.current_account = value self.history.current_account = value self._load_balance() @staticmethod def show_invalid_form_dialog(): title = "Invalid form" body = "Please check form fields." dialog = Controller.create_dialog(title, body) dialog.open() @staticmethod def patch_keystore_path(): """ Changes pywalib default keystore path depending on platform. Currently only updates it on Android. """ if platform != "android": return import pywalib # uses kivy user_data_dir (/sdcard/<app_name>) pywalib.KEYSTORE_DIR_PREFIX = App.get_running_app().user_data_dir @staticmethod def get_keystore_path(): """ This is the Kivy default keystore path. """ keystore_path = os.environ.get('KEYSTORE_PATH') if keystore_path is None: Controller.patch_keystore_path() keystore_path = PyWalib.get_default_keystore_path() return keystore_path @staticmethod def src_dir(): return os.path.dirname(os.path.abspath(__file__)) @staticmethod def create_list_dialog(title, items, on_selected_item): """ Creates a dialog from given title and list. items is a list of BaseListItem objects. """ # select_list = PWSelectList(items=items, on_release=on_release) select_list = PWSelectList(items=items) select_list.bind(selected_item=on_selected_item) content = select_list dialog = MDDialog(title=title, content=content, size_hint=(.9, .9)) # workaround for MDDialog container size (too small by default) dialog.ids.container.size_hint_y = 1 # close the dialog as we select the element select_list.bind( selected_item=lambda instance, value: dialog.dismiss()) dialog.add_action_button("Dismiss", action=lambda *x: dialog.dismiss()) return dialog @staticmethod def on_dialog_dismiss(dialog): """ Removes it from the dialogs track list. """ Controller.dialogs.remove(dialog) @staticmethod def dismiss_all_dialogs(): """ Dispatches dismiss event for all dialogs. """ dialogs = Controller.dialogs[:] for dialog in dialogs: dialog.dispatch('on_dismiss') @staticmethod def create_dialog(title, body): """ Creates a dialog from given title and body. Adds it to the dialogs track list. """ content = MDLabel(font_style='Body1', theme_text_color='Secondary', text=body, size_hint_y=None, valign='top') content.bind(texture_size=content.setter('size')) dialog = MDDialog(title=title, content=content, size_hint=(.8, None), height=dp(200), auto_dismiss=False) dialog.add_action_button("Dismiss", action=lambda *x: dialog.dismiss()) dialog.bind(on_dismiss=Controller.on_dialog_dismiss) Controller.dialogs.append(dialog) return dialog @staticmethod def on_balance_connection_error(): title = "Network error" body = "Couldn't load balance, no network access." dialog = Controller.create_dialog(title, body) dialog.open() @staticmethod def on_history_connection_error(): title = "Network error" body = "Couldn't load history, no network access." dialog = Controller.create_dialog(title, body) dialog.open() @staticmethod def show_not_implemented_dialog(): title = "Not implemented" body = "This feature is not yet implemented." dialog = Controller.create_dialog(title, body) dialog.open() @mainthread def update_balance_label(self, balance): overview_id = self.overview overview_id.balance_property = balance def get_overview_title(self): overview_id = self.overview return overview_id.get_title() @staticmethod @mainthread def snackbar_message(text): Snackbar(text=text).show() def load_landing_page(self): """ Loads the landing page. """ try: # will trigger account data fetching self.current_account = self.pywalib.get_main_account() self.ids.screen_manager_id.current = "overview" self.ids.screen_manager_id.transition.direction = "right" except IndexError: self.load_create_new_account() @run_in_thread def _load_balance(self): account = self.current_account try: balance = self.pywalib.get_balance(account.address.encode("hex")) except ConnectionError: Controller.on_balance_connection_error() return self.update_balance_label(balance) def load_manage_keystores(self): """ Loads the manage keystores screen. """ # loads the manage keystores screen self.ids.screen_manager_id.transition.direction = "left" self.ids.screen_manager_id.current = 'manage_keystores' def load_create_new_account(self): """ Loads the create new account tab from the maage keystores screen. """ self.load_manage_keystores() # loads the create new account tab manage_keystores = self.ids.manage_keystores_id create_new_account = manage_keystores.ids.create_new_account_id create_new_account.dispatch('on_tab_press') def load_about_screen(self): """ Loads the about screen. """ self.ids.screen_manager_id.transition.direction = "left" self.ids.screen_manager_id.current = "about"