Merge pull request #132 from informatik-ag-ngl/f/login_dialog
JavaFX LoginDialog
This commit is contained in:
		@@ -1,7 +1,6 @@
 | 
			
		||||
package envoy.client.data;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
 | 
			
		||||
@@ -110,11 +109,5 @@ public class ClientConfig extends Config {
 | 
			
		||||
	 *         the registration option
 | 
			
		||||
	 * @since Envoy Client v0.3-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public LoginCredentials getLoginCredentials() {
 | 
			
		||||
		try {
 | 
			
		||||
			return new LoginCredentials(getUser(), getPassword(), false);
 | 
			
		||||
		} catch (NoSuchAlgorithmException e) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false); }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
package envoy.client.event;
 | 
			
		||||
 | 
			
		||||
import envoy.event.Event;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This {@link Event} indicates that a handshake was completed successfully.
 | 
			
		||||
 *
 | 
			
		||||
 * Project: <strong>envoy-client</strong><br>
 | 
			
		||||
 * File: <strong>HandshakeSuccessfulEvent.java</strong><br>
 | 
			
		||||
 * Created: <strong>8 Feb 2020</strong><br>
 | 
			
		||||
 *
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
 * @since Envoy Client v0.3-alpha
 | 
			
		||||
 */
 | 
			
		||||
public class HandshakeSuccessfulEvent extends Event.Valueless {
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 0L;
 | 
			
		||||
}
 | 
			
		||||
@@ -46,9 +46,10 @@ public class Client implements Closeable {
 | 
			
		||||
	private volatile Set<? extends Contact>	contacts;
 | 
			
		||||
	private volatile boolean				rejected;
 | 
			
		||||
 | 
			
		||||
	// Configuration and logging
 | 
			
		||||
	private static final ClientConfig	config	= ClientConfig.getInstance();
 | 
			
		||||
	private static final Logger			logger	= EnvoyLog.getLogger(Client.class);
 | 
			
		||||
	// Configuration, logging and event management
 | 
			
		||||
	private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
			
		||||
	private static final Logger			logger		= EnvoyLog.getLogger(Client.class);
 | 
			
		||||
	private static final EventBus		eventBus	= EventBus.getInstance();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Enters the online mode by acquiring a user ID from the server. As a
 | 
			
		||||
@@ -80,7 +81,7 @@ public class Client implements Closeable {
 | 
			
		||||
		// Register user creation processor, contact list processor and message cache
 | 
			
		||||
		receiver.registerProcessor(User.class, sender -> { this.sender = sender; contacts = sender.getContacts(); });
 | 
			
		||||
		receiver.registerProcessor(Message.class, receivedMessageCache);
 | 
			
		||||
		receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; EventBus.getInstance().dispatch(evt); });
 | 
			
		||||
		receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; eventBus.dispatch(evt); });
 | 
			
		||||
 | 
			
		||||
		rejected = false;
 | 
			
		||||
 | 
			
		||||
