diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index ede0b46..abaf24f 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -11,10 +11,5 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/Client/pom.xml b/Client/pom.xml
index 1a04367..8855526 100644
--- a/Client/pom.xml
+++ b/Client/pom.xml
@@ -45,6 +45,12 @@
${junit.version}
test
+
+ org.jetbrains
+ annotations
+ 16.0.1
+ compile
+
diff --git a/Client/src/main/java/dev/wiing/gossip/client/Connection.java b/Client/src/main/java/dev/wiing/gossip/client/Connection.java
index c400349..efb88db 100644
--- a/Client/src/main/java/dev/wiing/gossip/client/Connection.java
+++ b/Client/src/main/java/dev/wiing/gossip/client/Connection.java
@@ -1,24 +1,37 @@
package dev.wiing.gossip.client;
+import dev.wiing.gossip.lib.PacketHandler;
import dev.wiing.gossip.lib.PacketManager;
import dev.wiing.gossip.lib.models.SecretUser;
+import dev.wiing.gossip.lib.models.User;
+import dev.wiing.gossip.lib.packets.FetchUserPacket;
import dev.wiing.gossip.lib.packets.Packet;
+import dev.wiing.gossip.lib.packets.UserDataPacket;
+import javafx.application.Platform;
import java.io.IOException;
import java.net.Socket;
+import java.net.SocketException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
public class Connection {
private static final Connection instance = new Connection();
- private final PacketManager packetManager;
+ private final PacketManager packetManager = new PacketManager();
+
+ private final PacketHandler packetHandler = new PacketHandler();
+
+ private Thread handlerThread;
private Socket socket;
private SecretUser self;
+ private final List queuedPackets = Collections.synchronizedList(new LinkedList<>());
+
private Connection() {
- packetManager = new PacketManager();
packetManager.registerPackets();
}
@@ -30,6 +43,35 @@ public class Connection {
return packetManager;
}
+ public PacketHandler getPacketHandler() {
+ return packetHandler;
+ }
+
+ public static class PacketHandlerRunnable implements Runnable {
+ private final Connection connection;
+
+ public PacketHandlerRunnable(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ Packet packet = connection.nextPacket();
+ connection.getPacketHandler().runPacket(packet);
+ } catch (SocketException e) {
+ break;
+ }
+ }
+ }
+ }
+
+ public void beginHandlingPackets() {
+ handlerThread = new Thread(new PacketHandlerRunnable(this));
+ handlerThread.start();
+ }
+
public Socket getSocket() {
return socket;
}
@@ -46,20 +88,81 @@ public class Connection {
}
}
- public Packet nextPacket() {
+ public Packet nextPacket(boolean useQueue) throws SocketException {
+ if (!queuedPackets.isEmpty()) {
+ return queuedPackets.remove(0);
+ }
+
try {
return getPacketManager().readPacket(getSocket().getInputStream());
+ } catch (SocketException e) {
+ throw e;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
+ public Packet nextPacket() throws SocketException {
+ return nextPacket(true);
+ }
+
+ public Packet nextPacket(short packetType) throws SocketException {
+ while (true) {
+ Packet packet = nextPacket(false);
+
+ if (packet.getType() != packetType) {
+ queuedPackets.add(packet);
+ continue;
+ }
+
+ return packet;
+ }
+ }
+
public SecretUser getSelf() {
return self;
}
public void setSelf(SecretUser self) {
this.self = self;
+
+ userCache.put(self.getUserID(), new User(
+ self.getUsername(),
+ self.getAvatarID(),
+ self.getUserID()
+ ));
+ }
+
+ private final Map userCache = new ConcurrentHashMap<>();
+
+ public User getUser(long userID) {
+ User result;
+
+ if ((result = userCache.getOrDefault(userID, null)) != null) {
+ return result;
+ }
+
+ FetchUserPacket fetch = new FetchUserPacket();
+ fetch.setUserID(userID);
+ Connection.getInstance().sendPacket(fetch);
+
+ Packet resp;
+ try {
+ resp = Connection.getInstance().nextPacket(UserDataPacket.TYPE);
+ } catch (SocketException e) {
+ throw new RuntimeException(e);
+ }
+ if (resp.getType() == UserDataPacket.TYPE) {
+ UserDataPacket userData = (UserDataPacket)resp;
+ result = new User(
+ userData.getUsername(),
+ userData.getAvatarID(),
+ userData.getUserID()
+ );
+ userCache.put(result.getUserID(), result);
+ }
+
+ return result;
}
}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/LoginController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/LoginController.java
index 95105ab..119daad 100644
--- a/Client/src/main/java/dev/wiing/gossip/client/controllers/LoginController.java
+++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/LoginController.java
@@ -2,7 +2,7 @@ package dev.wiing.gossip.client.controllers;
import dev.wiing.gossip.client.Connection;
-import dev.wiing.gossip.client.data.UserIcon;
+import dev.wiing.gossip.client.data.UserAvatar;
import dev.wiing.gossip.lib.models.SecretUser;
import dev.wiing.gossip.lib.packets.CredentialsPacket;
import dev.wiing.gossip.lib.packets.Packet;
@@ -12,12 +12,15 @@ import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
+import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.shape.Circle;
+import javafx.stage.Stage;
import javafx.util.Duration;
+import java.net.SocketException;
import java.net.URL;
import java.util.ResourceBundle;
@@ -34,15 +37,7 @@ public class LoginController implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
- paneIcon.setBackground(new Background(
- new BackgroundImage(
- UserIcon.MORNING.getImage(),
- BackgroundRepeat.NO_REPEAT,
- BackgroundRepeat.NO_REPEAT,
- BackgroundPosition.CENTER,
- new BackgroundSize(100.0, 100.0, true, true, false, true)
- )
- ));
+ UserAvatar.MORNING.applyToRegionBackground(paneIcon, false);
Circle circle = new Circle(paneIcon.getMinWidth() * 0.5);
paneIcon.setShape(circle);
@@ -57,17 +52,27 @@ public class LoginController implements Initializable {
@FXML
public void onLogin(ActionEvent event) {
RegisterPacket packet = new RegisterPacket();
- packet.setIconID((char)0);
+ packet.setAvatarID((byte)0);
packet.setUsername(txtUsername.getText());
Connection.getInstance().sendPacket(packet);
- Packet result = Connection.getInstance().nextPacket();
+ Packet result = null;
+ try {
+ result = Connection.getInstance().nextPacket();
+ } catch (SocketException e) {
+ throw new RuntimeException(e);
+ }
+
if (result != null && result.getType() == CredentialsPacket.TYPE) {
CredentialsPacket creds = (CredentialsPacket)result;
- SecretUser user = new SecretUser(packet.getUsername(), packet.getIconID(), creds.getUID(), creds.getSecret());
+ SecretUser user = new SecretUser(packet.getUsername(), packet.getAvatarID(), creds.getUID(), creds.getSecret());
Connection.getInstance().setSelf(user);
+
+ var pair = MainController.createInstance();
+
+ ((Stage)Stage.getWindows().get(0)).setScene(new Scene(pair.first()));
}
}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java
new file mode 100644
index 0000000..91c1c5e
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java
@@ -0,0 +1,114 @@
+package dev.wiing.gossip.client.controllers;
+
+import dev.wiing.gossip.client.Connection;
+import dev.wiing.gossip.client.generic.Pair;
+import dev.wiing.gossip.client.utils.Utils;
+import dev.wiing.gossip.lib.models.Topic;
+import dev.wiing.gossip.lib.models.User;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.VBox;
+
+import java.net.URL;
+import java.util.List;
+import java.util.ResourceBundle;
+
+public class MainChatController {
+
+ private Topic topic;
+
+ @FXML
+ private Label lblBabblersCount;
+
+ @FXML
+ private Label lblTopicName;
+
+ @FXML
+ private Label lblTyping;
+
+ @FXML
+ private Label lblVisitBabblerCount;
+
+ @FXML
+ private Label lblVisitHostName;
+
+ @FXML
+ private Label lblVisitTopicDescription;
+
+ @FXML
+ private Label lblVisitTopicName;
+
+ @FXML
+ private TextArea txtCompose;
+
+ @FXML
+ private VBox vboxChatPage;
+
+ @FXML
+ private VBox vboxMessages;
+
+ @FXML
+ private VBox vboxVisitPage;
+
+ public void setTopic(Topic topic) {
+ this.topic = topic;
+
+ boolean participating = topic.hasUser(Connection.getInstance().getSelf());
+ togglePages(participating);
+
+ setTopicName(topic.getName());
+ lblVisitTopicDescription.setText(topic.getDescription());
+ setBabblerCount(topic.getUsersReadOnly().size());
+ lblVisitHostName.setText(topic.getHost().getUsernameDisplay());
+
+ this.topic.addChangeListener(evt -> {
+ if (evt.getPropertyName().equals("userAdd") || evt.getPropertyName().equals("userRemove")) {
+ setBabblerCount(topic.getUsersReadOnly().size());
+ }
+ });
+ }
+
+ private void setTopicName(String name) {
+ lblTopicName.setText(name);
+ lblVisitTopicName.setText(name);
+ }
+
+ private void setBabblerCount(int count) {
+ String res = "No Babblers";
+ if (count == 1) {
+ res = "1 Babbler";
+ } else if (count > 1) {
+ res = Integer.toString(count) + " Babblers";
+ }
+
+ lblBabblersCount.setText(res);
+ lblVisitBabblerCount.setText(res + " active");
+ }
+
+ private void togglePages(boolean participating) {
+ vboxChatPage.setVisible(participating);
+ vboxVisitPage.setVisible(!participating);
+ }
+
+ @FXML
+ void onJoinTopic(ActionEvent event) {
+ topic.addUser(Connection.getInstance().getSelf());
+
+ togglePages(true);
+ }
+
+ @FXML
+ void onSend(MouseEvent event) {
+
+ }
+
+ public static Pair createInstance() {
+ return Utils.createInstance("views/main-chat-view.fxml");
+ }
+
+}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java
new file mode 100644
index 0000000..42bab5b
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java
@@ -0,0 +1,140 @@
+package dev.wiing.gossip.client.controllers;
+
+import dev.wiing.gossip.client.Connection;
+import dev.wiing.gossip.client.controllers.item.MainTopicItemController;
+import dev.wiing.gossip.client.data.UserAvatar;
+import dev.wiing.gossip.client.generic.Pair;
+import dev.wiing.gossip.client.utils.Utils;
+import dev.wiing.gossip.lib.models.Topic;
+import dev.wiing.gossip.lib.models.User;
+import dev.wiing.gossip.lib.packets.*;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+import javafx.scene.control.Label;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.VBox;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MainController implements Initializable {
+
+ Topic activeTopic = null;
+ Parent activeTopicElement = null;
+ private final Map topicContentMap = new ConcurrentHashMap<>();
+
+ @FXML
+ private VBox vboxRoot;
+
+ @FXML
+ private VBox vboxTopics;
+
+ @FXML
+ private Label lblUsername;
+
+ @FXML
+ private Pane paneIcon;
+
+ @FXML
+ private AnchorPane paneContent;
+
+ @FXML
+ private Label lblJoinMessage;
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ User user = Connection.getInstance().getSelf();
+ lblUsername.setText(user.getUsernameDisplay());
+
+ UserAvatar.getAvatar(user.getAvatarID()).applyToRegionBackground(paneIcon, true);
+
+ Connection.getInstance().getPacketHandler().addListener(TopicAddedPacket.class, packet -> {
+ User host = Connection.getInstance().getUser(packet.getHostID());
+
+ Topic topic = new Topic(
+ packet.getTopicID(),
+ packet.getTopicName(),
+ packet.getTopicDescription(),
+ host,
+ packet.getTopicColor()
+ );
+
+ var pair = MainTopicItemController.createInstance();
+ pair.second().setTopic(topic);
+
+ var contentPair = MainChatController.createInstance();
+ contentPair.second().setTopic(topic);
+
+ topicContentMap.put(topic.getId(), contentPair.first());
+
+ AnchorPane.setLeftAnchor(contentPair.first(), 0.0);
+ AnchorPane.setBottomAnchor(contentPair.first(), 0.0);
+ AnchorPane.setTopAnchor(contentPair.first(), 0.0);
+ AnchorPane.setRightAnchor(contentPair.first(), 0.0);
+ contentPair.first().setVisible(false);
+
+ Platform.runLater(() -> {
+ paneContent.getChildren().add(contentPair.first());
+
+ vboxTopics.getChildren().add(pair.first());
+ });
+
+ pair.second().setOnMouseClicked(event -> {
+ setActiveTopic(topic, pair.first());
+ });
+ });
+
+ Connection.getInstance().beginHandlingPackets();
+
+ vboxTopics.getChildren().clear();
+ }
+
+ public void setActiveTopic(Topic topic, Parent element) {
+ if (activeTopic != null) {
+ activeTopicElement.getStyleClass().remove("tag-min");
+ topicContentMap.get(activeTopic.getId()).setVisible(false);
+ } else {
+ lblJoinMessage.setVisible(false);
+ }
+
+ if (topic == null) {
+ activeTopic = null;
+ activeTopicElement = null;
+
+ lblJoinMessage.setVisible(true);
+
+ vboxRoot.setStyle("-accent: hsb(250, 10%, 50%);");
+ return;
+ }
+
+ vboxRoot.setStyle("-accent: hsb(" + topic.getColor() + ", 70%, 100%);");
+
+ element.getStyleClass().add("tag-min");
+
+ activeTopic = topic;
+ activeTopicElement = element;
+
+ topicContentMap.get(activeTopic.getId()).setVisible(true);
+ }
+
+ @FXML
+ void onCreateTopic(ActionEvent event) {
+ CreateTopicPacket packet = new CreateTopicPacket();
+ packet.setUserSecret(Connection.getInstance().getSelf().getUserSecret());
+ packet.setTopicName("Point Nemo");
+ packet.setTopicDescription("We are so gone XDD");
+
+ Connection.getInstance().sendPacket(packet);
+ }
+
+ public static Pair createInstance() {
+ return Utils.createInstance("views/main-view.fxml");
+ }
+
+}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MainTopicItemController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MainTopicItemController.java
new file mode 100644
index 0000000..db76afd
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MainTopicItemController.java
@@ -0,0 +1,41 @@
+package dev.wiing.gossip.client.controllers.item;
+
+import dev.wiing.gossip.client.generic.Pair;
+import dev.wiing.gossip.client.utils.Utils;
+import dev.wiing.gossip.lib.models.Topic;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.fxml.FXML;
+import javafx.scene.Parent;
+import javafx.scene.control.Label;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.HBox;
+
+public class MainTopicItemController {
+
+ private Topic topic;
+
+ @FXML
+ private HBox hboxParent;
+
+ @FXML
+ private Label lblTopicName;
+
+ public void setTopic(Topic topic) {
+ this.topic = topic;
+ this.lblTopicName.setText(topic.getName());
+ }
+
+ public Topic getTopic() {
+ return topic;
+ }
+
+ public static Pair createInstance() {
+ return Utils.createInstance("views/main-topic-item.fxml");
+ }
+
+ public void setOnMouseClicked(EventHandler eventHandler) {
+ hboxParent.setOnMouseClicked(eventHandler);
+ }
+
+}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/data/UserAvatar.java b/Client/src/main/java/dev/wiing/gossip/client/data/UserAvatar.java
new file mode 100644
index 0000000..6ff88e0
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/data/UserAvatar.java
@@ -0,0 +1,102 @@
+package dev.wiing.gossip.client.data;
+
+import dev.wiing.gossip.client.Program;
+import javafx.scene.image.Image;
+import javafx.scene.layout.*;
+import javafx.scene.shape.Circle;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+
+public class UserAvatar {
+
+ public static final UserAvatar MORNING = new UserAvatar("Morning", "Eggs and Bacon", 0, "avatars/avatar-0.png");
+ public static final UserAvatar VINTAGE = new UserAvatar("Vintage", "Red Car", 1, "avatars/avatar-1.png");
+ public static final UserAvatar ROUTINE = new UserAvatar("Routine", "Coffee Cup", 2, "avatars/avatar-2.png");
+ public static final UserAvatar SILLY = new UserAvatar("Silly", "Ghost Toy", 3, "avatars/avatar-3.png");
+ public static final UserAvatar ADVENTURE = new UserAvatar("Adventure", "Slide with a Plant", 4, "avatars/avatar-4.png");
+ public static final UserAvatar REBELLION = new UserAvatar("Rebellion", "Potted Cactus", 5, "avatars/avatar-5.png");
+ public static final UserAvatar DIGITAL = new UserAvatar("Digital", "Game Controller", 6, "avatars/avatar-6.png");
+ public static final UserAvatar MYSTERY = new UserAvatar("Mystery", "Floating Sphere", 7, "avatars/avatar-7.png");
+ public static final UserAvatar VACATION = new UserAvatar("Vacation", "Cat under an Umbrella", 8, "avatars/avatar-8.png");
+ public static final UserAvatar SIMPLE = new UserAvatar("Simple", "Potted Succulent", 9, "avatars/avatar-9.png");
+
+ public static UserAvatar getAvatar(int avatarID) {
+ return Arrays.stream(getAvatars())
+ .skip(avatarID)
+ .findFirst()
+ .orElse(null);
+ }
+
+ public static UserAvatar[] getAvatars() {
+ return new UserAvatar[]{
+ MORNING,
+ VINTAGE,
+ ROUTINE,
+ SILLY,
+ ADVENTURE,
+ REBELLION,
+ DIGITAL,
+ MYSTERY,
+ VACATION,
+ SIMPLE
+ };
+ }
+
+ private final String name;
+ private final String description;
+ private final int id;
+ private final URL url;
+
+ private UserAvatar(String name, String description, int id, String path) {
+ this.name = name;
+ this.description = description;
+ this.id = id;
+ this.url = Program.class.getResource(path);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+ public Image getImage() {
+ try {
+ return new Image(url.openStream());
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public void applyToRegionBackground(Region region, boolean applyShape) {
+ if (getImage() == null) return;
+
+ region.setBackground(new Background(
+ new BackgroundImage(
+ getImage(),
+ BackgroundRepeat.NO_REPEAT,
+ BackgroundRepeat.NO_REPEAT,
+ BackgroundPosition.CENTER,
+ new BackgroundSize(100.0, 100.0, true, true, false, true)
+ )
+ ));
+ region.setStyle("");
+
+ if (applyShape) {
+ Circle circle = new Circle(region.getMinWidth() * 0.5);
+ region.setShape(circle);
+ }
+ }
+}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/data/UserIcon.java b/Client/src/main/java/dev/wiing/gossip/client/data/UserIcon.java
deleted file mode 100644
index fc7d2b8..0000000
--- a/Client/src/main/java/dev/wiing/gossip/client/data/UserIcon.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package dev.wiing.gossip.client.data;
-
-import dev.wiing.gossip.client.Program;
-import javafx.scene.image.Image;
-
-import java.io.IOException;
-import java.net.URL;
-
-public class UserIcon {
-
- public static final UserIcon MORNING = new UserIcon("Morning", "Eggs and Bacon", 0, "icons/icon_0.png");
- public static final UserIcon VINTAGE = new UserIcon("Vintage", "Red Car", 1, "icons/icon_1.png");
- public static final UserIcon ROUTINE = new UserIcon("Routine", "Coffee Cup", 2, "icons/icon_2.png");
- public static final UserIcon SILLY = new UserIcon("Silly", "Ghost Toy", 3, "icons/icon_3.png");
- public static final UserIcon ADVENTURE = new UserIcon("Adventure", "Slide with a Plant", 4, "icons/icon_4.png");
- public static final UserIcon REBELLION = new UserIcon("Rebellion", "Potted Cactus", 5, "icons/icon_5.png");
- public static final UserIcon DIGITAL = new UserIcon("Digital", "Game Controller", 6, "icons/icon_6.png");
- public static final UserIcon MYSTERY = new UserIcon("Mystery", "Floating Sphere", 7, "icons/icon_7.png");
- public static final UserIcon VACATION = new UserIcon("Vacation", "Cat under an Umbrella", 8, "icons/icon_8.png");
- public static final UserIcon SIMPLE = new UserIcon("Simple", "Potted Succulent", 9, "icons/icon_9.png");
-
- public static UserIcon[] getIcons() {
- return new UserIcon[]{
- MORNING,
- VINTAGE,
- ROUTINE,
- SILLY,
- ADVENTURE,
- REBELLION,
- DIGITAL,
- MYSTERY,
- VACATION,
- SIMPLE
- };
- }
-
- private final String name;
- private final String description;
- private final int id;
- private final URL url;
-
- private UserIcon(String name, String description, int id, String path) {
- this.name = name;
- this.description = description;
- this.id = id;
- this.url = Program.class.getResource(path);
- }
-
- public String getName() {
- return name;
- }
-
- public String getDescription() {
- return description;
- }
-
- public int getId() {
- return id;
- }
-
- public URL getUrl() {
- return url;
- }
-
- public Image getImage() {
- try {
- return new Image(url.openStream());
- } catch (IOException e) {
- return null;
- }
- }
-}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/generic/Pair.java b/Client/src/main/java/dev/wiing/gossip/client/generic/Pair.java
new file mode 100644
index 0000000..c06836d
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/generic/Pair.java
@@ -0,0 +1,20 @@
+package dev.wiing.gossip.client.generic;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+
+public record Pair(@NotNull U first, @NotNull V second) {
+
+ public static Pair cast(Pair extends X, ? extends Y> second) {
+ return new Pair<>(second.first, second.second);
+ }
+
+ @Override
+ public String toString() {
+ return "Pair[" +
+ "first=" + first + ", " +
+ "second=" + second + ']';
+ }
+
+}
diff --git a/Client/src/main/java/dev/wiing/gossip/client/utils/Utils.java b/Client/src/main/java/dev/wiing/gossip/client/utils/Utils.java
new file mode 100644
index 0000000..942269a
--- /dev/null
+++ b/Client/src/main/java/dev/wiing/gossip/client/utils/Utils.java
@@ -0,0 +1,57 @@
+package dev.wiing.gossip.client.utils;
+
+import dev.wiing.gossip.client.Program;
+import dev.wiing.gossip.client.generic.Pair;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+public class Utils {
+
+ public static Pair createInstance(String res) {
+ FXMLLoader loader = new FXMLLoader(Program.class.getResource(res));
+
+ Parent parent;
+ try {
+ parent = loader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ T controller = loader.getController();
+
+ return new Pair<>(parent, controller);
+ }
+
+ public static void openParentAsWindow(Parent parent, String title) {
+ Stage stage = new Stage();
+ stage.setTitle(title);
+ stage.setScene(new Scene(parent));
+ stage.show();
+ }
+
+ public static void updateLabelColorOnText(Label label, TextField... texts) {
+ for (TextField text : texts) {
+ text.setOnKeyTyped(keyEvent -> updateLabelColorOnTextCallback(label, texts));
+ }
+ }
+
+ private static void updateLabelColorOnTextCallback(Label label, TextField... texts) {
+ boolean hadItems = label.getStyleClass().contains("accent");
+
+ if (Arrays.stream(texts).allMatch(textField -> textField.getText().isEmpty())) {
+ label.getStyleClass().remove("accent");
+ }
+ else if (Arrays.stream(texts).allMatch(textField -> !textField.getText().isEmpty() && !hadItems)) {
+ label.getStyleClass().add("accent");
+ }
+ }
+
+}
diff --git a/Client/src/main/java/module-info.java b/Client/src/main/java/module-info.java
index 9736d4f..41d05ac 100644
--- a/Client/src/main/java/module-info.java
+++ b/Client/src/main/java/module-info.java
@@ -2,8 +2,11 @@ module dev.wiing.gossip.client {
requires javafx.controls;
requires javafx.fxml;
requires dev.wiing.gossip.lib;
+ requires annotations;
+ requires java.desktop;
exports dev.wiing.gossip.client;
opens dev.wiing.gossip.client to javafx.fxml;
opens dev.wiing.gossip.client.controllers to javafx.fxml;
+ opens dev.wiing.gossip.client.controllers.item to javafx.fxml;
}
\ No newline at end of file
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_0.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-0.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_0.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-0.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_1.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-1.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_1.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-1.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_2.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-2.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_2.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-2.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_3.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-3.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_3.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-3.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_4.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-4.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_4.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-4.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_5.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-5.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_5.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-5.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_6.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-6.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_6.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-6.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_7.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-7.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_7.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-7.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_8.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-8.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_8.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-8.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon_9.png b/Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-9.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/icons/icon_9.png
rename to Client/src/main/resources/dev/wiing/gossip/client/avatars/avatar-9.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/custom.css b/Client/src/main/resources/dev/wiing/gossip/client/custom.css
new file mode 100644
index 0000000..68a6b85
--- /dev/null
+++ b/Client/src/main/resources/dev/wiing/gossip/client/custom.css
@@ -0,0 +1,11 @@
+.gradient {
+ -fx-background-color: radial-gradient(radius 130%, ladder(#eee, -accent, #111), #111);
+}
+
+.topic-button.clickable:hover {
+ -fx-background-color: #fff1;
+}
+
+.topic-button.clickable.tag-min:hover {
+ -fx-background-color: ladder(hsb(0, 0%, 50%), -color, #0000);
+}
\ No newline at end of file
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/door-enter.png b/Client/src/main/resources/dev/wiing/gossip/client/icons/door-enter.png
new file mode 100644
index 0000000..f43fdca
Binary files /dev/null and b/Client/src/main/resources/dev/wiing/gossip/client/icons/door-enter.png differ
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/edit.png b/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-edit.png
similarity index 100%
rename from Client/src/main/resources/dev/wiing/gossip/client/edit.png
rename to Client/src/main/resources/dev/wiing/gossip/client/icons/icon-edit.png
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-hash.png b/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-hash.png
new file mode 100644
index 0000000..bdc27ed
Binary files /dev/null and b/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-hash.png differ
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-send-2.png b/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-send-2.png
new file mode 100644
index 0000000..46eda0d
Binary files /dev/null and b/Client/src/main/resources/dev/wiing/gossip/client/icons/icon-send-2.png differ
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/styling.css b/Client/src/main/resources/dev/wiing/gossip/client/styling.css
index 1545f52..ab0a444 100644
--- a/Client/src/main/resources/dev/wiing/gossip/client/styling.css
+++ b/Client/src/main/resources/dev/wiing/gossip/client/styling.css
@@ -19,10 +19,10 @@
-fx-background-radius: 16px;
}
-.list.container .viewport,
+.list.container > .viewport,
.section,
.pane.list.container {
- -fx-background-color: #19191d;
+ -fx-background-color: #88f1;
}
.list.container.dim .viewport {
@@ -34,12 +34,12 @@ SplitPane {
}
SplitPane .split-pane-divider {
- -fx-background-color: #19191d;
+ -fx-background-color: #88f1;
-fx-padding: 0.0 5.0 0.0 0.0;
}
.elevated {
- -fx-background-color: #25252c;
+ -fx-background-color: #88f2;
}
.border-radius {
@@ -52,18 +52,29 @@ SplitPane .split-pane-divider {
Label,
RadioButton,
-TextField {
+TextField,
+TextArea {
-fx-text-fill: #fff;
-fx-fill: #fff;
}
+TextArea,
+TextArea .scroll-pane {
+ -fx-background-color: transparent;
+}
+
+TextArea .scroll-pane .content {
+ -fx-background-color: red;
+}
+
ChoiceBox,
-TextField {
- -fx-background-color: #19191d;
+TextField,
+TextArea .scroll-pane .content {
+ -fx-background-color: #88f1;
}
ColorPicker {
- -fx-background-color: #29292F;
+ -fx-background-color: #88f1;
-fx-padding: 4px 14px;
}
@@ -77,6 +88,12 @@ ColorPicker {
-fx-fill: #556;
}
+.transparent,
+.transparent.list.container .viewport,
+TextArea.transparent .scroll-pane .content {
+ -fx-background-color: transparent;
+}
+
.accent {
-fx-text-fill: -accent;
}
@@ -85,15 +102,10 @@ Separator {
-fx-opacity: 0.1;
}
-Button {
- -fx-background-color: -accent;
- -fx-background-insets: 0;
- -fx-text-fill: white;
- -fx-padding: 4px 14px;
-}
-
-Button.gray {
+Button.secondary {
-fx-background-color: #4a4a55;
+ -fx-border-width: 0px;
+ -fx-text-fill: white;
}
Button,
@@ -101,6 +113,17 @@ Button,
-fx-cursor: hand;
}
+Button:hover {
+ -fx-background-color: ladder(hsb(0, 0%, 40%), -color, #0000);
+ -fx-text-fill: ladder(hsb(0, 0%, 50%), -color, #fff);
+ -fx-border-color: ladder(hsb(0, 0%, 50%), -color, #fff);
+}
+
+Button.secondary:hover {
+ -fx-background-color: #6c6c80;
+ -fx-text-fill: white;
+}
+
ChoiceBox {
-fx-background-insets: 0;
}
@@ -109,7 +132,9 @@ ChoiceBox {
-fx-font-family: "AXIS Extra Bold";
}
+Button,
Label.tag {
+ -color: -accent;
-fx-background-color: ladder(hsb(0, 0%, 70%), -color, #0000);
-fx-background-radius: 8px;
-fx-border-radius: 8px;
@@ -119,6 +144,21 @@ Label.tag {
-fx-padding: 6px 14px;
}
+.tag-min {
+ -color: -accent;
+ -fx-background-color: ladder(hsb(0, 0%, 60%), -color, #0000);
+ -fx-text-fill: -color;
+}
+
+Label.tag.small {
+ -fx-background-color: -color;
+ -fx-background-radius: 4px;
+ -fx-border-width: 0;
+ -fx-text-fill: white;
+ -fx-font-size: 10px;
+ -fx-padding: 2px 8px;
+}
+
CheckBox .box {
-fx-background-color: #25252c;
}
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/login-view.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/login-view.fxml
index 8d2ee5c..8c4c8f6 100644
--- a/Client/src/main/resources/dev/wiing/gossip/client/views/login-view.fxml
+++ b/Client/src/main/resources/dev/wiing/gossip/client/views/login-view.fxml
@@ -12,10 +12,11 @@
-
+
+
@@ -31,13 +32,13 @@
-
+
-
+
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/main-chat-view.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/main-chat-view.fxml
new file mode 100644
index 0000000..154e3ba
--- /dev/null
+++ b/Client/src/main/resources/dev/wiing/gossip/client/views/main-chat-view.fxml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/main-topic-item.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/main-topic-item.fxml
new file mode 100644
index 0000000..ebe4432
--- /dev/null
+++ b/Client/src/main/resources/dev/wiing/gossip/client/views/main-topic-item.fxml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/main-view.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/main-view.fxml
index b980339..0c43c30 100644
--- a/Client/src/main/resources/dev/wiing/gossip/client/views/main-view.fxml
+++ b/Client/src/main/resources/dev/wiing/gossip/client/views/main-view.fxml
@@ -3,26 +3,63 @@
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -30,26 +67,210 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java b/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java
index 9bea038..dea93e1 100644
--- a/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java
@@ -1,6 +1,8 @@
package dev.wiing.gossip.lib;
+import dev.wiing.gossip.lib.packets.CreateTopicPacket;
import dev.wiing.gossip.lib.packets.Packet;
+import dev.wiing.gossip.lib.packets.TopicAddedPacket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -36,6 +38,14 @@ public class PacketHandler {
}
}
+ public void runPacket(Packet packet) {
+ switch (packet.getType()) {
+ case TopicAddedPacket.TYPE:
+ runPacket(TopicAddedPacket.class, (TopicAddedPacket)packet);
+ break;
+ }
+ }
+
public Set> getListeners(Class packetClass) {
if (!listeners.containsKey(packetClass))
return null;
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java b/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java
index b6e2968..f6e58f1 100644
--- a/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java
@@ -1,9 +1,6 @@
package dev.wiing.gossip.lib;
-import dev.wiing.gossip.lib.packets.CredentialsPacket;
-import dev.wiing.gossip.lib.packets.RegisterPacket;
-import dev.wiing.gossip.lib.packets.Packet;
-import dev.wiing.gossip.lib.packets.TestPacket;
+import dev.wiing.gossip.lib.packets.*;
import java.io.*;
import java.nio.ByteBuffer;
@@ -20,6 +17,12 @@ public class PacketManager {
add(new RegisterPacket());
add(new CredentialsPacket());
+
+ add(new CreateTopicPacket());
+ add(new TopicAddedPacket());
+
+ add(new FetchUserPacket());
+ add(new UserDataPacket());
}
};
@@ -33,6 +36,9 @@ public class PacketManager {
packetMap.clear();
for (Packet packet : packetList) {
+ if (packetMap.containsKey(packet.getType())) {
+ throw new RuntimeException("Packet type already exists");
+ }
packetMap.put(packet.getType(), packet);
}
}
@@ -40,6 +46,8 @@ public class PacketManager {
public Packet readPacket(BufferedInputStream stream) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(stream.readNBytes(6));
+ if (buffer.capacity() < 6) return null;
+
short type = buffer.getShort();
int size = buffer.getInt();
@@ -71,4 +79,10 @@ public class PacketManager {
writePacket(outputStream, packet);
outputStream.flush();
}
+
+ public void replyPacket(Packet source, Packet reply) throws IOException {
+ BufferedOutputStream outputStream = new BufferedOutputStream(source.getSource().getOutputStream());
+ writePacket(outputStream, reply);
+ outputStream.flush();
+ }
}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/data/StringType.java b/Lib/src/main/java/dev/wiing/gossip/lib/data/StringType.java
index 0126264..d6b5178 100644
--- a/Lib/src/main/java/dev/wiing/gossip/lib/data/StringType.java
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/data/StringType.java
@@ -7,7 +7,7 @@ import java.nio.charset.StandardCharsets;
public class StringType implements DataType {
- private String value;
+ private String value = "";
@Override
public String getValue() {
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java
new file mode 100644
index 0000000..a82ebad
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java
@@ -0,0 +1,17 @@
+package dev.wiing.gossip.lib.models;
+
+import java.time.LocalDateTime;
+
+public class Message {
+ private User author;
+ private String message;
+ private LocalDateTime postTime;
+
+ public Message(User author, String message, LocalDateTime postTime) {
+ this.author = author;
+ this.message = message;
+ this.postTime = postTime;
+ }
+
+
+}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java
new file mode 100644
index 0000000..57547c7
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java
@@ -0,0 +1,96 @@
+package dev.wiing.gossip.lib.models;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Topic {
+ private PropertyChangeSupport changeSupport;
+
+ private final long id;
+ private String name;
+ private String description;
+ private final User host;
+ private final Map users = new ConcurrentHashMap<>();
+ private short color;
+
+ public Topic(long id, String name, String description, User host, short color) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.host = host;
+ this.color = color;
+
+ this.changeSupport = new PropertyChangeSupport(this);
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ String prev = this.name;
+ this.name = name;
+ this.changeSupport.firePropertyChange("name", prev, name);
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ String prev = this.description;
+ this.description = description;
+ this.changeSupport.firePropertyChange("description", prev, description);
+ }
+
+ public User getHost() {
+ return host;
+ }
+
+ public short getColor() {
+ return color;
+ }
+
+ public void setColor(short color) {
+ short prev = this.color;
+ this.color = color;
+ this.changeSupport.firePropertyChange("color", prev, color);
+ }
+
+ public Map getUsersReadOnly() {
+ return Collections.unmodifiableMap(users);
+ }
+
+ public void addUser(User user) {
+ if (this.users.containsKey(user.getUserID())) return;
+
+ this.users.put(user.getUserID(), user);
+ this.changeSupport.firePropertyChange("userAdd", null, getUsersReadOnly());
+ }
+
+ public void removeUser(User user) {
+ if (!this.users.containsKey(user.getUserID())) return;
+
+ this.users.remove(user.getUserID());
+ this.changeSupport.firePropertyChange("userRemove", null, getUsersReadOnly());
+ }
+
+ public boolean hasUser(User user) {
+ return this.users.containsKey(user.getUserID());
+ }
+
+ public void addChangeListener(PropertyChangeListener listener) {
+ this.changeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removeChangeListener(PropertyChangeListener listener) {
+ this.changeSupport.removePropertyChangeListener(listener);
+ }
+}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/User.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/User.java
index 4940fca..78d73d7 100644
--- a/Lib/src/main/java/dev/wiing/gossip/lib/models/User.java
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/User.java
@@ -7,12 +7,12 @@ public class User {
private final PropertyChangeSupport change;
private String username;
- private int iconID;
+ private int avatarID;
private long userID;
- public User(String username, int iconID, long userID) {
+ public User(String username, int avatarID, long userID) {
this.username = username;
- this.iconID = iconID;
+ this.avatarID = avatarID;
this.userID = userID;
this.change = new PropertyChangeSupport(this);
@@ -22,20 +22,24 @@ public class User {
return username;
}
+ public String getUsernameDisplay() {
+ return "@ " + getUsername();
+ }
+
public void setUsername(String username) {
String previous = this.username;
this.username = username;
change.firePropertyChange("username", previous, username);
}
- public int getIconID() {
- return iconID;
+ public int getAvatarID() {
+ return avatarID;
}
- public void setIconID(int iconID) {
- int previous = this.iconID;
- this.iconID = iconID;
- change.firePropertyChange("iconID", previous, iconID);
+ public void setAvatarID(int avatarID) {
+ int previous = this.avatarID;
+ this.avatarID = avatarID;
+ change.firePropertyChange("iconID", previous, avatarID);
}
public long getUserID() {
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/CreateTopicPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/CreateTopicPacket.java
new file mode 100644
index 0000000..64123c3
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/CreateTopicPacket.java
@@ -0,0 +1,72 @@
+package dev.wiing.gossip.lib.packets;
+
+import dev.wiing.gossip.lib.data.StringType;
+
+import java.nio.ByteBuffer;
+
+public class CreateTopicPacket extends Packet {
+
+ public static final short TYPE = 0x11;
+ public static final int LENGTH = 0x022;
+
+ private byte[] userSecret = new byte[32];
+ private final StringType topicName = new StringType();
+ private final StringType topicDescription = new StringType();
+
+ public CreateTopicPacket() {
+ super(TYPE, LENGTH);
+ }
+
+ private void updateLength() {
+ setLength(
+ 32 + // User secret
+ topicName.getLength() +
+ topicDescription.getLength()
+ );
+ }
+
+ public byte[] getUserSecret() {
+ return userSecret;
+ }
+
+ public void setUserSecret(byte[] userSecret) {
+ this.userSecret = userSecret;
+ }
+
+ public String getTopicName() {
+ return topicName.getValue();
+ }
+
+ public String getTopicDescription() {
+ return topicDescription.getValue();
+ }
+
+ public void setTopicName(String topicName) {
+ this.topicName.setValue(topicName);
+ updateLength();
+ }
+
+ public void setTopicDescription(String topicDescription) {
+ this.topicDescription.setValue(topicDescription);
+ updateLength();
+ }
+
+ @Override
+ public Packet readBytes(ByteBuffer buffer, int size) {
+ CreateTopicPacket packet = new CreateTopicPacket();
+ packet.setLength(size);
+
+ buffer.get(packet.userSecret);
+ packet.topicName.setBytes(buffer);
+ packet.topicDescription.setBytes(buffer);
+
+ return packet;
+ }
+
+ @Override
+ public void writeBytes(ByteBuffer buffer) {
+ buffer.put(userSecret);
+ buffer.put(topicName.getBytes());
+ buffer.put(topicDescription.getBytes());
+ }
+}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/FetchUserPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/FetchUserPacket.java
new file mode 100644
index 0000000..00fad56
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/FetchUserPacket.java
@@ -0,0 +1,37 @@
+package dev.wiing.gossip.lib.packets;
+
+import java.nio.ByteBuffer;
+
+public class FetchUserPacket extends Packet {
+
+ public static final short TYPE = 0x21;
+ public static final int LENGTH = 0x008;
+
+ private long userID;
+
+ public FetchUserPacket() {
+ super(TYPE, LENGTH);
+ }
+
+ public long getUserID() {
+ return userID;
+ }
+
+ public void setUserID(long userID) {
+ this.userID = userID;
+ }
+
+ @Override
+ public Packet readBytes(ByteBuffer buffer, int size) {
+ FetchUserPacket packet = new FetchUserPacket();
+
+ packet.userID = buffer.getLong();
+
+ return packet;
+ }
+
+ @Override
+ public void writeBytes(ByteBuffer buffer) {
+ buffer.putLong(userID);
+ }
+}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/RegisterPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/RegisterPacket.java
index 31a6d27..4bb0431 100644
--- a/Lib/src/main/java/dev/wiing/gossip/lib/packets/RegisterPacket.java
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/RegisterPacket.java
@@ -7,10 +7,10 @@ import java.nio.ByteBuffer;
public class RegisterPacket extends Packet {
public static final short TYPE = 0x02;
- public static final int SIZE = 0x0001;
+ public static final int SIZE = 0x0005;
private final StringType username = new StringType();
- private char iconID;
+ private byte avatarID;
public RegisterPacket() {
super(TYPE, SIZE);
@@ -22,6 +22,7 @@ public class RegisterPacket extends Packet {
packet.setLength(size);
packet.username.setBytes(buffer);
+ packet.avatarID = buffer.get();
return packet;
}
@@ -29,24 +30,23 @@ public class RegisterPacket extends Packet {
@Override
public void writeBytes(ByteBuffer buffer) {
buffer.put(username.getBytes());
+ buffer.put(avatarID);
}
public String getUsername() {
return username.getValue();
}
- public RegisterPacket setUsername(String username) {
+ public void setUsername(String username) {
this.username.setValue(username);
this.setLength(1 + this.username.getLength());
- return this;
}
- public char getIconID() {
- return iconID;
+ public byte getAvatarID() {
+ return avatarID;
}
- public RegisterPacket setIconID(char iconID) {
- this.iconID = iconID;
- return this;
+ public void setAvatarID(byte avatarID) {
+ this.avatarID = avatarID;
}
}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicAddedPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicAddedPacket.java
new file mode 100644
index 0000000..81dd203
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicAddedPacket.java
@@ -0,0 +1,96 @@
+package dev.wiing.gossip.lib.packets;
+
+import dev.wiing.gossip.lib.data.StringType;
+
+import java.nio.ByteBuffer;
+
+public class TopicAddedPacket extends Packet {
+
+ public static final short TYPE = 0x12;
+ public static final int LENGTH = 0x014;
+
+ private long topicID;
+ private long hostID;
+ private final StringType topicName = new StringType();
+ private final StringType topicDescription = new StringType();
+ private short topicColor;
+
+ public TopicAddedPacket() {
+ super(TYPE, LENGTH);
+ }
+
+ private void updateLength() {
+ setLength(
+ 8 + // topicID
+ 8 + // hostID
+ topicName.getLength() +
+ topicDescription.getLength() +
+ 2 // topicColor
+ );
+ }
+
+ public long getTopicID() {
+ return topicID;
+ }
+
+ public void setTopicID(long topicID) {
+ this.topicID = topicID;
+ }
+
+ public long getHostID() {
+ return hostID;
+ }
+
+ public void setHostID(long hostID) {
+ this.hostID = hostID;
+ }
+
+ public String getTopicName() {
+ return topicName.getValue();
+ }
+
+ public void setTopicName(String topicName) {
+ this.topicName.setValue(topicName);
+ updateLength();
+ }
+
+ public String getTopicDescription() {
+ return topicDescription.getValue();
+ }
+
+ public void setTopicDescription(String topicDescription) {
+ this.topicDescription.setValue(topicDescription);
+ updateLength();
+ }
+
+ public short getTopicColor() {
+ return topicColor;
+ }
+
+ public void setTopicColor(short topicColor) {
+ this.topicColor = topicColor;
+ }
+
+ @Override
+ public Packet readBytes(ByteBuffer buffer, int size) {
+ TopicAddedPacket packet = new TopicAddedPacket();
+ setLength(size);
+
+ packet.topicID = buffer.getLong();
+ packet.hostID = buffer.getLong();
+ packet.topicName.setBytes(buffer);
+ packet.topicDescription.setBytes(buffer);
+ packet.topicColor = buffer.getShort();
+
+ return packet;
+ }
+
+ @Override
+ public void writeBytes(ByteBuffer buffer) {
+ buffer.putLong(topicID);
+ buffer.putLong(hostID);
+ buffer.put(topicName.getBytes());
+ buffer.put(topicDescription.getBytes());
+ buffer.putShort(topicColor);
+ }
+}
diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/UserDataPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/UserDataPacket.java
new file mode 100644
index 0000000..a77da12
--- /dev/null
+++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/UserDataPacket.java
@@ -0,0 +1,63 @@
+package dev.wiing.gossip.lib.packets;
+
+import dev.wiing.gossip.lib.data.StringType;
+
+import java.nio.ByteBuffer;
+
+public class UserDataPacket extends Packet {
+
+ public static final short TYPE = 0x22;
+ public static final int LENGTH = 0x00a;
+
+ private long userID;
+ private final StringType username = new StringType();
+ private byte avatarID;
+
+ public UserDataPacket() {
+ super(TYPE, LENGTH);
+ }
+
+ public long getUserID() {
+ return userID;
+ }
+
+ public void setUserID(long userID) {
+ this.userID = userID;
+ }
+
+ public String getUsername() {
+ return username.getValue();
+ }
+
+ public void setUsername(String username) {
+ this.username.setValue(username);
+ setLength(9 + this.username.getLength());
+ }
+
+ public byte getAvatarID() {
+ return avatarID;
+ }
+
+ public void setAvatarID(byte avatarID) {
+ this.avatarID = avatarID;
+ }
+
+ @Override
+ public Packet readBytes(ByteBuffer buffer, int size) {
+ UserDataPacket packet = new UserDataPacket();
+ packet.setLength(size);
+
+ packet.userID = buffer.getLong();
+ packet.username.setBytes(buffer);
+ packet.avatarID = buffer.get();
+
+ return packet;
+ }
+
+ @Override
+ public void writeBytes(ByteBuffer buffer) {
+ buffer.putLong(userID);
+ buffer.put(username.getBytes());
+ buffer.put(avatarID);
+ }
+}
diff --git a/Server/src/main/java/dev/wiing/gossip/server/Database.java b/Server/src/main/java/dev/wiing/gossip/server/Database.java
index d8bea91..f58c3a0 100644
--- a/Server/src/main/java/dev/wiing/gossip/server/Database.java
+++ b/Server/src/main/java/dev/wiing/gossip/server/Database.java
@@ -1,16 +1,23 @@
package dev.wiing.gossip.server;
import dev.wiing.gossip.lib.models.SecretUser;
+import dev.wiing.gossip.lib.models.Topic;
import dev.wiing.gossip.lib.models.User;
+import java.net.Socket;
import java.security.SecureRandom;
import java.util.*;
public class Database {
+ private int userIdCounter = 1;
private final Map users = Collections.synchronizedMap(new HashMap<>());
- private final Map usersBySecret = Collections.synchronizedMap(new HashMap<>());
+ private final Map usersBySecret = Collections.synchronizedMap(new HashMap<>());
+ private final Map userSockets = Collections.synchronizedMap(new HashMap<>());
private final Set usedUsernames = Collections.synchronizedSet(new HashSet<>());
+ private int topicIdCounter = 1;
+ private final Map topics = Collections.synchronizedMap(new HashMap<>());
+
private Database() {
}
@@ -21,18 +28,19 @@ public class Database {
return instance;
}
- public SecretUser registerUser(String username, char iconID) {
+ public SecretUser registerUser(String username, byte iconID, Socket socket) {
SecureRandom random = new SecureRandom();
byte[] secret = new byte[32];
random.nextBytes(secret);
- long userID = random.nextLong();
+ long userID = userIdCounter++;
User user = new User(username, iconID, userID);
users.put(userID, user);
- usersBySecret.put(secret, user);
+ userSockets.put(userID, socket);
+ usersBySecret.put(new String(secret), user);
usedUsernames.add(username);
System.out.println("Created user: " + username + " #" + Long.toUnsignedString(userID));
@@ -55,6 +63,23 @@ public class Database {
return usersBySecret.getOrDefault(secret, null);
}
+ public Collection getUsers() {
+ return Collections.unmodifiableCollection(users.values());
+ }
+
+ public Socket getUserSocket(long uID) {
+ return userSockets.getOrDefault(uID, null);
+ }
+
+ public User getUserOfSocket(Socket socket) {
+ return userSockets.entrySet().stream()
+ .filter(entry -> entry.getValue().equals(socket))
+ .findFirst()
+ .map(Map.Entry::getKey)
+ .map(this::getUserByID)
+ .orElse(null);
+ }
+
public void removeUser(User user) {
users.remove(user.getUserID());
@@ -64,6 +89,36 @@ public class Database {
.findFirst()
.ifPresent(usersBySecret::remove);
+ userSockets.remove(user.getUserID());
+
usedUsernames.remove(user.getUsername());
}
+
+ public Topic createTopic(byte[] userSecret, String topicName, String topicDescription) {
+ if (!usersBySecret.containsKey(new String(userSecret))) return null;
+
+ User user = usersBySecret.get(new String(userSecret));
+
+ short colorHue = (short)Math.abs((new Random().nextInt(360)));
+
+ Topic topic = new Topic(
+ topicIdCounter++,
+ topicName,
+ topicDescription,
+ user,
+ colorHue
+ );
+
+ topics.put(topic.getId(), topic);
+
+ return topic;
+ }
+
+ public Topic getTopic(long topicID) {
+ return topics.getOrDefault(topicID, null);
+ }
+
+ public void removeTopic(long topicID) {
+ topics.remove(topicID);
+ }
}
diff --git a/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java b/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java
index b1474c0..c221236 100644
--- a/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java
+++ b/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java
@@ -1,11 +1,9 @@
package dev.wiing.gossip.server;
import dev.wiing.gossip.lib.models.SecretUser;
+import dev.wiing.gossip.lib.models.Topic;
import dev.wiing.gossip.lib.models.User;
-import dev.wiing.gossip.lib.packets.CredentialsPacket;
-import dev.wiing.gossip.lib.packets.RegisterPacket;
-import dev.wiing.gossip.lib.packets.Packet;
-import dev.wiing.gossip.lib.packets.TestPacket;
+import dev.wiing.gossip.lib.packets.*;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -31,6 +29,14 @@ public record UserSocket(Socket socket) implements Runnable {
case RegisterPacket.TYPE:
onRegisterUser((RegisterPacket) packet);
break;
+
+ case CreateTopicPacket.TYPE:
+ onCreateTopic((CreateTopicPacket) packet);
+ break;
+
+ case FetchUserPacket.TYPE:
+ onFetchUser((FetchUserPacket) packet);
+ break;
}
} catch (SocketException e) {
break;
@@ -38,17 +44,59 @@ public record UserSocket(Socket socket) implements Runnable {
throw new RuntimeException(e);
}
}
+
+ // Connection lost
+ User user = Database.getInstance().getUserOfSocket(socket);
+ Database.getInstance().removeUser(user);
}
private void onRegisterUser(RegisterPacket packet) {
- SecretUser user = Database.getInstance().registerUser(packet.getUsername(), packet.getIconID());
+ SecretUser user = Database.getInstance().registerUser(packet.getUsername(), packet.getAvatarID(), packet.getSource());
CredentialsPacket creds = new CredentialsPacket();
creds.setSecret(user.getUserSecret());
creds.setUID(user.getUserID());
try {
- Globals.getPacketManager().writePacket(packet.getSource().getOutputStream(), creds);
+ Globals.getPacketManager().replyPacket(packet, creds);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void onCreateTopic(CreateTopicPacket packet) {
+ Topic topic = Database.getInstance().createTopic(packet.getUserSecret(), packet.getTopicName(), packet.getTopicDescription());
+
+ if (topic == null) return;
+
+ TopicAddedPacket added = new TopicAddedPacket();
+ added.setTopicID(topic.getId());
+ added.setHostID(topic.getHost().getUserID());
+ added.setTopicName(topic.getName());
+ added.setTopicDescription(topic.getDescription());
+ added.setTopicColor(topic.getColor());
+
+ for (User user : Database.getInstance().getUsers()) {
+ Socket socket = Database.getInstance().getUserSocket(user.getUserID());
+
+ try {
+ Globals.getPacketManager().writePacket(socket.getOutputStream(), added);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void onFetchUser(FetchUserPacket packet) {
+ User user = Database.getInstance().getUserByID(packet.getUserID());
+
+ UserDataPacket resp = new UserDataPacket();
+ resp.setUserID(user.getUserID());
+ resp.setUsername(user.getUsername());
+ resp.setAvatarID((byte) user.getAvatarID());
+
+ try {
+ Globals.getPacketManager().replyPacket(packet, resp);
} catch (IOException e) {
throw new RuntimeException(e);
}