URI: 
       timprove kivy password dialog: - separate classes for pin code and password - add file selector to initial screen - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 47b6c2d87f6b8147dfc085c8e4ccf9e872dc31cf
   DIR parent 25626cf23b0134edbef497dab0eefa3cbe79cda8
  HTML 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:
         M electrum/gui/kivy/main.kv           |       6 +++---
         M electrum/gui/kivy/main_window.py    |      43 ++++++++++++++++++++-----------
         M electrum/gui/kivy/uix/dialogs/pass… |     148 ++++++++++++++++++++++---------
         M electrum/gui/kivy/uix/dialogs/wall… |       4 ++--
       
       4 files changed, 140 insertions(+), 61 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv
       t@@ -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
       t@@ -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')
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -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')
       t@@ -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
       t@@ -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)
       t@@ -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)
       t@@ -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
       t@@ -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()
        
       t@@ -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,)))
        
       t@@ -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)
       t@@ -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':
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py
       t@@ -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:
       t@@ -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'
       t@@ -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'
       t@@ -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
       t@@ -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
       t@@ -109,7 +143,7 @@ Builder.load_string('''
        ''')
        
        
       -class PasswordDialog(Factory.Popup):
       +class AbstractPasswordDialog:
        
            def init(self, app: 'ElectrumWindow', *,
                     check_password = None,
       t@@ -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
       t@@ -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:
       t@@ -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:
       t@@ -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)
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/wallets.py b/electrum/gui/kivy/uix/dialogs/wallets.py
       t@@ -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