@@ -106,11 +107,12 @@ public class Client implements Closeable {
 | 
			
		||||
			Thread.sleep(500);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logger.info("Handshake completed.");
 | 
			
		||||
		online = true;
 | 
			
		||||
 | 
			
		||||
		// Remove user creation processor
 | 
			
		||||
		receiver.removeAllProcessors();
 | 
			
		||||
 | 
			
		||||
		logger.info("Handshake completed.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -146,20 +148,19 @@ public class Client implements Closeable {
 | 
			
		||||
		receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
 | 
			
		||||
 | 
			
		||||
		// Process name changes
 | 
			
		||||
		receiver.registerProcessor(NameChangeEvent.class, evt -> { localDB.replaceContactName(evt); EventBus.getInstance().dispatch(evt); });
 | 
			
		||||
		receiver.registerProcessor(NameChangeEvent.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
 | 
			
		||||
 | 
			
		||||
		// Process contact searches
 | 
			
		||||
		receiver.registerProcessor(ContactSearchResult.class, EventBus.getInstance()::dispatch);
 | 
			
		||||
		receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch);
 | 
			
		||||
 | 
			
		||||
		receiver.registerProcessor(Contact.class,
 | 
			
		||||
				contacts -> EventBus.getInstance()
 | 
			
		||||
					.dispatch(new ContactOperationEvent(contacts.getContacts().iterator().next(), ElementOperation.ADD)));
 | 
			
		||||
				contacts -> eventBus.dispatch(new ContactOperationEvent(contacts.getContacts().iterator().next(), ElementOperation.ADD)));
 | 
			
		||||
 | 
			
		||||
		// Process group size changes
 | 
			
		||||
		receiver.registerProcessor(GroupResizeEvent.class, evt -> { localDB.updateGroup(evt); EventBus.getInstance().dispatch(evt); });
 | 
			
		||||
		receiver.registerProcessor(GroupResizeEvent.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
 | 
			
		||||
 | 
			
		||||
		// Send event
 | 
			
		||||
		EventBus.getInstance().register(SendEvent.class, evt -> {
 | 
			
		||||
		eventBus.register(SendEvent.class, evt -> {
 | 
			
		||||
			try {
 | 
			
		||||
				sendEvent(evt.get());
 | 
			
		||||
			} catch (IOException e) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								src/main/java/envoy/client/ui/LoginDialog.fxml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/main/java/envoy/client/ui/LoginDialog.fxml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
 | 
			
		||||
<?import javafx.scene.control.Button?>
 | 
			
		||||
<?import javafx.scene.control.CheckBox?>
 | 
			
		||||
<?import javafx.scene.control.DialogPane?>
 | 
			
		||||
<?import javafx.scene.control.Label?>
 | 
			
		||||
<?import javafx.scene.control.PasswordField?>
 | 
			
		||||
<?import javafx.scene.control.TextField?>
 | 
			
		||||
<?import javafx.scene.layout.ColumnConstraints?>
 | 
			
		||||
<?import javafx.scene.layout.GridPane?>
 | 
			
		||||
<?import javafx.scene.layout.HBox?>
 | 
			
		||||
<?import javafx.scene.layout.RowConstraints?>
 | 
			
		||||
<?import javafx.scene.layout.VBox?>
 | 
			
		||||
<?import javafx.scene.text.Font?>
 | 
			
		||||
 | 
			
		||||
<DialogPane prefHeight="201.0" prefWidth="525.0"
 | 
			
		||||
	xmlns="http://javafx.com/javafx/11.0.1"
 | 
			
		||||
	xmlns:fx="http://javafx.com/fxml/1">
 | 
			
		||||
	<content>
 | 
			
		||||
		<VBox maxHeight="-Infinity" maxWidth="-Infinity"
 | 
			
		||||
			minHeight="-Infinity" minWidth="-Infinity" prefHeight="217.0"
 | 
			
		||||
			prefWidth="545.0">
 | 
			
		||||
			<children>
 | 
			
		||||
				<Label text="User Login">
 | 
			
		||||
					<font>
 | 
			
		||||
						<Font size="26.0" />
 | 
			
		||||
					</font>
 | 
			
		||||
				</Label>
 | 
			
		||||
				<GridPane>
 | 
			
		||||
					<columnConstraints>
 | 
			
		||||
						<ColumnConstraints hgrow="SOMETIMES"
 | 
			
		||||
							minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
 | 
			
		||||
						<ColumnConstraints hgrow="SOMETIMES"
 | 
			
		||||
							minWidth="10.0" prefWidth="100.0" />
 | 
			
		||||
					</columnConstraints>
 | 
			
		||||
					<rowConstraints>
 | 
			
		||||
						<RowConstraints minHeight="10.0" prefHeight="30.0"
 | 
			
		||||
							vgrow="SOMETIMES" />
 | 
			
		||||
						<RowConstraints minHeight="10.0" prefHeight="30.0"
 | 
			
		||||
							vgrow="SOMETIMES" />
 | 
			
		||||
						<RowConstraints minHeight="10.0" prefHeight="30.0"
 | 
			
		||||
							vgrow="SOMETIMES" />
 | 
			
		||||
					</rowConstraints>
 | 
			
		||||
					<children>
 | 
			
		||||
						<Label text="User Name:" />
 | 
			
		||||
						<Label text="Password" GridPane.rowIndex="1" />
 | 
			
		||||
						<Label fx:id="repeatPasswordLabel" text="Repeat Password:"
 | 
			
		||||
							visible="false" GridPane.rowIndex="2" />
 | 
			
		||||
						<TextField fx:id="userTextField"
 | 
			
		||||
							GridPane.columnIndex="1" />
 | 
			
		||||
						<PasswordField fx:id="passwordField"
 | 
			
		||||
							GridPane.columnIndex="1" GridPane.rowIndex="1" />
 | 
			
		||||
						<PasswordField fx:id="repeatPasswordField"
 | 
			
		||||
							visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2" />
 | 
			
		||||
					</children>
 | 
			
		||||
				</GridPane>
 | 
			
		||||
				<CheckBox fx:id="registerCheckBox" mnemonicParsing="false"
 | 
			
		||||
					onAction="#registerCheckboxChanged" text="Register" />
 | 
			
		||||
			</children>
 | 
			
		||||
		</VBox>
 | 
			
		||||
	</content>
 | 
			
		||||
</DialogPane>
 | 
			
		||||
							
								
								
									
										162
									
								
								src/main/java/envoy/client/ui/LoginDialog.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/main/java/envoy/client/ui/LoginDialog.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
package envoy.client.ui;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
import javax.naming.TimeLimitExceededException;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.Cache;
 | 
			
		||||
import envoy.client.data.ClientConfig;
 | 
			
		||||
import envoy.client.data.LocalDB;
 | 
			
		||||
import envoy.client.net.Client;
 | 
			
		||||
import envoy.data.LoginCredentials;
 | 
			
		||||
import envoy.data.Message;
 | 
			
		||||
import envoy.data.User;
 | 
			
		||||
import envoy.event.EventBus;
 | 
			
		||||
import envoy.event.HandshakeRejectionEvent;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
import javafx.application.Platform;
 | 
			
		||||
import javafx.event.ActionEvent;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.fxml.FXMLLoader;
 | 
			
		||||
import javafx.scene.control.*;
 | 
			
		||||
import javafx.scene.control.Alert.AlertType;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>envoy-client</strong><br>
 | 
			
		||||
 * File: <strong>LoginDialog.java</strong><br>
 | 
			
		||||
 * Created: <strong>03.04.2020</strong><br>
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Kai S. K. Engelbart
 | 
			
		||||
 * @since Envoy Client v0.1-beta
 | 
			
		||||
 */
 | 
			
		||||
public final class LoginDialog extends Dialog<Void> {
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private TextField userTextField;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private PasswordField passwordField;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private PasswordField repeatPasswordField;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private Label repeatPasswordLabel;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private CheckBox registerCheckBox;
 | 
			
		||||
 | 
			
		||||
	private final Client			client;
 | 
			
		||||
	private final LocalDB			localDB;
 | 
			
		||||
	private final Cache<Message>	receivedMessageCache;
 | 
			
		||||
 | 
			
		||||
	private static final Logger			logger		= EnvoyLog.getLogger(LoginDialog.class);
 | 
			
		||||
	private static final EventBus		eventBus	= EventBus.getInstance();
 | 
			
		||||
	private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Loads the login dialog using the FXML file {@code LoginDialog.fxml}.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param client               the client used to perform the handshake
 | 
			
		||||
	 * @param localDB              the local database used for offline login
 | 
			
		||||
	 * @param receivedMessageCache the cache storing messages received during the
 | 
			
		||||
	 *                             handshake
 | 
			
		||||
	 * @throws IOException if an exception occurs during loading
 | 
			
		||||
	 * @since Envoy Client v0.1-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public LoginDialog(Client client, LocalDB localDB, Cache<Message> receivedMessageCache) throws IOException {
 | 
			
		||||
		this.client					= client;
 | 
			
		||||
		this.localDB				= localDB;
 | 
			
		||||
		this.receivedMessageCache	= receivedMessageCache;
 | 
			
		||||
 | 
			
		||||
		final var loader = new FXMLLoader(getClass().getResource("LoginDialog.fxml"));
 | 
			
		||||
		loader.setController(this);
 | 
			
		||||
		final var dialogPane = loader.<DialogPane>load();
 | 
			
		||||
 | 
			
		||||
		// Configure dialog buttons
 | 
			
		||||
		dialogPane.getButtonTypes().addAll(ButtonType.CLOSE, ButtonType.OK);
 | 
			
		||||
 | 
			
		||||
		// Close button
 | 
			
		||||
		dialogPane.lookupButton(ButtonType.CLOSE).addEventHandler(ActionEvent.ACTION, e -> abortLogin());
 | 
			
		||||
 | 
			
		||||
		// Login button
 | 
			
		||||
		final var loginButton = (Button) dialogPane.lookupButton(ButtonType.OK);
 | 
			
		||||
		loginButton.setText("Login");
 | 
			
		||||
		loginButton.addEventFilter(ActionEvent.ACTION, e -> {
 | 
			
		||||
			e.consume();
 | 
			
		||||
 | 
			
		||||
			// Prevent registration with unequal passwords
 | 
			
		||||
			if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
 | 
			
		||||
				clearPasswordFields();
 | 
			
		||||
				new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
 | 
			
		||||
			} else
 | 
			
		||||
				performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected()));
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Perform automatic login if configured
 | 
			
		||||
		setOnShown(e -> { if (config.hasLoginCredentials()) performHandshake(config.getLoginCredentials()); });
 | 
			
		||||
 | 
			
		||||
		setDialogPane(dialogPane);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private void initialize() {
 | 
			
		||||
 | 
			
		||||
		// Show an alert after an unsuccessful handshake
 | 
			
		||||
		eventBus.register(HandshakeRejectionEvent.class,
 | 
			
		||||
				e -> Platform.runLater(() -> { clearPasswordFields(); new Alert(AlertType.ERROR, e.get()).showAndWait(); }));
 | 
			
		||||
 | 
			
		||||
		// Set initial cursor
 | 
			
		||||
		userTextField.requestFocus();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private void registerCheckboxChanged() {
 | 
			
		||||
 | 
			
		||||
		// Make repeat password field and label visible / invisible
 | 
			
		||||
		repeatPasswordField.setVisible(registerCheckBox.isSelected());
 | 
			
		||||
		repeatPasswordLabel.setVisible(registerCheckBox.isSelected());
 | 
			
		||||
 | 
			
		||||
		clearPasswordFields();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void abortLogin() {
 | 
			
		||||
		logger.info("The login process has been cancelled. Exiting...");
 | 
			
		||||
		System.exit(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void performHandshake(LoginCredentials credentials) {
 | 
			
		||||
		try {
 | 
			
		||||
			client.performHandshake(credentials, receivedMessageCache);
 | 
			
		||||
			if (client.isOnline()) {
 | 
			
		||||
				client.initReceiver(localDB, receivedMessageCache);
 | 
			
		||||
				Platform.runLater(this::hide);
 | 
			
		||||
			}
 | 
			
		||||
		} catch (IOException | InterruptedException | TimeLimitExceededException e) {
 | 
			
		||||
			logger.warning("Could not connect to server. Trying offline mode...");
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			try {
 | 
			
		||||
				// Try entering offline mode
 | 
			
		||||
				localDB.loadUsers();
 | 
			
		||||
				User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier());
 | 
			
		||||
				if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
 | 
			
		||||
				client.setSender(clientUser);
 | 
			
		||||
				Platform.runLater(() -> {
 | 
			
		||||
					new Alert(AlertType.WARNING, "A connection to the server could not be established. Starting in offline mode.\n" + e)
 | 
			
		||||
						.showAndWait();
 | 
			
		||||
					hide();
 | 
			
		||||
				});
 | 
			
		||||
			} catch (Exception e1) {
 | 
			
		||||
				Platform.runLater(() -> new Alert(AlertType.ERROR, "Client error: " + e.toString()).showAndWait());
 | 
			
		||||
				System.exit(1);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void clearPasswordFields() {
 | 
			
		||||
		passwordField.clear();
 | 
			
		||||
		repeatPasswordField.clear();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -7,12 +7,9 @@ import java.util.Properties;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JOptionPane;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.net.Client;
 | 
			
		||||
import envoy.client.net.WriteProxy;
 | 
			
		||||
import envoy.client.ui.container.LoginDialog;
 | 
			
		||||
import envoy.data.Message;
 | 
			
		||||
import envoy.data.User;
 | 
			
		||||
import envoy.data.User.UserStatus;
 | 
			
		||||
@@ -21,6 +18,8 @@ import envoy.util.EnvoyLog;
 | 
			
		||||
import javafx.application.Application;
 | 
			
		||||
import javafx.fxml.FXMLLoader;
 | 
			
		||||
import javafx.scene.Scene;
 | 
			
		||||
import javafx.scene.control.Alert;
 | 
			
		||||
import javafx.scene.control.Alert.AlertType;
 | 
			
		||||
import javafx.scene.image.Image;
 | 
			
		||||
import javafx.scene.layout.GridPane;
 | 
			
		||||
import javafx.stage.Stage;
 | 
			
		||||
@@ -47,7 +46,7 @@ public final class Startup extends Application {
 | 
			
		||||
	 * {@inheritDoc}
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public void init() throws Exception {
 | 
			
		||||
	public void start(Stage stage) throws Exception {
 | 
			
		||||
		try {
 | 
			
		||||
			// Load the configuration from client.properties first
 | 
			
		||||
			Properties properties = new Properties();
 | 
			
		||||
@@ -61,7 +60,7 @@ public final class Startup extends Application {
 | 
			
		||||
			// Check if all mandatory configuration values have been initialized
 | 
			
		||||
			if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			JOptionPane.showMessageDialog(null, "Error loading configuration values:\n" + e, "Configuration error", JOptionPane.ERROR_MESSAGE);
 | 
			
		||||
			new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			System.exit(1);
 | 
			
		||||
		}
 | 
			
		||||
@@ -75,15 +74,12 @@ public final class Startup extends Application {
 | 
			
		||||
		// Initialize the local database
 | 
			
		||||
		if (config.isIgnoreLocalDB()) {
 | 
			
		||||
			localDB = new TransientLocalDB();
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"Ignoring local database.\nMessages will not be saved!",
 | 
			
		||||
					"Local database warning",
 | 
			
		||||
					JOptionPane.WARNING_MESSAGE);
 | 
			
		||||
			new Alert(AlertType.WARNING, "Ignoring local database.\nMessages will not be saved!").showAndWait();
 | 
			
		||||
		} else try {
 | 
			
		||||
			localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
 | 
			
		||||
		} catch (IOException e3) {
 | 
			
		||||
			logger.log(Level.SEVERE, "Could not initialize local database", e3);
 | 
			
		||||
			JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3, "Local database error", JOptionPane.ERROR_MESSAGE);
 | 
			
		||||
			new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e3).showAndWait();
 | 
			
		||||
			System.exit(1);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
@@ -93,7 +89,7 @@ public final class Startup extends Application {
 | 
			
		||||
		cache	= new Cache<>();
 | 
			
		||||
 | 
			
		||||
		// Try to connect to the server
 | 
			
		||||
		new LoginDialog(client, localDB, cache);
 | 
			
		||||
		new LoginDialog(client, localDB, cache).showAndWait();
 | 
			
		||||
 | 
			
		||||
		// Set client user in local database
 | 
			
		||||
		localDB.setUser(client.getSender());
 | 
			
		||||
@@ -106,10 +102,7 @@ public final class Startup extends Application {
 | 
			
		||||
			// The local database file has not yet been created, probably first login
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			JOptionPane.showMessageDialog(null,
 | 
			
		||||
					"Error while loading local database: " + e + "\nChats will not be stored locally.",
 | 
			
		||||
					"Local DB error",
 | 
			
		||||
					JOptionPane.WARNING_MESSAGE);
 | 
			
		||||
			new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Initialize write proxy
 | 
			
		||||
@@ -128,13 +121,6 @@ public final class Startup extends Application {
 | 
			
		||||
				.filter(u -> u instanceof User && u != localDB.getUser())
 | 
			
		||||
				.map(User.class::cast)
 | 
			
		||||
				.forEach(u -> u.setStatus(UserStatus.OFFLINE));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * {@inheritDoc}
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public void start(Stage stage) throws Exception {
 | 
			
		||||
 | 
			
		||||
		// Prepare stage and load ChatScene
 | 
			
		||||
		var	loader		= new FXMLLoader(getClass().getResource("ChatScene.fxml"));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,346 +0,0 @@
 | 
			
		||||
package envoy.client.ui.container;
 | 
			
		||||
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.awt.event.ItemEvent;
 | 
			
		||||
import java.awt.event.WindowAdapter;
 | 
			
		||||
import java.awt.event.WindowEvent;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
import javax.naming.TimeLimitExceededException;
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import javax.swing.border.EmptyBorder;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.event.HandshakeSuccessfulEvent;
 | 
			
		||||
import envoy.client.net.Client;
 | 
			
		||||
import envoy.client.ui.Theme;
 | 
			
		||||
import envoy.client.ui.primary.PrimaryButton;
 | 
			
		||||
import envoy.data.LoginCredentials;
 | 
			
		||||
import envoy.data.Message;
 | 
			
		||||
import envoy.data.User;
 | 
			
		||||
import envoy.event.EventBus;
 | 
			
		||||
import envoy.event.HandshakeRejectionEvent;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>envoy-client</strong><br>
 | 
			
		||||
 * File: <strong>LoginDialog.java</strong><br>
 | 
			
		||||
 * Created: <strong>01.01.2020</strong><br>
 | 
			
		||||
 *
 | 
			
		||||
 * @author Kai S. K. Engelbart
 | 
			
		||||
 * @author Maximilian Käfer
 | 
			
		||||
 * @since Envoy Client v0.3-alpha
 | 
			
		||||
 */
 | 
			
		||||
public class LoginDialog extends JDialog {
 | 
			
		||||
 | 
			
		||||
	private JPanel			contentPanel;
 | 
			
		||||
	private JTextField		textField;
 | 
			
		||||
	private JPasswordField	passwordField;
 | 
			
		||||
	private JPasswordField	repeatPasswordField;
 | 
			
		||||
 | 
			
		||||
	private JLabel	lblUserName;
 | 
			
		||||
	private JLabel	lblPassword;
 | 
			
		||||
	private JLabel	lblRepeatPassword;
 | 
			
		||||
	private JLabel	errorMessage;
 | 
			
		||||
 | 
			
		||||
	private GridBagConstraints	gbc_lblRepeatPassword;
 | 
			
		||||
	private GridBagConstraints	gbc_repeatPasswordField;
 | 
			
		||||
	private GridBagConstraints	gbc_errorMessage;
 | 
			
		||||
 | 
			
		||||
	private JPanel			buttonPane;
 | 
			
		||||
	private JTextPane		registerText;
 | 
			
		||||
	private JCheckBox		registerCheckBox;
 | 
			
		||||
	private PrimaryButton	okButton;
 | 
			
		||||
	private PrimaryButton	cancelButton;
 | 
			
		||||
 | 
			
		||||
	private LoginCredentials credentials;
 | 
			
		||||
 | 
			
		||||
	private final Client			client;
 | 
			
		||||
	private final LocalDB			localDB;
 | 
			
		||||
	private final Cache<Message>	receivedMessageCache;
 | 
			
		||||
 | 
			
		||||
	private static final ClientConfig	config				= ClientConfig.getInstance();
 | 
			
		||||
	private static final Logger			logger				= EnvoyLog.getLogger(LoginDialog.class);
 | 
			
		||||
	private static final long			serialVersionUID	= 0L;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Displays a dialog enabling the user to enter their user name and password.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param client               the client used to perform the handshake
 | 
			
		||||
	 * @param localDB              the local database in which data is persisted
 | 
			
		||||
	 * @param receivedMessageCache the cache that stored messages received during
 | 
			
		||||
	 *                             the handshake
 | 
			
		||||
	 * @since Envoy Client v0.3-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public LoginDialog(Client client, LocalDB localDB, Cache<Message> receivedMessageCache) {
 | 
			
		||||
		this.client					= client;
 | 
			
		||||
		this.localDB				= localDB;
 | 
			
		||||
		this.receivedMessageCache	= receivedMessageCache;
 | 
			
		||||
 | 
			
		||||
		// Prepare handshake
 | 
			
		||||
		localDB.loadIDGenerator();
 | 
			
		||||
 | 
			
		||||
		addWindowListener(new WindowAdapter() {
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public void windowClosing(WindowEvent e) { abortLogin(); }
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		initUi();
 | 
			
		||||
 | 
			
		||||
		okButton.addActionListener((evt) -> {
 | 
			
		||||
			try {
 | 
			
		||||
				if (registerCheckBox.isSelected()) {
 | 
			
		||||
					// Check password equality
 | 
			
		||||
					if (Arrays.equals(passwordField.getPassword(), repeatPasswordField.getPassword())) {
 | 
			
		||||
						credentials = new LoginCredentials(textField.getText(), passwordField.getPassword(), true);
 | 
			
		||||
						performHandshake();
 | 
			
		||||
					} else {
 | 
			
		||||
						JOptionPane.showMessageDialog(this, "The repeated password is not the original password!");
 | 
			
		||||
						clearPasswordFields();
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					credentials = new LoginCredentials(textField.getText(), passwordField.getPassword(), false);
 | 
			
		||||
					performHandshake();
 | 
			
		||||
				}
 | 
			
		||||
			} catch (NoSuchAlgorithmException e) {
 | 
			
		||||
				e.printStackTrace();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Listen to handshake rejections
 | 
			
		||||
		EventBus.getInstance()
 | 
			
		||||
			.register(HandshakeRejectionEvent.class,
 | 
			
		||||
					evt -> { clearPasswordFields(); errorMessage.setVisible(true); errorMessage.setText(evt.get()); });
 | 
			
		||||
 | 
			
		||||
		// Exit the application when the dialog is cancelled
 | 
			
		||||
		cancelButton.addActionListener(evt -> abortLogin());
 | 
			
		||||
 | 
			
		||||
		// Log in directly if configured
 | 
			
		||||
		if (config.hasLoginCredentials()) {
 | 
			
		||||
			credentials = config.getLoginCredentials();
 | 
			
		||||
			performHandshake();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		setVisible(true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void performHandshake() {
 | 
			
		||||
		try {
 | 
			
		||||
			client.performHandshake(credentials, receivedMessageCache);
 | 
			
		||||
			if (client.isOnline()) {
 | 
			
		||||
				client.initReceiver(localDB, receivedMessageCache);
 | 
			
		||||
				dispose();
 | 
			
		||||
			}
 | 
			
		||||
		} catch (IOException | InterruptedException | TimeLimitExceededException e) {
 | 
			
		||||
			logger.warning("Could not connect to server. Trying offline mode...");
 | 
			
		||||
			e.printStackTrace();
 | 
			
		||||
			try {
 | 
			
		||||
				// Try entering offline mode
 | 
			
		||||
				localDB.loadUsers();
 | 
			
		||||
				User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier());
 | 
			
		||||
				if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
 | 
			
		||||
				client.setSender(clientUser);
 | 
			
		||||
				JOptionPane.showMessageDialog(null,
 | 
			
		||||
						"A connection to the server could not be established. Starting in offline mode.\n" + e,
 | 
			
		||||
						"Connection error",
 | 
			
		||||
						JOptionPane.WARNING_MESSAGE);
 | 
			
		||||
				dispose();
 | 
			
		||||
			} catch (Exception e1) {
 | 
			
		||||
				JOptionPane.showMessageDialog(null, e1, "Client error", JOptionPane.ERROR_MESSAGE);
 | 
			
		||||
				System.exit(1);
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void initUi() {
 | 
			
		||||
		setSize(338, 123);
 | 
			
		||||
		setLocationRelativeTo(null);
 | 
			
		||||
		setResizable(false);
 | 
			
		||||
		getContentPane().setLayout(new BorderLayout());
 | 
			
		||||
		contentPanel = new JPanel();
 | 
			
		||||
		contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
 | 
			
		||||
		getContentPane().add(contentPanel, BorderLayout.CENTER);
 | 
			
		||||
		GridBagLayout gbl_contentPanel = new GridBagLayout();
 | 
			
		||||
		gbl_contentPanel.columnWidths	= new int[] { 0, 0, 0 };
 | 
			
		||||
		gbl_contentPanel.rowHeights		= new int[] { 0, 0, 0 };
 | 
			
		||||
		gbl_contentPanel.columnWeights	= new double[] { 0.0, 1.0, Double.MIN_VALUE };
 | 
			
		||||
		gbl_contentPanel.rowWeights		= new double[] { 0.0, 0.0, Double.MIN_VALUE };
 | 
			
		||||
		contentPanel.setLayout(gbl_contentPanel);
 | 
			
		||||
 | 
			
		||||
		lblUserName = new JLabel("Username:");
 | 
			
		||||
		GridBagConstraints gbc_lblUserName = new GridBagConstraints();
 | 
			
		||||
		gbc_lblUserName.anchor	= GridBagConstraints.EAST;
 | 
			
		||||
		gbc_lblUserName.insets	= new Insets(0, 0, 5, 5);
 | 
			
		||||
		gbc_lblUserName.gridx	= 0;
 | 
			
		||||
		gbc_lblUserName.gridy	= 0;
 | 
			
		||||
		contentPanel.add(lblUserName, gbc_lblUserName);
 | 
			
		||||
 | 
			
		||||
		textField = new JTextField();
 | 
			
		||||
		textField.setBorder(null);
 | 
			
		||||
		GridBagConstraints gbc_textField = new GridBagConstraints();
 | 
			
		||||
		gbc_textField.insets	= new Insets(0, 0, 5, 0);
 | 
			
		||||
		gbc_textField.fill		= GridBagConstraints.HORIZONTAL;
 | 
			
		||||
		gbc_textField.gridx		= 1;
 | 
			
		||||
		gbc_textField.gridy		= 0;
 | 
			
		||||
		contentPanel.add(textField, gbc_textField);
 | 
			
		||||
		textField.setColumns(10);
 | 
			
		||||
 | 
			
		||||
		lblPassword = new JLabel("Password:");
 | 
			
		||||
		GridBagConstraints gbc_lblPassword = new GridBagConstraints();
 | 
			
		||||
		gbc_lblPassword.anchor	= GridBagConstraints.EAST;
 | 
			
		||||
		gbc_lblPassword.insets	= new Insets(0, 0, 0, 5);
 | 
			
		||||
		gbc_lblPassword.gridx	= 0;
 | 
			
		||||
		gbc_lblPassword.gridy	= 1;
 | 
			
		||||
		contentPanel.add(lblPassword, gbc_lblPassword);
 | 
			
		||||
 | 
			
		||||
		passwordField = new JPasswordField();
 | 
			
		||||
		passwordField.setBorder(null);
 | 
			
		||||
		GridBagConstraints gbc_passwordField = new GridBagConstraints();
 | 
			
		||||
		gbc_passwordField.fill	= GridBagConstraints.HORIZONTAL;
 | 
			
		||||
		gbc_passwordField.gridx	= 1;
 | 
			
		||||
		gbc_passwordField.gridy	= 1;
 | 
			
		||||
		contentPanel.add(passwordField, gbc_passwordField);
 | 
			
		||||
 | 
			
		||||
		lblRepeatPassword				= new JLabel("Repeat Password:");
 | 
			
		||||
		gbc_lblRepeatPassword			= new GridBagConstraints();
 | 
			
		||||
		gbc_lblRepeatPassword.anchor	= GridBagConstraints.EAST;
 | 
			
		||||
		gbc_lblRepeatPassword.insets	= new Insets(0, 0, 0, 5);
 | 
			
		||||
		gbc_lblRepeatPassword.gridx		= 0;
 | 
			
		||||
		gbc_lblRepeatPassword.gridy		= 2;
 | 
			
		||||
 | 
			
		||||
		repeatPasswordField				= new JPasswordField();
 | 
			
		||||
		gbc_repeatPasswordField			= new GridBagConstraints();
 | 
			
		||||
		gbc_repeatPasswordField.fill	= GridBagConstraints.HORIZONTAL;
 | 
			
		||||
		gbc_repeatPasswordField.gridx	= 1;
 | 
			
		||||
		gbc_repeatPasswordField.gridy	= 2;
 | 
			
		||||
 | 
			
		||||
		errorMessage			= new JLabel();
 | 
			
		||||
		gbc_errorMessage		= new GridBagConstraints();
 | 
			
		||||
		gbc_errorMessage.gridx	= 1;
 | 
			
		||||
		gbc_errorMessage.gridy	= 3;
 | 
			
		||||
		gbc_errorMessage.fill	= GridBagConstraints.HORIZONTAL;
 | 
			
		||||
		gbc_errorMessage.insets	= new Insets(5, 5, 5, 5);
 | 
			
		||||
		errorMessage.setForeground(Color.RED);
 | 
			
		||||
		errorMessage.setVisible(false);
 | 
			
		||||
		contentPanel.add(errorMessage, gbc_errorMessage);
 | 
			
		||||
 | 
			
		||||
		buttonPane = new JPanel();
 | 
			
		||||
 | 
			
		||||
		registerText = new JTextPane();
 | 
			
		||||
		registerText.setEditable(false);
 | 
			
		||||
		registerText.setText("Register?");
 | 
			
		||||
		registerText.setFont(new Font("Arial", Font.BOLD, 12));
 | 
			
		||||
		registerText.setAlignmentX(LEFT_ALIGNMENT);
 | 
			
		||||
		buttonPane.add(registerText);
 | 
			
		||||
 | 
			
		||||
		registerCheckBox = new JCheckBox();
 | 
			
		||||
		registerCheckBox.setAlignmentX(LEFT_ALIGNMENT);
 | 
			
		||||
		registerCheckBox.addItemListener(e -> {
 | 
			
		||||
			switch (e.getStateChange()) {
 | 
			
		||||
				case ItemEvent.SELECTED:
 | 
			
		||||
					contentPanel.add(lblRepeatPassword, gbc_lblRepeatPassword);
 | 
			
		||||
					contentPanel.add(repeatPasswordField, gbc_repeatPasswordField);
 | 
			
		||||
					setSize(338, 173);
 | 
			
		||||
					break;
 | 
			
		||||
 | 
			
		||||
				case ItemEvent.DESELECTED:
 | 
			
		||||
					if (repeatPasswordField.getParent() == contentPanel) {
 | 
			
		||||
						contentPanel.remove(lblRepeatPassword);
 | 
			
		||||
						contentPanel.remove(repeatPasswordField);
 | 
			
		||||
						setSize(338, 148);
 | 
			
		||||
					}
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
			contentPanel.revalidate();
 | 
			
		||||
			contentPanel.repaint();
 | 
			
		||||
		});
 | 
			
		||||
		buttonPane.add(registerCheckBox);
 | 
			
		||||
 | 
			
		||||
		buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
 | 
			
		||||
		getContentPane().add(buttonPane, BorderLayout.SOUTH);
 | 
			
		||||
		okButton = new PrimaryButton("OK");
 | 
			
		||||
		okButton.setActionCommand("OK");
 | 
			
		||||
		buttonPane.add(okButton);
 | 
			
		||||
		getRootPane().setDefaultButton(okButton);
 | 
			
		||||
 | 
			
		||||
		cancelButton = new PrimaryButton("Cancel");
 | 
			
		||||
		cancelButton.setActionCommand("Cancel");
 | 
			
		||||
		buttonPane.add(cancelButton);
 | 
			
		||||
		setTheme();
 | 
			
		||||
 | 
			
		||||
		setModalityType(Dialog.DEFAULT_MODALITY_TYPE);
 | 
			
		||||
 | 
			
		||||
		EventBus.getInstance().register(HandshakeSuccessfulEvent.class, evt -> dispose());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Resets the text stored in the password fields.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.3-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	private void clearPasswordFields() {
 | 
			
		||||
		passwordField.setText(null);
 | 
			
		||||
		repeatPasswordField.setText(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void setTheme() {
 | 
			
		||||
		Theme theme = Settings.getInstance().getCurrentTheme();
 | 
			
		||||
 | 
			
		||||
		// Panels
 | 
			
		||||
		contentPanel.setBackground(theme.getBackgroundColor());
 | 
			
		||||
		contentPanel.setForeground(theme.getBackgroundColor());
 | 
			
		||||
 | 
			
		||||
		buttonPane.setBackground(theme.getBackgroundColor());
 | 
			
		||||
		buttonPane.setForeground(theme.getBackgroundColor());
 | 
			
		||||
 | 
			
		||||
		// Input Fields
 | 
			
		||||
		textField.setBackground(theme.getCellColor());
 | 
			
		||||
		textField.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		passwordField.setBackground(theme.getCellColor());
 | 
			
		||||
		passwordField.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		repeatPasswordField.setBackground(theme.getCellColor());
 | 
			
		||||
		repeatPasswordField.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		// JLabels
 | 
			
		||||
		lblUserName.setBackground(theme.getCellColor());
 | 
			
		||||
		lblUserName.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		lblPassword.setBackground(theme.getCellColor());
 | 
			
		||||
		lblPassword.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		lblRepeatPassword.setBackground(theme.getCellColor());
 | 
			
		||||
		lblRepeatPassword.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		// Register
 | 
			
		||||
		registerText.setBackground(theme.getCellColor());
 | 
			
		||||
		registerText.setForeground(theme.getUserNameColor());
 | 
			
		||||
 | 
			
		||||
		registerCheckBox.setBackground(theme.getCellColor());
 | 
			
		||||
 | 
			
		||||
		// Buttons
 | 
			
		||||
		okButton.setBackground(theme.getInteractableBackgroundColor());
 | 
			
		||||
		okButton.setForeground(theme.getInteractableForegroundColor());
 | 
			
		||||
 | 
			
		||||
		cancelButton.setBackground(theme.getInteractableBackgroundColor());
 | 
			
		||||
		cancelButton.setForeground(theme.getInteractableForegroundColor());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Shuts the system down properly if the login was aborted.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.1-beta
 | 
			
		||||
	 */
 | 
			
		||||
	private void abortLogin() {
 | 
			
		||||
		logger.info("The login process has been cancelled. Exiting...");
 | 
			
		||||
		System.exit(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This package contains all graphical Containers, like Dialogs and Frames.<br>
 | 
			
		||||
 * <br>
 | 
			
		||||
 * Project: <strong>envoy-client</strong><br>
 | 
			
		||||
 * File: <strong>package-info.java</strong><br>
 | 
			
		||||
 * Created: <strong>16 Mar 2020</strong><br>
 | 
			
		||||
 *
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
 * @author Kai S. K. Engelbart
 | 
			
		||||
 * @author Maximilian Käfer
 | 
			
		||||
 * @since Envoy Client v0.1-beta
 | 
			
		||||
 */
 | 
			
		||||
package envoy.client.ui.container;
 | 
			
		||||
		Reference in New Issue
	
	Block a user