Global hotkey, iconifying and application activation, help text.
This commit is contained in:
		@@ -11,6 +11,7 @@ dependencies {
 | 
			
		||||
    implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
 | 
			
		||||
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
 | 
			
		||||
    implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0'
 | 
			
		||||
    implementation group: 'com.github.tulskiy', name: 'jkeymaster', version: '1.2'
 | 
			
		||||
 | 
			
		||||
    compile project( ':masterpassword-model' )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,10 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Platform;
 | 
			
		||||
import com.lyndir.masterpassword.gui.util.Res;
 | 
			
		||||
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
 | 
			
		||||
import com.tulskiy.keymaster.common.Provider;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.event.*;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -18,9 +22,18 @@ public class GUI {
 | 
			
		||||
    public GUI() {
 | 
			
		||||
        Platform.get().installAppForegroundHandler( this::open );
 | 
			
		||||
        Platform.get().installAppReopenHandler( this::open );
 | 
			
		||||
 | 
			
		||||
        KeyStroke keyStroke = KeyStroke.getKeyStroke( KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK | InputEvent.META_DOWN_MASK );
 | 
			
		||||
        Provider.getCurrentProvider( true ).register( keyStroke, hotKey -> open() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void open() {
 | 
			
		||||
        Res.ui( () -> frame.setVisible( true ) );
 | 
			
		||||
        Res.ui( () -> {
 | 
			
		||||
            frame.setAlwaysOnTop( true );
 | 
			
		||||
            frame.setVisible( true );
 | 
			
		||||
            frame.setExtendedState( Frame.NORMAL );
 | 
			
		||||
            frame.setAlwaysOnTop( false );
 | 
			
		||||
            Platform.get().requestForeground();
 | 
			
		||||
        } );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,12 @@
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.gui.util;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.event.ActionEvent;
 | 
			
		||||
import java.awt.event.ActionListener;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URISyntaxException;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
@@ -30,9 +32,9 @@ import javax.annotation.Nullable;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.border.Border;
 | 
			
		||||
import javax.swing.border.CompoundBorder;
 | 
			
		||||
import javax.swing.event.DocumentEvent;
 | 
			
		||||
import javax.swing.event.DocumentListener;
 | 
			
		||||
import javax.swing.event.*;
 | 
			
		||||
import javax.swing.text.DefaultFormatterFactory;
 | 
			
		||||
import org.jetbrains.annotations.NonNls;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -41,6 +43,8 @@ import javax.swing.text.DefaultFormatterFactory;
 | 
			
		||||
@SuppressWarnings({ "SerializableStoresNonSerializable", "serial" })
 | 
			
		||||
public abstract class Components {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = Logger.get( Components.class );
 | 
			
		||||
 | 
			
		||||
    public static final float TEXT_SIZE_HEADING = 19f;
 | 
			
		||||
    public static final float TEXT_SIZE_CONTROL = 13f;
 | 
			
		||||
    public static final int   SIZE_MARGIN       = 12;
 | 
			
		||||
@@ -100,11 +104,11 @@ public abstract class Components {
 | 
			
		||||
        showDialog( dialog );
 | 
			
		||||
 | 
			
		||||
        Object selectedValue = pane.getValue();
 | 
			
		||||
        if(selectedValue == null)
 | 
			
		||||
        if (selectedValue == null)
 | 
			
		||||
            return JOptionPane.CLOSED_OPTION;
 | 
			
		||||
 | 
			
		||||
        Object[] options = pane.getOptions();
 | 
			
		||||
        if(options == null)
 | 
			
		||||
        if (options == null)
 | 
			
		||||
            return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
 | 
			
		||||
 | 
			
		||||
        int option = Arrays.binarySearch( options, selectedValue );
 | 
			
		||||
@@ -337,7 +341,7 @@ public abstract class Components {
 | 
			
		||||
                        BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
 | 
			
		||||
                DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory();
 | 
			
		||||
                if (model instanceof UnsignedIntegerModel)
 | 
			
		||||
                    formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel)model).getFormatter() );
 | 
			
		||||
                    formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel) model).getFormatter() );
 | 
			
		||||
                ((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory );
 | 
			
		||||
                ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
 | 
			
		||||
                setAlignmentX( LEFT_ALIGNMENT );
 | 
			
		||||
@@ -474,6 +478,24 @@ public abstract class Components {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static JEditorPane linkLabel(@NonNls final String html) {
 | 
			
		||||
        return new JEditorPane( "text/html", "<html><body style='width:640'>" + html ) {
 | 
			
		||||
            {
 | 
			
		||||
                setOpaque( false );
 | 
			
		||||
                setEditable( false );
 | 
			
		||||
                addHyperlinkListener( event -> {
 | 
			
		||||
                    if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
 | 
			
		||||
                        try {
 | 
			
		||||
                            Platform.get().open( event.getURL().toURI() );
 | 
			
		||||
                        }
 | 
			
		||||
                        catch (final URISyntaxException e) {
 | 
			
		||||
                            logger.err( e, "After triggering hyperlink: %s", event );
 | 
			
		||||
                        }
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class GradientPanel extends JPanel {
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
 
 | 
			
		||||
@@ -138,6 +138,10 @@ public abstract class Res {
 | 
			
		||||
            return icon( "media/icon_import.png" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Icon help() {
 | 
			
		||||
            return icon( "media/icon_help.png" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Icon export() {
 | 
			
		||||
            return icon( "media/icon_export.png" );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,9 @@ package com.lyndir.masterpassword.gui.util.platform;
 | 
			
		||||
import com.apple.eawt.*;
 | 
			
		||||
import com.apple.eio.FileManager;
 | 
			
		||||
import com.google.common.base.Preconditions;
 | 
			
		||||
import com.google.common.base.Throwables;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -13,7 +13,8 @@ import java.io.FileNotFoundException;
 | 
			
		||||
 */
 | 
			
		||||
public class ApplePlatform implements IPlatform {
 | 
			
		||||
 | 
			
		||||
    static Application application = Preconditions.checkNotNull(
 | 
			
		||||
    private static final Logger      logger      = Logger.get( ApplePlatform.class );
 | 
			
		||||
    private static final Application application = Preconditions.checkNotNull(
 | 
			
		||||
            Application.getApplication(), "Not an Apple Java application." );
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -37,12 +38,31 @@ public class ApplePlatform implements IPlatform {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean requestForeground() {
 | 
			
		||||
        application.requestForeground( true );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean show(final File file) {
 | 
			
		||||
        try {
 | 
			
		||||
            return FileManager.revealInFinder( file );
 | 
			
		||||
        }
 | 
			
		||||
        catch (final FileNotFoundException ignored) {
 | 
			
		||||
        catch (final FileNotFoundException e) {
 | 
			
		||||
            logger.err( e, "While showing: %s", file );
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean open(final URI url) {
 | 
			
		||||
        try {
 | 
			
		||||
            FileManager.openURL( url.toString() );
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        catch (final IOException e) {
 | 
			
		||||
            logger.err( e, "While opening: %s", url );
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.util.platform;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -18,8 +19,18 @@ public class BasePlatform implements IPlatform {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean requestForeground() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean show(final File file) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean open(final URI url) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.util.platform;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -12,5 +14,9 @@ public interface IPlatform {
 | 
			
		||||
 | 
			
		||||
    boolean installAppReopenHandler(Runnable handler);
 | 
			
		||||
 | 
			
		||||
    boolean requestForeground();
 | 
			
		||||
 | 
			
		||||
    boolean show(File file);
 | 
			
		||||
 | 
			
		||||
    boolean open(URI url);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
package com.lyndir.masterpassword.gui.util.platform;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.desktop.*;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -11,9 +14,12 @@ import java.io.File;
 | 
			
		||||
@SuppressWarnings("Since15")
 | 
			
		||||
public class JDK9Platform implements IPlatform {
 | 
			
		||||
 | 
			
		||||
    private static final Logger  logger  = Logger.get( JDK9Platform.class );
 | 
			
		||||
    private static final Desktop desktop = Desktop.getDesktop();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppForegroundHandler(final Runnable handler) {
 | 
			
		||||
        Desktop.getDesktop().addAppEventListener( new AppForegroundListener() {
 | 
			
		||||
        desktop.addAppEventListener( new AppForegroundListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void appRaisedToForeground(final AppForegroundEvent e) {
 | 
			
		||||
                handler.run();
 | 
			
		||||
@@ -28,7 +34,13 @@ public class JDK9Platform implements IPlatform {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean installAppReopenHandler(final Runnable handler) {
 | 
			
		||||
        Desktop.getDesktop().addAppEventListener( (AppReopenedListener) e -> handler.run() );
 | 
			
		||||
        desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean requestForeground() {
 | 
			
		||||
        desktop.requestForeground( true );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +49,19 @@ public class JDK9Platform implements IPlatform {
 | 
			
		||||
        if (!file.exists())
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        Desktop.getDesktop().browseFileDirectory( file );
 | 
			
		||||
        desktop.browseFileDirectory( file );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean open(final URI url) {
 | 
			
		||||
        try {
 | 
			
		||||
            desktop.browse( url );
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        catch (final IOException e) {
 | 
			
		||||
            logger.err( e, "While opening: %s", url );
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
                                                            "Add a new user to Master Password." );
 | 
			
		||||
    private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(),
 | 
			
		||||
                                                            "Import a user from a backup file into Master Password." );
 | 
			
		||||
    private final JButton helpButton   = Components.button( Res.icons().help(), event -> showHelp(),
 | 
			
		||||
                                                            "Show information on how to use Master Password." );
 | 
			
		||||
 | 
			
		||||
    private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
 | 
			
		||||
    private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
 | 
			
		||||
@@ -128,7 +130,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void addUser() {
 | 
			
		||||
        JTextField nameField = Components.textField( "Robert Lee Mitchell", null );
 | 
			
		||||
        JTextField nameField      = Components.textField( "Robert Lee Mitchell", null );
 | 
			
		||||
        JCheckBox  incognitoField = Components.checkBox( "<html>Incognito <em>(Do not save this user to disk)</em></html>" );
 | 
			
		||||
        if (JOptionPane.OK_OPTION != Components.showDialog( this, "Add User", new JOptionPane( Components.panel(
 | 
			
		||||
                BoxLayout.PAGE_AXIS,
 | 
			
		||||
@@ -208,7 +210,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
                    this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
 | 
			
		||||
                    "Import Failed", JOptionPane.ERROR_MESSAGE );
 | 
			
		||||
        }
 | 
			
		||||
        catch (MPMarshalException e) {
 | 
			
		||||
        catch (final MPMarshalException e) {
 | 
			
		||||
            logger.err( e, "While parsing user import file." );
 | 
			
		||||
            JOptionPane.showMessageDialog(
 | 
			
		||||
                    this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
 | 
			
		||||
@@ -216,6 +218,31 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void showHelp() {
 | 
			
		||||
        JOptionPane.showMessageDialog( this, Components.linkLabel( strf(
 | 
			
		||||
                "<h1>Master Password</h1>"
 | 
			
		||||
                + "<p>The primary goal of this application is to provide a reliable security solution that also "
 | 
			
		||||
                + "makes you independent from your computer.  If you lose access to this computer or your data, "
 | 
			
		||||
                + "the application can regenerate all your secrets from scratch on any new device.</p>"
 | 
			
		||||
                + "<h2>Opening Master Password</h2>"
 | 
			
		||||
                + "<p>To use Master Password, simply open the application on your computer. "
 | 
			
		||||
                + "Once running, you can bring up the user interface at any time by pressing the keys "
 | 
			
		||||
                + "<strong><code>%s + %s + p</code></strong>."
 | 
			
		||||
                + "<h2>Persistence</h2>"
 | 
			
		||||
                + "<p>Though at the core, Master Password does not require the use of any form of data "
 | 
			
		||||
                + "storage, the application does remember the names of the sites you've used in the past to "
 | 
			
		||||
                + "make it easier for you to use them again in the future.  All user information is saved in "
 | 
			
		||||
                + "files on your computer at the following location:<br><pre>%s</pre></p>"
 | 
			
		||||
                + "<p>You can read, modify, backup or place new files in this location as you see fit. "
 | 
			
		||||
                + "Some people even configure this location to be synced between their different computers "
 | 
			
		||||
                + "using services such as those provided by SpiderOak or Dropbox.</p>"
 | 
			
		||||
                + "<hr><p><a href='https://masterpassword.app'>https://masterpassword.app</a> — by Maarten Billemont</p>",
 | 
			
		||||
                KeyEvent.getKeyText( KeyEvent.VK_CONTROL ),
 | 
			
		||||
                KeyEvent.getKeyText( KeyEvent.VK_META ),
 | 
			
		||||
                MPFileUserManager.get().getPath().getAbsolutePath() ) ),
 | 
			
		||||
                                       "About Master Password", JOptionPane.INFORMATION_MESSAGE );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private enum ContentMode {
 | 
			
		||||
        NO_USER,
 | 
			
		||||
        AUTHENTICATE,
 | 
			
		||||
@@ -239,6 +266,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
 | 
			
		||||
            userToolbar.add( addButton );
 | 
			
		||||
            userToolbar.add( importButton );
 | 
			
		||||
            userToolbar.add( Box.createGlue() );
 | 
			
		||||
            userToolbar.add( helpButton );
 | 
			
		||||
 | 
			
		||||
            add( Box.createGlue() );
 | 
			
		||||
            add( Components.heading( "Select a user to proceed." ) );
 | 
			
		||||
@@ -275,6 +304,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
            userToolbar.add( exportButton );
 | 
			
		||||
            userToolbar.add( deleteButton );
 | 
			
		||||
            userToolbar.add( resetButton );
 | 
			
		||||
            userToolbar.add( Box.createGlue() );
 | 
			
		||||
            userToolbar.add( helpButton );
 | 
			
		||||
 | 
			
		||||
            add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
 | 
			
		||||
            add( Components.strut() );
 | 
			
		||||
@@ -461,6 +492,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
            userToolbar.add( addButton );
 | 
			
		||||
            userToolbar.add( userButton );
 | 
			
		||||
            userToolbar.add( logoutButton );
 | 
			
		||||
            userToolbar.add( Box.createGlue() );
 | 
			
		||||
            userToolbar.add( helpButton );
 | 
			
		||||
 | 
			
		||||
            siteToolbar.add( settingsButton );
 | 
			
		||||
            siteToolbar.add( questionsButton );
 | 
			
		||||
@@ -615,8 +648,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
 | 
			
		||||
                Res.ui( () -> {
 | 
			
		||||
                    Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
 | 
			
		||||
                    if (window != null)
 | 
			
		||||
                        window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) );
 | 
			
		||||
                    if (window instanceof Frame) {
 | 
			
		||||
                        ((Frame) window).setExtendedState( Frame.ICONIFIED );
 | 
			
		||||
                        window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_DEACTIVATED ) );
 | 
			
		||||
                        window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_ICONIFIED ) );
 | 
			
		||||
                        window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_LOST_FOCUS ) );
 | 
			
		||||
                        window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSED ) );
 | 
			
		||||
                    }
 | 
			
		||||
                    //                                            window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_ICONIFIED ) );
 | 
			
		||||
                } );
 | 
			
		||||
            } );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.1 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.7 KiB  | 
		Reference in New Issue
	
	Block a user