Added user and message loading and message posting

This commit is contained in:
Kai S. K. Engelbart 2020-03-28 15:32:24 +01:00
parent a68a01b455
commit bd0da338a7
8 changed files with 252 additions and 139 deletions

View File

@ -2,9 +2,10 @@ package envoy.client.data;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.list.Model;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User; import envoy.data.User;
@ -23,12 +24,12 @@ import envoy.event.MessageStatusChangeEvent;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class Chat implements Serializable { public final class Chat implements Serializable {
private static final long serialVersionUID = 0L; private final User recipient;
private final List<Message> messages = new ArrayList<>();
private final User recipient; private static final long serialVersionUID = 1L;
private final Model<Message> model = new Model<>();
/** /**
* Provides the list of messages that the recipient receives.<br> * Provides the list of messages that the recipient receives.<br>
@ -39,14 +40,6 @@ public class Chat implements Serializable {
*/ */
public Chat(User recipient) { this.recipient = recipient; } public Chat(User recipient) { this.recipient = recipient; }
/**
* Appends a message to the bottom of this chat
*
* @param message the message to append
* @since Envoy Client v0.1-alpha
*/
public void appendMessage(Message message) { model.add(message); }
/** /**
* Sets the status of all chat messages received from the recipient to * Sets the status of all chat messages received from the recipient to
* {@code READ} starting from the bottom and stopping once a read message is * {@code READ} starting from the bottom and stopping once a read message is
@ -59,8 +52,8 @@ public class Chat implements Serializable {
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void read(WriteProxy writeProxy) throws IOException { public void read(WriteProxy writeProxy) throws IOException {
for (int i = model.size() - 1; i >= 0; --i) { for (int i = messages.size() - 1; i >= 0; --i) {
final Message m = model.get(i); final Message m = messages.get(i);
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break; if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
else { else {
m.setStatus(MessageStatus.READ); m.setStatus(MessageStatus.READ);
@ -74,13 +67,13 @@ public class Chat implements Serializable {
* the status {@code READ} * the status {@code READ}
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; } public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
/** /**
* @return all messages in the current chat * @return all messages in the current chat
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-beta
*/ */
public Model<Message> getModel() { return model; } public List<Message> getMessages() { return messages; }
/** /**
* @return the recipient of a message * @return the recipient of a message

View File

@ -154,7 +154,7 @@ public abstract class LocalDB {
*/ */
public Message getMessage(long id) { public Message getMessage(long id) {
for (Chat c : chats) for (Chat c : chats)
for (Message m : c.getModel()) for (Message m : c.getMessages())
if (m.getID() == id) return m; if (m.getID() == id) return m;
return null; return null;
} }

View File

@ -29,16 +29,20 @@
vgrow="SOMETIMES" /> vgrow="SOMETIMES" />
</rowConstraints> </rowConstraints>
<children> <children>
<ListView fx:id="userList" prefHeight="211.0" prefWidth="300.0" <ListView fx:id="userList" prefHeight="211.0"
GridPane.rowIndex="1" /> prefWidth="300.0" GridPane.rowIndex="1"
<Label text="Label" /> onMouseClicked="#userListClicked" />
<Button id="settingsButton" mnemonicParsing="false" <Label text="Select a contact to chat with" fx:id="contactLabel" />
text="Settings" GridPane.columnIndex="1" /> <Button fx:id="settingsButton" mnemonicParsing="false"
text="Settings" GridPane.columnIndex="1"
onAction="#settingsButtonClicked" />
<ListView fx:id="messageList" prefHeight="257.0" <ListView fx:id="messageList" prefHeight="257.0"
prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="1" /> prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Button id="postButton" mnemonicParsing="false" text="Post" <Button fx:id="postButton" mnemonicParsing="false" text="Post"
GridPane.columnIndex="1" GridPane.rowIndex="2" /> GridPane.columnIndex="1" GridPane.rowIndex="2"
<TextArea prefHeight="200.0" prefWidth="200.0" onAction="#postButtonClicked" disable="true" />
GridPane.rowIndex="2" /> <TextArea fx:id="messageTextArea" prefHeight="200.0"
prefWidth="200.0" GridPane.rowIndex="2"
onKeyReleased="#messageTextUpdated" />
</children> </children>
</GridPane> </GridPane>

View File

@ -1,11 +1,22 @@
package envoy.client.ui; package envoy.client.ui;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.data.Message;
import envoy.data.MessageBuilder;
import envoy.data.User;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import javafx.event.ActionEvent; import javafx.collections.FXCollections;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ListView; import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -18,26 +29,121 @@ import javafx.scene.control.ListView;
public final class ChatSceneController { public final class ChatSceneController {
@FXML @FXML
private ListView messageList; private Label contactLabel;
@FXML @FXML
private ListView userList; private ListView<Message> messageList;
@FXML
private ListView<User> userList;
@FXML
private Button postButton;
@FXML
private Button settingsButton;
@FXML
private TextArea messageTextArea;
private LocalDB localDB;
private Client client;
private WriteProxy writeProxy;
private Chat currentChat;
private static final Logger logger = EnvoyLog.getLogger(ChatSceneController.class); private static final Logger logger = EnvoyLog.getLogger(ChatSceneController.class);
@FXML @FXML
public void initialize() { private void initialize() {
messageList.setCellFactory(listView -> new MessageListCell()); messageList.setCellFactory(listView -> new MessageListCell());
userList.setCellFactory(listView -> new UserListCell()); userList.setCellFactory(listView -> new UserListCell());
} }
@FXML void initializeData(LocalDB localDB, Client client, WriteProxy writeProxy) {
public void postButtonClicked(ActionEvent e) { this.localDB = localDB;
logger.info("Post Button clicked."); this.client = client;
this.writeProxy = writeProxy;
// TODO: handle offline mode
userList.getItems().addAll(client.getContacts().getContacts());
// userList.getItems().addAll(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList()));
} }
@FXML @FXML
public void settingsButtonClicked(ActionEvent e) { private void userListClicked() {
logger.info("Settings Button clicked."); final User user = userList.getSelectionModel().getSelectedItem();
if (user != null && (currentChat == null || user != currentChat.getRecipient())) {
contactLabel.setText(user.getName());
// Swap observable list
if (currentChat != null) {
currentChat.getMessages().clear();
currentChat.getMessages().addAll(messageList.getItems());
}
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
// Load the chat or create a new one and add it to the LocalDB
currentChat = localDB.getChats()
.stream()
.filter(c -> c.getRecipient() == user)
.findAny()
.orElseGet(() -> { var chat = new Chat(user); localDB.getChats().add(chat); return chat; });
messageList.setItems(FXCollections.observableArrayList(currentChat.getMessages()));
}
}
@FXML
private void postButtonClicked() { postMessage(); }
@FXML
private void settingsButtonClicked() { logger.info("Settings Button clicked."); }
@FXML
private void messageTextUpdated(KeyEvent e) {
if (e.getCode() == KeyCode.ENTER) postMessage();
else postButton.setDisable(messageTextArea.getText().isBlank());
}
/**
* Sends a new message to the server based on the text entered in the
* messageTextArea.
*
* @since Envoy Client v0.1-beta
*/
private void postMessage() {
// Create and send message
sendMessage(new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(messageTextArea.getText().strip())
.build());
// Clear text field and disable post button
messageTextArea.setText("");
postButton.setDisable(true);
}
/**
* Sends a message to the server.
*
* @param message the message to send
* @since Envoy Client v0.1-beta
*/
private void sendMessage(Message message) {
try {
// Send message
writeProxy.writeMessage(message);
// Add message to LocalDB and update UI
messageList.getItems().add(message);
// Request a new ID generator if all IDs were used
if (!localDB.getIDGenerator().hasNext()) client.requestIdGenerator();
} catch (IOException e) {
logger.log(Level.SEVERE, "Error sending message", e);
}
} }
} }

View File

@ -21,6 +21,7 @@ public class MessageListCell extends ListCell<Message> {
@Override @Override
protected void updateItem(Message message, boolean empty) { protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty); super.updateItem(message, empty);
if (!empty && message != null) { setGraphic(new Label(message.getText())); } if (!empty && message != null) setGraphic(new Label(message.getText()));
else setGraphic(null);
} }
} }

