diff --git a/client/src/main/java/envoy/client/data/Cache.java b/client/src/main/java/envoy/client/data/Cache.java index 25b985e..4283800 100644 --- a/client/src/main/java/envoy/client/data/Cache.java +++ b/client/src/main/java/envoy/client/data/Cache.java @@ -51,6 +51,12 @@ public final class Cache implements Consumer, Serializable { */ public void setProcessor(Consumer processor) { this.processor = processor; } + /** + * @return the processor + * @since Envoy Client v0.2-beta + */ + public Consumer getProcessor() { return processor; } + /** * Relays all cached elements to the processor. * diff --git a/client/src/main/java/envoy/client/data/Context.java b/client/src/main/java/envoy/client/data/Context.java new file mode 100644 index 0000000..26a3b0e --- /dev/null +++ b/client/src/main/java/envoy/client/data/Context.java @@ -0,0 +1,97 @@ +package envoy.client.data; + +import javafx.stage.Stage; + +import envoy.client.net.Client; +import envoy.client.net.WriteProxy; +import envoy.client.ui.SceneContext; + +/** + * Provides access to commonly used objects. + *

+ * Project: client
+ * File: Context.java
+ * Created: 01.09.2020
+ * + * @author Leon Hofmeister + * @since Envoy Client v0.2-beta + */ +public class Context { + + private final Client client = new Client(); + + private WriteProxy writeProxy; + + private LocalDB localDB; + + private Stage stage; + + private SceneContext sceneContext; + + private static final Context instance = new Context(); + + /** + * @return the instance of {@code Context} used throughout Envoy + * @since Envoy Client v0.2-beta + */ + public static Context getInstance() { return instance; } + + /** + * @return the localDB + * @since Envoy Client v0.2-beta + */ + public LocalDB getLocalDB() { return localDB; } + + /** + * @param localDB the localDB to set + * @since Envoy Client v0.2-beta + */ + public void setLocalDB(LocalDB localDB) { this.localDB = localDB; } + + /** + * @return the sceneContext + * @since Envoy Client v0.2-beta + */ + public SceneContext getSceneContext() { return sceneContext; } + + /** + * @param sceneContext the sceneContext to set. Additionally sets the stage. + * @since Envoy Client v0.2-beta + */ + public void setSceneContext(SceneContext sceneContext) { + this.sceneContext = sceneContext; + stage = sceneContext.getStage(); + } + + /** + * @return the client + * @since Envoy Client v0.2-beta + */ + public Client getClient() { return client; } + + /** + * @return the writeProxy + * @since Envoy Client v0.2-beta + */ + public WriteProxy getWriteProxy() { return writeProxy; } + + /** + * @return the stage + * @since Envoy Client v0.2-beta + */ + public Stage getStage() { return stage; } + + private Context() {} + + /** + * @param writeProxy the writeProxy to set + * @since Envoy Client v0.2-beta + */ + public void setWriteProxy(WriteProxy writeProxy) { this.writeProxy = writeProxy; } + + /** + * @param stage the stage to set + * @since Envoy Client v0.2-beta + */ + public void setStage(Stage stage) { this.stage = stage; } +} diff --git a/client/src/main/java/envoy/client/event/BackEvent.java b/client/src/main/java/envoy/client/event/BackEvent.java index 55c7691..202e80f 100644 --- a/client/src/main/java/envoy/client/event/BackEvent.java +++ b/client/src/main/java/envoy/client/event/BackEvent.java @@ -3,8 +3,9 @@ package envoy.client.event; import envoy.event.Event.Valueless; /** - * This event serves the purpose to trigger the tab change to tab 0 in {@link ChatScene}.

- * + * This event serves the purpose to trigger the tab change to tab 0 in + * {@link envoy.client.ui.controller.ChatScene}. + *

* Project: client
* File: BackEvent.java
* Created: 23.08.2020
@@ -12,7 +13,7 @@ import envoy.event.Event.Valueless; * @author Maximilian Käfer * @since Envoy Client v0.2-beta */ -public class BackEvent extends Valueless{ +public class BackEvent extends Valueless { private static final long serialVersionUID = 0L; } diff --git a/client/src/main/java/envoy/client/event/LoadGroupCreationEvent.java b/client/src/main/java/envoy/client/event/LoadGroupCreationEvent.java deleted file mode 100644 index db5d455..0000000 --- a/client/src/main/java/envoy/client/event/LoadGroupCreationEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package envoy.client.event; - -import envoy.client.data.LocalDB; -import envoy.client.ui.controller.ChatScene; -import envoy.event.Event; - -/** - * This event carries an instance of {@link LocalDB} so the groupCreationTab has the most recent version of the contactList.
- * It is triggered as soon as the corresponding button in {@link ChatScene} is clicked.

- * - * Project: client
- * File: LoadGroupCreationEvent.java
- * Created: 23.08.2020
- * - * @author Maximilian Käfer - * @since Envoy Client v0.2-beta - */ -public class LoadGroupCreationEvent extends Event{ - - private static final long serialVersionUID = 0L; - - /** - * @param value the localDB - * @since Envoy Client v0.2-beta - */ - public LoadGroupCreationEvent(LocalDB value) { super(value); } -} diff --git a/client/src/main/java/envoy/client/net/WriteProxy.java b/client/src/main/java/envoy/client/net/WriteProxy.java index 06b72e2..b953e5f 100644 --- a/client/src/main/java/envoy/client/net/WriteProxy.java +++ b/client/src/main/java/envoy/client/net/WriteProxy.java @@ -5,6 +5,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import envoy.client.data.Cache; +import envoy.client.data.Context; import envoy.client.data.LocalDB; import envoy.data.Message; import envoy.event.MessageStatusChange; @@ -24,8 +25,8 @@ import envoy.util.EnvoyLog; */ public final class WriteProxy { - private final Client client; - private final LocalDB localDB; + private final Client client = Context.getInstance().getClient(); + private final LocalDB localDB = Context.getInstance().getLocalDB(); private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class); @@ -33,15 +34,9 @@ public final class WriteProxy { * Initializes a write proxy using a client and a local database. The * corresponding cache processors are injected into the caches. * - * @param client the client used to send messages and message status change - * events - * @param localDB the local database used to cache messages and message status - * change events * @since Envoy Client v0.3-alpha */ - public WriteProxy(Client client, LocalDB localDB) { - this.client = client; - this.localDB = localDB; + public WriteProxy() { // Initialize cache processors for messages and message status change events localDB.getCacheMap().get(Message.class).setProcessor(msg -> { @@ -68,7 +63,32 @@ public final class WriteProxy { * * @since Envoy Client v0.3-alpha */ - public void flushCache() { localDB.getCacheMap().getMap().values().forEach(Cache::relay); } + public void flushCache() { + + // supplying default values if not initialized - for some reason these + // processors can be not defined... + final var messageCache = localDB.getCacheMap().get(Message.class); + if (messageCache.getProcessor() == null) messageCache.setProcessor(msg -> { + try { + logger.log(Level.FINER, "Sending cached " + msg); + client.sendMessage(msg); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Could not send cached message: ", e); + } + }); + final var messageStatusCache = localDB.getCacheMap().get(MessageStatusChange.class); + if (messageStatusCache.getProcessor() == null) messageStatusCache.setProcessor(evt -> { + logger.log(Level.FINER, "Sending cached " + evt); + try { + client.sendEvent(evt); + } catch (final IOException e) { + logger.log(Level.SEVERE, "Could not send cached message status change event: ", e); + } + }); + + // sending these solely local objects to the server + localDB.getCacheMap().getMap().values().forEach(Cache::relay); + } /** * Delivers a message to the server if online. Otherwise the message is cached diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java index 63edc06..39096d1 100644 --- a/client/src/main/java/envoy/client/ui/SceneContext.java +++ b/client/src/main/java/envoy/client/ui/SceneContext.java @@ -124,8 +124,8 @@ public final class SceneContext { * @since Envoy Client v0.1-beta */ public void pop() { - sceneStack.pop(); - controllerStack.pop(); + if (!sceneStack.isEmpty()) sceneStack.pop(); + if (!controllerStack.isEmpty()) controllerStack.pop(); if (!sceneStack.isEmpty()) { final var newScene = sceneStack.peek(); stage.setScene(newScene); diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 7c2bff6..51a929d 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -1,7 +1,10 @@ package envoy.client.ui; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.time.Instant; +import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -12,12 +15,14 @@ import javafx.stage.Stage; import envoy.client.data.*; import envoy.client.net.Client; +import envoy.client.net.WriteProxy; import envoy.client.ui.SceneContext.SceneInfo; import envoy.client.ui.controller.LoginScene; -import envoy.data.GroupMessage; -import envoy.data.Message; +import envoy.data.*; +import envoy.data.User.UserStatus; import envoy.event.GroupMessageStatusChange; import envoy.event.MessageStatusChange; +import envoy.exception.EnvoyException; import envoy.util.EnvoyLog; /** @@ -40,9 +45,10 @@ public final class Startup extends Application { */ public static final String VERSION = "0.1-beta"; - private LocalDB localDB; - private Client client; + private static LocalDB localDB; + private static final Context context = Context.getInstance(); + private static final Client client = context.getClient(); private static final ClientConfig config = ClientConfig.getInstance(); private static final Logger logger = EnvoyLog.getLogger(Startup.class); @@ -76,22 +82,142 @@ public final class Startup extends Application { System.exit(1); return; } - - // Initialize client and unread message cache - client = new Client(); - - final var cacheMap = new CacheMap(); - cacheMap.put(Message.class, new Cache()); - cacheMap.put(GroupMessage.class, new Cache()); - cacheMap.put(MessageStatusChange.class, new Cache()); - cacheMap.put(GroupMessageStatusChange.class, new Cache()); + // Prepare handshake + localDB.loadIDGenerator(); + context.setLocalDB(localDB); stage.setTitle("Envoy"); stage.getIcons().add(IconUtil.loadIcon("envoy_logo")); final var sceneContext = new SceneContext(stage); - sceneContext.load(SceneInfo.LOGIN_SCENE); - sceneContext.getController().initializeData(client, localDB, cacheMap, sceneContext); + context.setSceneContext(sceneContext); + context.setWriteProxy(new WriteProxy()); + + // Perform automatic login if configured + if (config.hasLoginCredentials()) + performHandshake(new LoginCredentials(config.getUser(), config.getPassword(), false, Startup.VERSION, loadLastSync(config.getUser()))); + else sceneContext.load(SceneInfo.LOGIN_SCENE); + } + + /** + * Tries to perform a Handshake with the server. + * + * @param credentials the credentials to use for the handshake + * @since Envoy Client v0.2-beta + */ + public static void performHandshake(LoginCredentials credentials) { + final var cacheMap = new CacheMap(); + cacheMap.put(Message.class, new Cache()); + cacheMap.put(GroupMessage.class, new Cache()); + cacheMap.put(MessageStatusChange.class, new Cache()); + cacheMap.put(GroupMessageStatusChange.class, new Cache()); + try { + final var client = context.getClient(); + client.performHandshake(credentials, cacheMap); + if (client.isOnline()) { + loadChatScene(); + client.initReceiver(localDB, cacheMap); + } + } catch (IOException | InterruptedException | TimeoutException e) { + logger.log(Level.INFO, "Could not connect to server. Entering offline mode..."); + attemptOfflineMode(credentials.getIdentifier()); + } + } + + /** + * Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode + * for a given user. + * + * @param identifier the identifier of the user - currently his username + * @since Envoy Client v0.2-beta + */ + public static void attemptOfflineMode(String identifier) { + try { + // Try entering offline mode + localDB.loadUsers(); + final User clientUser = localDB.getUsers().get(identifier); + if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); + client.setSender(clientUser); + loadChatScene(); + } catch (final Exception e) { + new Alert(AlertType.ERROR, "Client error: " + e).showAndWait(); + logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e); + System.exit(1); + } + } + + /** + * Loads the last known time a user has been online. + * + * @param identifier the identifier of this user - currently his name + * @return the last {@code Instant} at which he has been online + * @since Envoy Client v0.2-beta + */ + public static Instant loadLastSync(String identifier) { + try { + localDB.loadUsers(); + localDB.setUser(localDB.getUsers().get(identifier)); + localDB.initializeUserStorage(); + localDB.loadUserData(); + } catch (final Exception e) { + // User storage empty, wrong user name etc. -> default lastSync + } + return localDB.getLastSync(); + } + + private static void loadChatScene() { + + // Set client user in local database + localDB.setUser(client.getSender()); + + // Initialize chats in local database + try { + localDB.initializeUserStorage(); + localDB.loadUserData(); + } catch (final FileNotFoundException e) { + // The local database file has not yet been created, probably first login + } catch (final Exception e) { + new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait(); + logger.log(Level.WARNING, "Could not load local database: ", e); + } + + localDB.synchronize(); + + if (client.isOnline()) context.getWriteProxy().flushCache(); + else + // Set all contacts to offline mode + localDB.getChats() + .stream() + .map(Chat::getRecipient) + .filter(User.class::isInstance) + .map(User.class::cast) + .forEach(u -> u.setStatus(UserStatus.OFFLINE)); + + final var stage = context.getStage(); + // Load ChatScene + context.getSceneContext().pop(); + stage.setMinHeight(400); + stage.setMinWidth(843); + context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE); + stage.centerOnScreen(); + + if (StatusTrayIcon.isSupported()) { + + // Configure hide on close + stage.setOnCloseRequest(e -> { + if (Settings.getInstance().isHideOnClose()) { + stage.setIconified(true); + e.consume(); + } + }); + + // Initialize status tray icon + final var trayIcon = new StatusTrayIcon(stage); + Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> { + if ((Boolean) c) trayIcon.show(); + else trayIcon.hide(); + }); + } } /** @@ -106,7 +232,7 @@ public final class Startup extends Application { localDB.save(client.isOnline()); Settings.getInstance().save(); - logger.log(Level.INFO, "Closing connection..."); + if (client.isOnline()) logger.log(Level.INFO, "Closing connection..."); client.close(); logger.log(Level.INFO, "Envoy was terminated by its user"); diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java index c4e286b..c35db20 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -29,7 +29,9 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.layout.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.FileChooser; @@ -39,13 +41,16 @@ import envoy.client.data.*; import envoy.client.data.audio.AudioRecorder; import envoy.client.data.commands.SystemCommandBuilder; import envoy.client.data.commands.SystemCommandsMap; -import envoy.client.event.*; +import envoy.client.event.BackEvent; +import envoy.client.event.MessageCreationEvent; +import envoy.client.event.SendEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; import envoy.client.ui.*; -import envoy.client.ui.listcell.*; +import envoy.client.ui.listcell.ChatControl; +import envoy.client.ui.listcell.ListCellFactory; +import envoy.client.ui.listcell.MessageListCell; import envoy.client.util.ReflectionUtil; -import envoy.constant.Tabs; import envoy.data.*; import envoy.data.Attachment.AttachmentType; import envoy.event.*; @@ -122,42 +127,41 @@ public final class ChatScene implements Restorable { @FXML private TextArea contactSearch; - + @FXML private VBox contactOperations; - + @FXML private TabPane tabPane; - + @FXML private Tab contactSearchTab; - + @FXML private Tab groupCreationTab; - private LocalDB localDB; - private Client client; - private WriteProxy writeProxy; - private SceneContext sceneContext; + private final LocalDB localDB = context.getLocalDB(); + private final Client client = context.getClient(); + private final WriteProxy writeProxy = context.getWriteProxy(); + private final SceneContext sceneContext = context.getSceneContext(); + private final AudioRecorder recorder = new AudioRecorder(); + private final SystemCommandsMap messageTextAreaCommands = new SystemCommandsMap(); - private Chat currentChat; - private AudioRecorder recorder; - private boolean recording; - private Attachment pendingAttachment; - private boolean postingPermanentlyDisabled; - - private final SystemCommandsMap messageTextAreaCommands = new SystemCommandsMap(); + private Chat currentChat; + private FilteredList chats; + private boolean recording; + private Attachment pendingAttachment; + private boolean postingPermanentlyDisabled; private static final Settings settings = Settings.getInstance(); private static final EventBus eventBus = EventBus.getInstance(); private static final Logger logger = EnvoyLog.getLogger(ChatScene.class); + private static final Context context = Context.getInstance(); private static final Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20); private static final int MAX_MESSAGE_LENGTH = 255; private static final int DEFAULT_ICON_SIZE = 16; - private FilteredList chats; - /** * Initializes the appearance of certain visual components. * @@ -181,24 +185,28 @@ public final class ChatScene implements Restorable { clip.setArcHeight(43); clip.setArcWidth(43); clientProfilePic.setClip(clip); - + + chatList.setItems(chats = new FilteredList<>(FXCollections.observableList(localDB.getChats()))); + contactLabel.setText(localDB.getUser().getName()); + + initializeSystemCommandsMap(); + Platform.runLater(() -> { - if(client.isOnline()) { - try { - contactSearchTab.setContent(FXMLLoader.load(new File("src/main/resources/fxml/ContactSearchTab.fxml").toURI().toURL())); - groupCreationTab.setContent(FXMLLoader.load(new File("src/main/resources/fxml/GroupCreationTab.fxml").toURI().toURL())); - } catch (IOException e2) { - logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e2); - } - } else { + if (client.isOnline()) try { + contactSearchTab.setContent(FXMLLoader.load(new File("src/main/resources/fxml/ContactSearchTab.fxml").toURI().toURL())); + groupCreationTab.setContent(FXMLLoader.load(new File("src/main/resources/fxml/GroupCreationTab.fxml").toURI().toURL())); + } catch (final IOException e2) { + logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e2); + } + else { contactSearchTab.setContent(createOfflineNote()); groupCreationTab.setContent(createOfflineNote()); } }); - - //Listen to backEvents - eventBus.register(BackEvent.class, e -> tabPane.getSelectionModel().select(Tabs.CONTACT_LIST)); - + + // Listen to backEvents + eventBus.register(BackEvent.class, e -> tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal())); + // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { final var message = e.get(); @@ -284,15 +292,15 @@ public final class ChatScene implements Restorable { eventBus.register(GroupCreationResult.class, e -> Platform.runLater(() -> { newGroupButton.setDisable(!e.get()); })); } - + private AnchorPane createOfflineNote() { - AnchorPane anc = new AnchorPane(); - VBox vBox = new VBox(); + final var anc = new AnchorPane(); + final var vBox = new VBox(); vBox.setAlignment(Pos.TOP_CENTER); vBox.setPrefWidth(316); - Label label = new Label("You have to be online!"); + final var label = new Label("You have to be online!"); label.setPadding(new Insets(50, 0, 5, 0)); - Button button = new Button("OK"); + final var button = new Button("OK"); button.setOnAction(e -> eventBus.dispatch(new BackEvent())); vBox.getChildren().add(label); vBox.getChildren().add(button); @@ -317,34 +325,6 @@ public final class ChatScene implements Restorable { messageTextAreaCommands.add("DABR", builder.build()); } - /** - * Initializes all necessary data via dependency injection- - * - * @param sceneContext the scene context used to load other scenes - * @param localDB the local database form which chats and users are loaded - * @param client the client used to request ID generators - * @param writeProxy the write proxy used to send messages and other data to - * the server - * @since Envoy Client v0.1-beta - */ - public void initializeData(SceneContext sceneContext, LocalDB localDB, Client client, WriteProxy writeProxy) { - this.sceneContext = sceneContext; - this.localDB = localDB; - this.client = client; - this.writeProxy = writeProxy; - - chats = new FilteredList<>(FXCollections.observableList(localDB.getChats())); - chatList.setItems(chats); - contactLabel.setText(localDB.getUser().getName()); - MessageControl.setLocalDB(localDB); - MessageControl.setSceneContext(sceneContext); - - if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info"); - - recorder = new AudioRecorder(); - initializeSystemCommandsMap(); - } - @Override public void onRestore() { updateRemainingCharsLabel(); } @@ -423,10 +403,7 @@ public final class ChatScene implements Restorable { * @since Envoy Client v0.1-beta */ @FXML - private void settingsButtonClicked() { - sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); - sceneContext.getController().initializeData(sceneContext, client); - } + private void settingsButtonClicked() { sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); } /** * Actions to perform when the "Add Contact" - Button has been clicked. @@ -434,15 +411,10 @@ public final class ChatScene implements Restorable { * @since Envoy Client v0.1-beta */ @FXML - private void addContactButtonClicked() { - tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH); - } - + private void addContactButtonClicked() { tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal()); } + @FXML - private void groupCreationButtonClicked() { - eventBus.dispatch(new LoadGroupCreationEvent(localDB)); - tabPane.getSelectionModel().select(Tabs.GROUP_CREATION); - } + private void groupCreationButtonClicked() { tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal()); } @FXML private void voiceButtonClicked() { diff --git a/client/src/main/java/envoy/client/ui/controller/GroupCreationTab.java b/client/src/main/java/envoy/client/ui/controller/GroupCreationTab.java index 9672063..40ac115 100644 --- a/client/src/main/java/envoy/client/ui/controller/GroupCreationTab.java +++ b/client/src/main/java/envoy/client/ui/controller/GroupCreationTab.java @@ -4,15 +4,14 @@ import static java.util.function.Predicate.not; import java.util.stream.Collectors; -import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.HBox; import envoy.client.data.Chat; +import envoy.client.data.Context; import envoy.client.data.LocalDB; import envoy.client.event.BackEvent; -import envoy.client.event.LoadGroupCreationEvent; import envoy.client.event.SendEvent; import envoy.client.ui.listcell.ContactControl; import envoy.client.ui.listcell.ListCellFactory; @@ -43,7 +42,7 @@ public class GroupCreationTab { @FXML private Button createButton; - + @FXML private Button cancelButton; @@ -52,21 +51,22 @@ public class GroupCreationTab { @FXML private ListView userList; - + @FXML private Label errorMessageLabel; - + @FXML private Button proceedDupButton; - + @FXML private Button cancelDupButton; - + @FXML private HBox errorProceedBox; - private LocalDB localDB; - private String name; + private String name; + + private final LocalDB localDB = Context.getInstance().getLocalDB(); private static final EventBus eventBus = EventBus.getInstance(); @@ -74,20 +74,15 @@ public class GroupCreationTab { private void initialize() { userList.setCellFactory(new ListCellFactory<>(ContactControl::new)); userList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - - eventBus.register(LoadGroupCreationEvent.class, e -> { - createButton.setDisable(true); - this.localDB = e.get(); - userList.getItems().clear(); - Platform.runLater(() -> userList.getItems() - .addAll(localDB.getChats() - .stream() - .map(Chat::getRecipient) - .filter(User.class::isInstance) - .filter(not(localDB.getUser()::equals)) - .map(User.class::cast) - .collect(Collectors.toList()))); - }); + createButton.setDisable(true); + userList.getItems() + .addAll(localDB.getChats() + .stream() + .map(Chat::getRecipient) + .filter(User.class::isInstance) + .filter(not(localDB.getUser()::equals)) + .map(User.class::cast) + .collect(Collectors.toList())); } /** @@ -96,9 +91,7 @@ public class GroupCreationTab { * @since Envoy Client v0.1-beta */ @FXML - private void userListClicked() { - createButton.setDisable(userList.getSelectionModel().isEmpty() || groupNameField.getText().isBlank()); - } + private void userListClicked() { createButton.setDisable(userList.getSelectionModel().isEmpty() || groupNameField.getText().isBlank()); } /** * Checks, whether the {@code createButton} can be enabled because text is @@ -173,7 +166,7 @@ public class GroupCreationTab { setErrorMessageLabelSize(0); setProcessPaneSize(0); } - + @FXML private void proceedOnNameDuplication() { createButton.setDisable(false); @@ -184,7 +177,7 @@ public class GroupCreationTab { setProcessPaneSize(0); groupNameField.clear(); } - + @FXML private void cancelOnNameDuplication() { createButton.setDisable(false); @@ -193,13 +186,13 @@ public class GroupCreationTab { setProcessPaneSize(0); groupNameField.clear(); } - + private void setErrorMessageLabelSize(int value) { errorMessageLabel.setPrefHeight(value); errorMessageLabel.setMinHeight(value); errorMessageLabel.setMaxHeight(value); } - + private void setProcessPaneSize(int value) { proceedDupButton.setPrefHeight(value); proceedDupButton.setMinHeight(value); diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index d3f1956..6d3f4ee 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -1,9 +1,5 @@ package envoy.client.ui.controller; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.time.Instant; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -14,16 +10,12 @@ import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.ImageView; -import envoy.client.data.*; -import envoy.client.net.Client; -import envoy.client.net.WriteProxy; -import envoy.client.ui.*; +import envoy.client.data.ClientConfig; +import envoy.client.ui.IconUtil; +import envoy.client.ui.Startup; import envoy.data.LoginCredentials; -import envoy.data.User; -import envoy.data.User.UserStatus; import envoy.event.EventBus; import envoy.event.HandshakeRejection; -import envoy.exception.EnvoyException; import envoy.util.Bounds; import envoy.util.EnvoyLog; @@ -65,17 +57,11 @@ public final class LoginScene { @FXML private ImageView logo; - private Client client; - private LocalDB localDB; - private CacheMap cacheMap; - private SceneContext sceneContext; - private boolean registration = false; private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); private static final ClientConfig config = ClientConfig.getInstance(); - private static final Settings settings = Settings.getInstance(); @FXML private void initialize() { @@ -85,32 +71,9 @@ public final class LoginScene { eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); })); logo.setImage(IconUtil.loadIcon("envoy_logo")); - } - - /** - * 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 cacheMap the map of all caches needed - * @param sceneContext the scene context used to initialize the chat scene - * @since Envoy Client v0.1-beta - */ - public void initializeData(Client client, LocalDB localDB, CacheMap cacheMap, SceneContext sceneContext) { - this.client = client; - this.localDB = localDB; - this.cacheMap = cacheMap; - this.sceneContext = sceneContext; - - // Prepare handshake - localDB.loadIDGenerator(); // Set initial cursor userTextField.requestFocus(); - - // Perform automatic login if configured - if (config.hasLoginCredentials()) - performHandshake(new LoginCredentials(config.getUser(), config.getPassword(), false, Startup.VERSION, loadLastSync(config.getUser()))); } @FXML @@ -123,15 +86,12 @@ public final class LoginScene { } else if (!Bounds.isValidContactName(userTextField.getText())) { new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); userTextField.clear(); - } else performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText(), registration, - Startup.VERSION, loadLastSync(userTextField.getText()))); + } else Startup.performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText(), registration, Startup.VERSION, + Startup.loadLastSync(userTextField.getText()))); } @FXML - private void offlineModeButtonPressed() { - attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText(), false, Startup.VERSION, - loadLastSync(userTextField.getText()))); - } + private void offlineModeButtonPressed() { Startup.attemptOfflineMode(userTextField.getText()); } @FXML private void registerSwitchPressed() { @@ -159,102 +119,4 @@ public final class LoginScene { logger.log(Level.INFO, "The login process has been cancelled. Exiting..."); System.exit(0); } - - private Instant loadLastSync(String identifier) { - try { - localDB.loadUsers(); - localDB.setUser(localDB.getUsers().get(identifier)); - localDB.initializeUserStorage(); - localDB.loadUserData(); - } catch (final Exception e) { - // User storage empty, wrong user name etc. -> default lastSync - } - return localDB.getLastSync(); - } - - private void performHandshake(LoginCredentials credentials) { - try { - client.performHandshake(credentials, cacheMap); - if (client.isOnline()) { - loadChatScene(); - client.initReceiver(localDB, cacheMap); - } - } catch (IOException | InterruptedException | TimeoutException e) { - logger.log(Level.INFO, "Could not connect to server. Entering offline mode..."); - attemptOfflineMode(credentials); - } - } - - private void attemptOfflineMode(LoginCredentials credentials) { - try { - // Try entering offline mode - localDB.loadUsers(); - final User clientUser = localDB.getUsers().get(credentials.getIdentifier()); - if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); - client.setSender(clientUser); - loadChatScene(); - } catch (final Exception e) { - new Alert(AlertType.ERROR, "Client error: " + e).showAndWait(); - logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e); - System.exit(1); - } - } - - private void loadChatScene() { - - // Set client user in local database - localDB.setUser(client.getSender()); - - // Initialize chats in local database - try { - localDB.initializeUserStorage(); - localDB.loadUserData(); - } catch (final FileNotFoundException e) { - // The local database file has not yet been created, probably first login - } catch (final Exception e) { - new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait(); - logger.log(Level.WARNING, "Could not load local database: ", e); - } - - // Initialize write proxy - final var writeProxy = new WriteProxy(client, localDB); - - localDB.synchronize(); - - if (client.isOnline()) writeProxy.flushCache(); - else - // Set all contacts to offline mode - localDB.getChats() - .stream() - .map(Chat::getRecipient) - .filter(User.class::isInstance) - .map(User.class::cast) - .forEach(u -> u.setStatus(UserStatus.OFFLINE)); - - // Load ChatScene - sceneContext.pop(); - sceneContext.getStage().setMinHeight(400); - sceneContext.getStage().setMinWidth(843); - sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); - sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); - sceneContext.getStage().centerOnScreen(); - - if (StatusTrayIcon.isSupported()) { - - // Configure hide on close - sceneContext.getStage().setOnCloseRequest(e -> { - if (settings.isHideOnClose()) { - sceneContext.getStage().setIconified(true); - e.consume(); - } - }); - - // Initialize status tray icon - final var trayIcon = new StatusTrayIcon(sceneContext.getStage()); - settings.getItems().get("hideOnClose").setChangeHandler(c -> { - if ((Boolean) c) trayIcon.show(); - else trayIcon.hide(); - }); - } - } } diff --git a/client/src/main/java/envoy/client/ui/controller/SettingsScene.java b/client/src/main/java/envoy/client/ui/controller/SettingsScene.java index 03c1048..bd3a237 100644 --- a/client/src/main/java/envoy/client/ui/controller/SettingsScene.java +++ b/client/src/main/java/envoy/client/ui/controller/SettingsScene.java @@ -5,6 +5,7 @@ import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.TitledPane; +import envoy.client.data.Context; import envoy.client.net.Client; import envoy.client.ui.SceneContext; import envoy.client.ui.listcell.AbstractListCell; @@ -26,21 +27,8 @@ public final class SettingsScene { @FXML private TitledPane titledPane; - private SceneContext sceneContext; - - /** - * @param sceneContext enables the user to return to the chat scene - * @param client the {@code Client} used to get the current user and to - * check if this user is online - * @since Envoy Client v0.1-beta - */ - public void initializeData(SceneContext sceneContext, Client client) { - this.sceneContext = sceneContext; - settingsList.getItems().add(new GeneralSettingsPane()); - settingsList.getItems().add(new UserSettingsPane(sceneContext, client.getSender(), client.isOnline())); - settingsList.getItems().add(new DownloadSettingsPane(sceneContext)); - settingsList.getItems().add(new BugReportPane(client.getSender(), client.isOnline())); - } + private final Client client = Context.getInstance().getClient(); + private final SceneContext sceneContext = Context.getInstance().getSceneContext(); @FXML private void initialize() { @@ -49,6 +37,10 @@ public final class SettingsScene { @Override protected Label renderItem(SettingsPane item) { return new Label(item.getTitle()); } }); + settingsList.getItems().add(new GeneralSettingsPane()); + settingsList.getItems().add(new UserSettingsPane(sceneContext, client.getSender(), client.isOnline())); + settingsList.getItems().add(new DownloadSettingsPane(sceneContext)); + settingsList.getItems().add(new BugReportPane(client.getSender(), client.isOnline())); } @FXML diff --git a/client/src/main/java/envoy/client/ui/controller/Tabs.java b/client/src/main/java/envoy/client/ui/controller/Tabs.java new file mode 100644 index 0000000..8c365a5 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/controller/Tabs.java @@ -0,0 +1,29 @@ +package envoy.client.ui.controller; + +/** + * Provides options to select different tabs. + *

+ * Project: client
+ * File: Tabs.java
+ * Created: 30.8.2020
+ * + * @author Maximilian Käfer + * @since Envoy Client v0.2-beta + */ +public enum Tabs { + + /** + * Selects the {@code contact list} tab. + */ + CONTACT_LIST, + + /** + * Selects the {@code contact search} tab. + */ + CONTACT_SEARCH, + + /** + * Selects the {@code group creation} tab. + */ + GROUP_CREATION; +} diff --git a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java index e3c1664..ff4674e 100644 --- a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -19,6 +19,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; +import envoy.client.data.Context; import envoy.client.data.LocalDB; import envoy.client.data.Settings; import envoy.client.ui.AudioControl; @@ -43,11 +44,10 @@ import envoy.util.EnvoyLog; */ public final class MessageControl extends Label { - private boolean ownMessage; + private final boolean ownMessage; - private static LocalDB localDB; - - private static SceneContext sceneContext; + private final LocalDB localDB = Context.getInstance().getLocalDB(); + private final SceneContext sceneContext = Context.getInstance().getSceneContext(); private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") .withZone(ZoneId.systemDefault()); @@ -177,22 +177,10 @@ public final class MessageControl extends Label { } } - /** - * @param localDB the localDB used by the current user - * @since Envoy Client v0.2-beta - */ - public static void setLocalDB(LocalDB localDB) { MessageControl.localDB = localDB; } - /** * @return whether the message stored by this {@code MessageControl} has been * sent by this user of Envoy * @since Envoy Client v0.1-beta */ public boolean isOwnMessage() { return ownMessage; } - - /** - * @param sceneContext the scene context storing the stage used in Envoy - * @since Envoy Client v0.1-beta - */ - public static void setSceneContext(SceneContext sceneContext) { MessageControl.sceneContext = sceneContext; } } diff --git a/client/src/main/java/envoy/constant/Tabs.java b/client/src/main/java/envoy/constant/Tabs.java deleted file mode 100644 index 2a8b42d..0000000 --- a/client/src/main/java/envoy/constant/Tabs.java +++ /dev/null @@ -1,17 +0,0 @@ -package envoy.constant; - -/** - * Project: client
- * File: Tabs.java
- * Created: Aug 30, 2020
- * - * @author Maximilian Käfer - * @since Envoy Client v0.2-beta - */ -public class Tabs { - private Tabs() {} - - public static int CONTACT_LIST = 0; - public static int CONTACT_SEARCH = 1; - public static int GROUP_CREATION = 2; -}