electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 47b6c2d87f6b8147dfc085c8e4ccf9e872dc31cf
parent 25626cf23b0134edbef497dab0eefa3cbe79cda8
Author: ThomasV <thomasv@electrum.org>
Date:   Mon,  9 Mar 2020 11:12:59 +0100

improve kivy password dialog:
 - separate classes for pin code and password
 - add file selector to initial screen

Diffstat:
Melectrum/gui/kivy/main.kv | 6+++---
Melectrum/gui/kivy/main_window.py | 43++++++++++++++++++++++++++++---------------
Melectrum/gui/kivy/uix/dialogs/password_dialog.py | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Melectrum/gui/kivy/uix/dialogs/wallets.py | 4++--
4 files changed, 140 insertions(+), 61 deletions(-)

diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv @@ -428,7 +428,7 @@ BoxLayout: size: 0, 0 ActionButton: - size_hint_x: 0.5 + size_hint_x: None text: app.wallet_name bold: True color: 0.7, 0.7, 0.7, 1 @@ -438,13 +438,13 @@ BoxLayout: self.state = 'normal' ActionButton: - size_hint_x: 0.4 + size_hint_x: 0.8 text: '' opacity:0 ActionOverflow: id: ao - size_hint_x: 0.15 + size_hint_x: 0.2 ActionOvrButton: name: 'about' text: _('About') diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py @@ -31,7 +31,7 @@ from kivy.clock import Clock from kivy.factory import Factory from kivy.metrics import inch from kivy.lang import Builder -from .uix.dialogs.password_dialog import PasswordDialog +from .uix.dialogs.password_dialog import PasswordDialog, PincodeDialog ## lazy imports for factory so that widgets can be used in kv #Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard') @@ -370,6 +370,7 @@ class ElectrumWindow(App): # cached dialogs self._settings_dialog = None + self._pincode_dialog = None self._password_dialog = None self._channels_dialog = None self._addresses_dialog = None @@ -626,10 +627,11 @@ class ElectrumWindow(App): if wallet: if wallet.has_password(): def on_success(x): - # save pin_code so that we can create backups + # save password in memory self.password = x self.load_wallet(wallet) self.password_dialog( + basename = wallet.basename(), check_password=wallet.check_password, on_success=on_success, on_failure=self.stop) @@ -652,6 +654,7 @@ class ElectrumWindow(App): storage.decrypt(pw) self._on_decrypted_storage(storage) self.password_dialog( + basename = storage.basename(), check_password=storage.check_password, on_success=on_password, on_failure=self.stop) @@ -735,13 +738,17 @@ class ElectrumWindow(App): if self._channels_dialog: Clock.schedule_once(lambda dt: self._channels_dialog.update()) + def wallets_dialog(self): + from .uix.dialogs.wallets import WalletDialog + d = WalletDialog() + d.path = os.path.dirname(self.electrum_config.get_wallet_path()) + d.open() + def popup_dialog(self, name): if name == 'settings': self.settings_dialog() elif name == 'wallets': - from .uix.dialogs.wallets import WalletDialog - d = WalletDialog() - d.open() + self.wallets_dialog() elif name == 'status': popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv') master_public_keys_layout = popup.ids.master_public_keys @@ -949,7 +956,7 @@ class ElectrumWindow(App): def on_resume(self): now = time.time() if self.wallet and self.wallet.has_password() and now - self.pause_time > 5*60: - self.password_dialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop, is_password=False) + self.pincode_dialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop) if self.nfcscanner: self.nfcscanner.nfc_enable() @@ -1128,12 +1135,11 @@ class ElectrumWindow(App): def protected(self, msg, f, args): if self.electrum_config.get('pin_code'): on_success = lambda pw: f(*(args + (self.password,))) - self.password_dialog( + self.pincode_dialog( message = msg, check_password=self.check_pin_code, on_success=on_success, - on_failure=lambda: None, - is_password=False) + on_failure=lambda: None) else: f(*(args + (self.password,))) @@ -1220,6 +1226,12 @@ class ElectrumWindow(App): self._password_dialog.init(self, **kwargs) self._password_dialog.open() + def pincode_dialog(self, **kwargs): + if self._pincode_dialog is None: + self._pincode_dialog = PincodeDialog() + self._pincode_dialog.init(self, **kwargs) + self._pincode_dialog.open() + def change_password(self, cb): def on_success(old_password, new_password): self.wallet.update_password(old_password, new_password) @@ -1227,25 +1239,26 @@ class ElectrumWindow(App): self.show_info(_("Your password was updated")) on_failure = lambda: self.show_error(_("Password not updated")) self.password_dialog( + basename = self.wallet.basename(), check_password = self.wallet.check_password, on_success=on_success, on_failure=on_failure, - is_change=True, is_password=True, + is_change=True, has_password=self.wallet.has_password()) def change_pin_code(self, cb): - if self._password_dialog is None: - self._password_dialog = PasswordDialog() + if self._pincode_dialog is None: + self._pincode_dialog = PincodeDialog() def on_success(old_password, new_password): self.electrum_config.set_key('pin_code', new_password) cb() self.show_info(_("PIN updated") if new_password else _('PIN disabled')) on_failure = lambda: self.show_error(_("PIN not updated")) - self._password_dialog.init( + self._pincode_dialog.init( self, check_password=self.check_pin_code, on_success=on_success, on_failure=on_failure, - is_change=True, is_password=False, + is_change=True, has_password = self.has_pin_code()) - self._password_dialog.open() + self._pincode_dialog.open() def save_backup(self): if platform != 'android': diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py @@ -19,12 +19,32 @@ Builder.load_string(''' <PasswordDialog@Popup> id: popup - is_generic: False title: 'Electrum' message: '' + basename:'' + is_change: False BoxLayout: size_hint: 1, 1 orientation: 'vertical' + spacing: '12dp' + padding: '12dp' + BoxLayout: + size_hint: 1, None + orientation: 'horizontal' + height: '40dp' + Label: + size_hint: 0.85, None + height: '40dp' + font_size: '20dp' + text: _('Wallet') + ': ' + root.basename + text_size: self.width, None + IconButton: + size_hint: 0.15, None + height: '40dp' + icon: 'atlas://electrum/gui/kivy/theming/light/btn_create_account' + on_release: root.select_file() + disabled: root.is_change + opacity: 0 if root.is_change else 1 Widget: size_hint: 1, 0.05 Label: @@ -37,10 +57,7 @@ Builder.load_string(''' BoxLayout: orientation: 'horizontal' id: box_generic_password - visible: root.is_generic size_hint_y: 0.05 - opacity: 1 if self.visible else 0 - disabled: not self.visible WizardTextInput: id: textinput_generic_password valign: 'center' @@ -50,7 +67,7 @@ Builder.load_string(''' password: True size_hint: 0.9, None unfocus_on_touch: False - focus: root.is_generic + focus: True Button: size_hint: 0.1, None valign: 'center' @@ -61,12 +78,30 @@ Builder.load_string(''' padding: '5dp', '5dp' on_release: textinput_generic_password.password = False if textinput_generic_password.password else True + Widget: + size_hint: 1, 1 + + +<PincodeDialog@Popup> + id: popup + title: 'Electrum' + message: '' + basename:'' + BoxLayout: + size_hint: 1, 1 + orientation: 'vertical' + Widget: + size_hint: 1, 0.05 + Label: + size_hint: 0.70, None + font_size: '20dp' + text: root.message + text_size: self.width, None + Widget: + size_hint: 1, 0.05 Label: id: label_pin - visible: not root.is_generic size_hint_y: 0.05 - opacity: 1 if self.visible else 0 - disabled: not self.visible font_size: '50dp' text: '*'*len(kb.password) + '-'*(6-len(kb.password)) size: self.texture_size @@ -74,7 +109,6 @@ Builder.load_string(''' size_hint: 1, 0.05 GridLayout: id: kb - disabled: root.is_generic size_hint: 1, None height: self.minimum_height update_amount: popup.update_password @@ -109,7 +143,7 @@ Builder.load_string(''' ''') -class PasswordDialog(Factory.Popup): +class AbstractPasswordDialog: def init(self, app: 'ElectrumWindow', *, check_password = None, @@ -117,7 +151,8 @@ class PasswordDialog(Factory.Popup): is_change: bool = False, is_password: bool = True, # whether this is for a generic password or for a numeric PIN has_password: bool = False, - message: str = ''): + message: str = '', + basename:str=''): self.app = app self.pw_check = check_password self.message = message @@ -129,18 +164,17 @@ class PasswordDialog(Factory.Popup): self.new_password = None self.title = 'Electrum' self.level = 1 if is_change and not has_password else 0 - self.is_generic = is_password + self.basename = basename self.update_screen() def update_screen(self): - self.ids.kb.password = '' - self.ids.textinput_generic_password.text = '' + self.clear_password() if self.level == 0 and self.message == '': - self.message = _('Enter your password') if self.is_generic else _('Enter your PIN') + self.message = self.enter_pw_message elif self.level == 1: - self.message = _('Enter new password') if self.is_generic else _('Enter new PIN') + self.message = self.enter_new_pw_message elif self.level == 2: - self.message = _('Confirm new password') if self.is_generic else _('Confirm new PIN') + self.message = self.confirm_new_pw_message def check_password(self, password): if self.level > 0: @@ -152,7 +186,7 @@ class PasswordDialog(Factory.Popup): return False def on_dismiss(self): - if self.level == 1 and not self.is_generic and self.on_success: + if self.level == 1 and self.allow_disable and self.on_success: self.on_success(self.pw, None) return False if not self.success: @@ -178,31 +212,63 @@ class PasswordDialog(Factory.Popup): kb.password = text + def do_check(self, pw): + if self.check_password(pw): + if self.is_change is False: + self.success = True + self.pw = pw + self.message = _('Please wait...') + self.dismiss() + elif self.level == 0: + self.level = 1 + self.pw = pw + self.update_screen() + elif self.level == 1: + self.level = 2 + self.new_password = pw + self.update_screen() + elif self.level == 2: + self.success = pw == self.new_password + self.dismiss() + else: + self.app.show_error(self.wrong_password_message) + self.clear_password() + + +class PasswordDialog(AbstractPasswordDialog, Factory.Popup): + enter_pw_message = _('Enter your password') + enter_new_pw_message = _('Enter new password') + confirm_new_pw_message = _('Confirm new password') + wrong_password_message = _('Wrong password') + allow_disable = False + + def clear_password(self): + self.ids.textinput_generic_password.text = '' + def on_password(self, pw: str): # if setting new generic password, enforce min length - if self.is_generic and self.level > 0: + if self.level > 0: if len(pw) < 6: self.app.show_error(_('Password is too short (min {} characters)').format(6)) return - # PIN codes are exactly 6 chars; generic pw can be any (don't enforce minimum on existing) - if len(pw) >= 6 or self.is_generic: - if self.check_password(pw): - if self.is_change is False: - self.success = True - self.pw = pw - self.message = _('Please wait...') - self.dismiss() - elif self.level == 0: - self.level = 1 - self.pw = pw - self.update_screen() - elif self.level == 1: - self.level = 2 - self.new_password = pw - self.update_screen() - elif self.level == 2: - self.success = pw == self.new_password - self.dismiss() - else: - self.app.show_error(_('Wrong PIN')) - self.ids.kb.password = '' + # don't enforce minimum length on existing + self.do_check(pw) + + def select_file(self): + self.app.wallets_dialog() + + +class PincodeDialog(AbstractPasswordDialog, Factory.Popup): + enter_pw_message = _('Enter your PIN') + enter_new_pw_message = _('Enter new PIN') + confirm_new_pw_message = _('Confirm new PIN') + wrong_password_message = _('Wrong PIN') + allow_disable = True + + def clear_password(self): + self.ids.kb.password = '' + + def on_password(self, pw: str): + # PIN codes are exactly 6 chars + if len(pw) >= 6: + self.do_check(pw) diff --git a/electrum/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py @@ -16,11 +16,11 @@ Builder.load_string(''' <WalletDialog@Popup>: title: _('Wallets') id: popup - path: os.path.dirname(app.get_wallet_path()) + path: '' BoxLayout: orientation: 'vertical' padding: '10dp' - FileChooserListView: + FileChooserIconView: id: wallet_selector dirselect: False filter_dirs: True