View File

@ -1,12 +1,21 @@
package envoy.client.ui; package envoy.client.ui;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.swing.JOptionPane;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.container.LoginDialog;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.User.UserStatus;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@ -25,9 +34,9 @@ import javafx.stage.Stage;
*/ */
public final class Startup extends Application { public final class Startup extends Application {
private LocalDB localDB; private LocalDB localDB;
private Client client; private Client client;
private WriteProxy writeProxy; private WriteProxy writeProxy;
private Cache<Message> cache; private Cache<Message> cache;
private static final ClientConfig config = ClientConfig.getInstance(); private static final ClientConfig config = ClientConfig.getInstance();
@ -38,87 +47,81 @@ public final class Startup extends Application {
*/ */
@Override @Override
public void init() throws Exception { public void init() throws Exception {
// try { try {
// // Load the configuration from client.properties first // Load the configuration from client.properties first
// Properties properties = new Properties(); Properties properties = new Properties();
// properties.load(Startup.class.getClassLoader().getResourceAsStream("client.properties")); properties.load(Startup.class.getClassLoader().getResourceAsStream("client.properties"));
// config.load(properties); config.load(properties);
//
// // Override configuration values with command line arguments // Override configuration values with command line arguments
// String[] args = getParameters().getRaw().toArray(new String[0]); String[] args = getParameters().getRaw().toArray(new String[0]);
// if (args.length > 0) config.load(args); if (args.length > 0) config.load(args);
//
// // Check if all mandatory configuration values have been initialized // Check if all mandatory configuration values have been initialized
// if (!config.isInitialized()) throw new EnvoyException("Configuration is not if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
// fully initialized"); } catch (Exception e) {
// } catch (Exception e) { JOptionPane.showMessageDialog(null, "Error loading configuration values:\n" + e, "Configuration error", JOptionPane.ERROR_MESSAGE);
// JOptionPane.showMessageDialog(null, "Error loading configuration values:\n" + e.printStackTrace();
// e, "Configuration error", JOptionPane.ERROR_MESSAGE); System.exit(1);
// e.printStackTrace(); }
// System.exit(1);
// } // Setup logger for the envoy package
// EnvoyLog.initialize(config);
// // Setup logger for the envoy package EnvoyLog.attach("envoy");
// EnvoyLog.initialize(config); EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
// EnvoyLog.attach("envoy"); EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
// EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
// EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier()); // Initialize the local database
// if (config.isIgnoreLocalDB()) {
// // Initialize the local database localDB = new TransientLocalDB();
// if (config.isIgnoreLocalDB()) { JOptionPane.showMessageDialog(null,
// localDB = new TransientLocalDB(); "Ignoring local database.\nMessages will not be saved!",
// JOptionPane.showMessageDialog(null, "Local database warning",
// "Ignoring local database.\nMessages will not be saved!", JOptionPane.WARNING_MESSAGE);
// "Local database warning", } else try {
// JOptionPane.WARNING_MESSAGE); localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
// } else try { } catch (IOException e3) {
// localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), logger.log(Level.SEVERE, "Could not initialize local database", e3);
// config.getLocalDB().getPath())); JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3, "Local database error", JOptionPane.ERROR_MESSAGE);
// } catch (IOException e3) { System.exit(1);
// logger.log(Level.SEVERE, "Could not initialize local database", e3); return;
// JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" }
// + e3, "Local database error", JOptionPane.ERROR_MESSAGE);
// System.exit(1); // Initialize client and unread message cache
// return; client = new Client();
// } cache = new Cache<>();
//
// // Initialize client and unread message cache // Try to connect to the server
// client = new Client(); new LoginDialog(client, localDB, cache);
// cache = new Cache<>();
// // Set client user in local database
// // Try to connect to the server localDB.setUser(client.getSender());
// new LoginDialog(client, localDB, cache);
// // Initialize chats in local database
// // Set client user in local database try {
// localDB.setUser(client.getSender()); localDB.initializeUserStorage();
// localDB.loadUserData();
// // Initialize chats in local database } catch (FileNotFoundException e) {
// try { // The local database file has not yet been created, probably first login
// localDB.initializeUserStorage(); } catch (Exception e) {
// localDB.loadUserData(); e.printStackTrace();
// } catch (FileNotFoundException e) { JOptionPane.showMessageDialog(null,
// // The local database file has not yet been created, probably first login "Error while loading local database: " + e + "\nChats will not be stored locally.",
// } catch (Exception e) { "Local DB error",
// e.printStackTrace(); JOptionPane.WARNING_MESSAGE);
// JOptionPane.showMessageDialog(null, }
// "Error while loading local database: " + e + "\nChats will not be stored
// locally.", // Initialize write proxy
// "Local DB error", writeProxy = client.createWriteProxy(localDB);
// JOptionPane.WARNING_MESSAGE);
// } if (client.isOnline()) {
//
// // Initialize write proxy // Save all users to the local database and flush cache
// writeProxy = client.createWriteProxy(localDB); localDB.setUsers(client.getUsers());
// writeProxy.flushCache();
// if (client.isOnline()) { } else
// // Set all contacts to offline mode
// // Save all users to the local database and flush cache localDB.getUsers().values().stream().filter(u -> u != localDB.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE));
// localDB.setUsers(client.getUsers());
// writeProxy.flushCache();
// } else
// // Set all contacts to offline mode
// localDB.getUsers().values().stream().filter(u -> u !=
// localDB.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE));
} }
/** /**
@ -134,6 +137,8 @@ public final class Startup extends Application {
stage.setTitle("Envoy"); stage.setTitle("Envoy");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/envoy_logo.png"))); stage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/envoy_logo.png")));
stage.setScene(chatScene); stage.setScene(chatScene);
loader.<ChatSceneController>getController().initializeData(localDB, client, writeProxy);
stage.show(); stage.show();
// Relay unread messages from cache // Relay unread messages from cache

View File

@ -1,7 +1,9 @@
package envoy.client.ui; package envoy.client.ui;
import envoy.data.User; import envoy.data.User;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell; import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -20,7 +22,9 @@ public class UserListCell extends ListCell<User> {
protected void updateItem(User user, boolean empty) { protected void updateItem(User user, boolean empty) {
super.updateItem(user, empty); super.updateItem(user, empty);
if (!empty && user != null) { if (!empty && user != null) {
final Label name = new Label(user.getName());
final Label status = new Label(user.getStatus().toString());
setGraphic(new VBox(name, status));
} }
} }
} }

View File

@ -259,7 +259,7 @@ public class ChatWindow extends JFrame {
textPane.setText(currentChat.getRecipient().getName()); textPane.setText(currentChat.getRecipient().getName());
// Update model and scroll down // Update model and scroll down
messageList.setModel(currentChat.getModel()); // messageList.setModel(currentChat.getModel());
scrollPane.setChatOpened(true); scrollPane.setChatOpened(true);
messageList.synchronizeModel(); messageList.synchronizeModel();
@ -413,7 +413,7 @@ public class ChatWindow extends JFrame {
EventBus.getInstance().register(MessageCreationEvent.class, evt -> { EventBus.getInstance().register(MessageCreationEvent.class, evt -> {
Message message = evt.get(); Message message = evt.get();
Chat chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findFirst().get(); Chat chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findFirst().get();
chat.appendMessage(message); // chat.appendMessage(message);
// Read message and update UI if in current chat // Read message and update UI if in current chat
if (chat == currentChat) readCurrentChat(); if (chat == currentChat) readCurrentChat();
@ -428,18 +428,18 @@ public class ChatWindow extends JFrame {
final MessageStatus status = evt.get(); final MessageStatus status = evt.get();
for (Chat c : localDB.getChats()) for (Chat c : localDB.getChats())
for (Message m : c.getModel()) // for (Message m : c.getModel())
if (m.getID() == id) { // if (m.getID() == id) {
//
// Update message status // // Update message status
m.setStatus(status); // m.setStatus(status);
//
// Update model and scroll down if current chat // // Update model and scroll down if current chat
if (c == currentChat) { // if (c == currentChat) {
messageList.setModel(currentChat.getModel()); // messageList.setModel(currentChat.getModel());
scrollPane.setChatOpened(true); // scrollPane.setChatOpened(true);
} else messageList.synchronizeModel(); // } else messageList.synchronizeModel();
} // }
revalidate(); revalidate();
repaint(); repaint();
@ -584,7 +584,7 @@ public class ChatWindow extends JFrame {
writeProxy.writeMessage(message); writeProxy.writeMessage(message);
// Add message to PersistentLocalDB and update UI // Add message to PersistentLocalDB and update UI
currentChat.appendMessage(message); // currentChat.appendMessage(message);
// Update UI // Update UI
revalidate(); revalidate();