Add: basic chat functionality
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* responsible for the transferring of messages from the server to the ChatModel
|
||||
* or from the ChatViewController to the server
|
||||
*/
|
||||
public class ChatController {
|
||||
|
||||
private final String username;
|
||||
private final ClientService clientService;
|
||||
|
||||
private ChatModel chatModel;
|
||||
|
||||
private int game_id;
|
||||
|
||||
public ChatController(String username, ClientService clientService) {
|
||||
this.username = username;
|
||||
this.clientService = clientService;
|
||||
}
|
||||
|
||||
public void createChat(int game_id) {
|
||||
chatModel = new ChatModel(ChatModel.ChatType.GLOBAL, username);
|
||||
this.game_id = game_id;
|
||||
}
|
||||
|
||||
public ChatModel getChatModel() {
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send a message, the ChatViewController received to the server
|
||||
*/
|
||||
public void sendMessage(String msg, String username) {
|
||||
Message message = new Message(Message.MessageType.GLOBAL, 0, username, null, msg);
|
||||
onSendToNetwork(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all messages from the server
|
||||
*/
|
||||
public Boolean receiveMessage() {
|
||||
List<Message> newMessages = clientService.getMessages();
|
||||
if (!newMessages.isEmpty()) {
|
||||
for (Message msg : newMessages) {
|
||||
chatModel.addMessage(msg);
|
||||
}
|
||||
return true;
|
||||
} else { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send a message to the server
|
||||
* @param message
|
||||
*/
|
||||
public void onSendToNetwork(Message message) {
|
||||
clientService.sendMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* ChatModel, stores the data for a specific chat
|
||||
*
|
||||
* Holds the current state of a chat
|
||||
*/
|
||||
|
||||
public class ChatModel {
|
||||
|
||||
public ArrayList<Message> messages;
|
||||
|
||||
public ChatType chattype;
|
||||
|
||||
public String username;
|
||||
|
||||
public int count;
|
||||
|
||||
public enum ChatType {
|
||||
GLOBAL,
|
||||
LOBBY,
|
||||
WHISPER
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ChatModel, given a username of the client
|
||||
* @param chattype
|
||||
* @param username
|
||||
*/
|
||||
|
||||
public ChatModel(ChatType chattype, String username) {
|
||||
this.messages = new ArrayList<Message>();
|
||||
this.chattype = chattype;
|
||||
this.username = username;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* method, used by the ChatViewController, to access all new messages, that are stored in the ChatModel
|
||||
*/
|
||||
public synchronized String viewNextMessage() {
|
||||
count--;
|
||||
Message msg = messages.getLast();
|
||||
return String.format("[%s] %s: %s", msg.timestamp, msg.user, msg.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new message
|
||||
* method used by the ChatController
|
||||
* @param msg
|
||||
*/
|
||||
public synchronized void addMessage(Message msg) {
|
||||
messages.add(msg);
|
||||
count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to send all current messages to the ChatViewController, if needed
|
||||
*/
|
||||
public void addCompleteChat() {
|
||||
for (int i = 0; i < this.messages.size(); i++) {
|
||||
Message msg = this.messages.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Message Object for internal handling of Chat-Messages
|
||||
* TODO: Should be used on both sides of the network
|
||||
*/
|
||||
|
||||
public class Message {
|
||||
private final MessageType type;
|
||||
private final String message;
|
||||
public String user;
|
||||
public String timestamp;
|
||||
public int game_id = 0;
|
||||
public String target = null;
|
||||
public enum MessageType {
|
||||
GLOBAL, LOBBY, WHISPER
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for creating Messages with all information given
|
||||
* @param type - Either global, local or whisper
|
||||
* @param game_id - lobby id, or null, if the type is global
|
||||
* @param user - username
|
||||
* @param target - username of the target user, for whisper chat
|
||||
* @param message
|
||||
*/
|
||||
|
||||
public Message(MessageType type, int game_id, String user, String target, String timestamp, String message) {
|
||||
this.type = type;
|
||||
this.game_id = game_id;
|
||||
this.user = user;
|
||||
this.target = target;
|
||||
this.timestamp = timestamp;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for creating the Messages of the current user, using this client -> time of writing is being recorded
|
||||
* @param type - Either global, local or whisper
|
||||
* @param game_id - lobby id, or null, if the type is global
|
||||
* @param user - username
|
||||
* @param target - username of the target user, for whisper chat
|
||||
* @param message
|
||||
*/
|
||||
|
||||
public Message(MessageType type, int game_id, String user, String target, String message) {
|
||||
this.type = type;
|
||||
this.game_id = game_id;
|
||||
this.user = user;
|
||||
this.target = target;
|
||||
this.message = message;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
this.timestamp = now.format(formatter);
|
||||
}
|
||||
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* Method to test the system
|
||||
* @return - String representation of the Message instance, as for example "player1: Hello World"
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s: %s", this.user, this.message);
|
||||
}
|
||||
*/
|
||||
/**
|
||||
* Method to create the request representation of the message object, to be sent to the server
|
||||
* @return - request as specified in the network protocol, as String
|
||||
*/
|
||||
public String toArgsString() {
|
||||
return String.format("TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s",
|
||||
this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pattern, to analyze the response String with the given parameters
|
||||
*/
|
||||
public static Pattern msgRex = Pattern.compile(
|
||||
"TYPE=(?<type>\\w+) GAME=(?<game>\\w+) USER=(?<user>\\w+) TARGET=(?<target>\\w+) TIME=(?<time>[0-9:.]+) TEXT=(?<text>.*)$");
|
||||
|
||||
/**
|
||||
* Method to create a Message Object, from the information given by the String
|
||||
* @param response - String that got sent as a response from the server
|
||||
* @return - New Message Object
|
||||
*/
|
||||
public static Message toMessage(String response) {
|
||||
Matcher m = msgRex.matcher(response);
|
||||
if (! m.matches()) {
|
||||
throw new RuntimeException("Can not parse message: '" + response+"'");
|
||||
}
|
||||
String typeString=m.group("type");
|
||||
|
||||
switch (typeString) {
|
||||
case "GLOBAL":
|
||||
return new Message(MessageType.GLOBAL,
|
||||
0,
|
||||
m.group("user"),
|
||||
null,
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
case "LOBBY":
|
||||
return new Message(MessageType.LOBBY,
|
||||
Integer.parseInt(m.group("game")),
|
||||
m.group("user"),
|
||||
null,
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
case "WHISPER":
|
||||
return new Message(MessageType.WHISPER,
|
||||
Integer.parseInt(m.group("game")),
|
||||
m.group("user"),
|
||||
m.group("target"),
|
||||
m.group("time"),
|
||||
m.group("text"));
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Unknown message type " + typeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.network;
|
||||
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.RawPacket;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* Responsible for the transferring of the data from the Client to the Server and the other way around
|
||||
*/
|
||||
|
||||
public class ClientService {
|
||||
|
||||
private final TcpTransport clienttcptransport;
|
||||
private final Socket socket;
|
||||
|
||||
private final String ip;
|
||||
private final int port;
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
public static ArrayList<String> response;
|
||||
private final AtomicInteger idGenerator;
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the Outputstream and a pool of threads to send requests and receive responses.
|
||||
* @param ip : ip-adress of the server
|
||||
* @param port : port of the server
|
||||
*/
|
||||
|
||||
public ClientService(String ip, int port) {
|
||||
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.idGenerator = new AtomicInteger(0);
|
||||
this.logger = LogManager.getLogger(NetworkManager.class);
|
||||
|
||||
try {
|
||||
socket = new Socket(this.ip, this.port);
|
||||
logger.info("Connected at port " + this.port);
|
||||
clienttcptransport = new TcpTransport(socket);
|
||||
}
|
||||
catch (IOException i) {
|
||||
throw new RuntimeException(i);
|
||||
}
|
||||
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ping to the server
|
||||
* Returns nothing, the method processMessage() already handles the cases "+OK" or "-ERROR"
|
||||
*/
|
||||
|
||||
public void ping() {
|
||||
processMessage("PING");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a login request to the server to create a new user on the server
|
||||
* @param username
|
||||
* @return - a new username, if the same username is already used by someone else
|
||||
*/
|
||||
|
||||
public String login(String username) {
|
||||
String msg = "LOGIN USERNAME=" + username;
|
||||
return processMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Request to get the number of Messages currently in the Queue for the client.
|
||||
* Then proceeds, if needed, to get the messages by sending
|
||||
*/
|
||||
|
||||
public List<Message> getMessages() {
|
||||
String countStr = processMessage("GET_MESSAGE_COUNT");
|
||||
int count = Integer.parseInt(countStr);
|
||||
System.out.println("Got " + count + " messages");
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String message = processMessage("GET_NEXT_MESSAGE");
|
||||
if (message != null) {
|
||||
Message message1 = Message.toMessage(message);
|
||||
messages.add(message1);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Request to the Server containing all relevant information of the message the client wrote.
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
|
||||
public void sendMessage(Message message) {
|
||||
String request = "SEND_MESSAGE " + message.toArgsString();
|
||||
processMessage(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Requests to the server and waits for the response
|
||||
* If the response is "+OK" it proceeds normal
|
||||
* If the response "-ERROR" it throws a runtime exception
|
||||
* @param message
|
||||
* @return - The response as a string, if it has to be returned
|
||||
* (+OK will not be returned)
|
||||
*/
|
||||
|
||||
private String processMessage(String message) {
|
||||
AtomicReference<String> response = new AtomicReference<>();
|
||||
sendRequest(() -> {
|
||||
try {
|
||||
writeToTransport(message);
|
||||
String responseLine=null;
|
||||
do {
|
||||
responseLine = clienttcptransport.read().payload();
|
||||
System.out.println("Raw message '" + responseLine + "'");
|
||||
if ("+OK".equals(responseLine)) {
|
||||
return;
|
||||
} else if (("-ERROR").equals(responseLine)) {
|
||||
throw new RuntimeException(responseLine);
|
||||
}
|
||||
response.set(responseLine);
|
||||
} while(true);
|
||||
} catch (Exception e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
});
|
||||
return response.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
|
||||
private void sendRequest(Runnable request) {
|
||||
Future<?> future = executor.submit(request);
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw getRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Runtime Exceptions thrown by the sendRequest method
|
||||
* @param e - an Exception
|
||||
* @return - a Runtime Exception
|
||||
*/
|
||||
|
||||
private static RuntimeException getRuntimeException(Exception e) {
|
||||
Throwable reason = e.getCause();
|
||||
RuntimeException re;
|
||||
if (reason == null) {
|
||||
reason = e;
|
||||
} else if (reason instanceof RuntimeException rte) {
|
||||
re = rte;
|
||||
}
|
||||
re = new RuntimeException(reason);
|
||||
return re;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Socket and shuts down the Threadpool associated with that Socket-Connection.
|
||||
*/
|
||||
|
||||
public void closeSocket() {
|
||||
try {
|
||||
executor.shutdown();
|
||||
clienttcptransport.close();
|
||||
socket.close();
|
||||
} catch (IOException j) {
|
||||
System.out.println(j);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to write with the tcp transport to the server
|
||||
* @param s - Message to be sent
|
||||
* @throws IOException
|
||||
*/
|
||||
|
||||
private void writeToTransport(String s) throws IOException {
|
||||
int id = this.idGenerator.incrementAndGet();
|
||||
this.clienttcptransport.write(new RawPacket(id, s));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Controller-Klasse für das Chat-System innerhalb der Spieloberfläche.
|
||||
*
|
||||
* <p>Verwaltet das Anzeigen von Chatnachrichten, das Eingabefeld für eigene Nachrichten sowie den
|
||||
* Senden-Button. Unterstützt drei Arten von Nachrichten: - Player-to-Player (Privat) - Lobby-Chat
|
||||
* (Raum) - Globaler Chat (Serverweit)
|
||||
*
|
||||
* <p>Nachrichten werden in einem {@link VBox}-Container als {@link Label} angezeigt. Eigene
|
||||
* Nachrichten werden über {@link #onSendToNetwork(String)} an das Netzwerkprotokoll weitergeleitet,
|
||||
* während eingehende Nachrichten über {@link #receiveMessage(String, String)} verarbeitet und
|
||||
* angezeigt werden.
|
||||
*
|
||||
* <p>Hinweis: Einige TODOs stehen in der zugehörigen FXML-Datei
|
||||
*/
|
||||
public class ChatController {
|
||||
|
||||
@FXML private VBox chatVBox;
|
||||
|
||||
@FXML private TextField inputField;
|
||||
|
||||
@FXML private Button sendButton;
|
||||
|
||||
@FXML private ScrollPane chatScrollPane;
|
||||
|
||||
private static final int CHAT_PADDING = 20;
|
||||
|
||||
/** Initialisiert den ChatController nach dem Laden der FXML. */
|
||||
public void initialize() {
|
||||
inputField.setOnAction(event -> sendMessage());
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode wird vom Senden-Button oder Enter ausgelöst. Sie gibt die eigene Nachricht an
|
||||
* das Netzwerkprotokoll weiter.
|
||||
*/
|
||||
@FXML
|
||||
private void sendMessage() {
|
||||
String message = inputField.getText().trim();
|
||||
if (!message.isEmpty()) {
|
||||
inputField.clear();
|
||||
|
||||
// Hier wird die eigene Nachricht ans Netzwerkprotokoll übergeben
|
||||
onSendToNetwork(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Funktion muss vom Netzwerkprotokoll aufgerufen werden, wenn eine neue Nachricht von
|
||||
* einem anderen Spieler kommt.
|
||||
*
|
||||
* @param player Name des Spielers
|
||||
* @param message Nachricht des Spielers
|
||||
*/
|
||||
public void receiveMessage(String player, String message) {
|
||||
String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
|
||||
Label label = new Label("[" + time + "] " + player + ": " + message);
|
||||
label.getStyleClass().add("info-text");
|
||||
label.setWrapText(true); // Zeilenumbruch aktivieren
|
||||
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
|
||||
chatVBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittstelle zum Netzwerkprotokoll. Diese Funktion wird automatisch aufgerufen, wenn der
|
||||
* Benutzer eine eigene Nachricht sendet.
|
||||
*
|
||||
* @param message Nachricht, die der Benutzer abgeschickt hat
|
||||
*/
|
||||
public void onSendToNetwork(String message) {
|
||||
// TODO: Netzwerkcode einfügen
|
||||
receiveMessage("Du", message);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatModel;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Responsible for the presentation of the ChatModel to the Client
|
||||
*/
|
||||
|
||||
public class ChatViewController {
|
||||
|
||||
private final ChatModel chatmodel;
|
||||
private final String username;
|
||||
private final ChatController controller;
|
||||
private final Timer timer;
|
||||
|
||||
@FXML private VBox chatVBox;
|
||||
|
||||
@FXML private TextField inputField;
|
||||
|
||||
@FXML private ScrollPane chatScrollPane;
|
||||
|
||||
@FXML private Button sendButton;
|
||||
|
||||
@FXML private Button refreshButton;
|
||||
|
||||
private static final int CHAT_PADDING = 20;
|
||||
|
||||
public ChatViewController() {
|
||||
this(null, null ,null);
|
||||
}
|
||||
|
||||
public ChatViewController(String username, ChatModel chatmodel, ChatController controller) {
|
||||
this.username = username;
|
||||
this.chatmodel = chatmodel;
|
||||
this.controller = controller;
|
||||
this.timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.err.println("tick");
|
||||
if (controller.receiveMessage()) {
|
||||
showMessage();
|
||||
}
|
||||
}
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
inputField.setOnAction(event -> sendMessage());
|
||||
sendButton.setOnAction(event -> sendMessage());
|
||||
refreshButton.setOnAction(event -> showMessage());
|
||||
chatScrollPane.vvalueProperty().bind(chatVBox.heightProperty());
|
||||
}
|
||||
|
||||
public void showMessage() {
|
||||
while (chatmodel.count > 0) {
|
||||
String msg = chatmodel.viewNextMessage();
|
||||
Label label = new Label(msg);
|
||||
label.getStyleClass().add("info-text");
|
||||
label.setWrapText(true);
|
||||
label.maxWidthProperty().bind(chatVBox.widthProperty().subtract(CHAT_PADDING));
|
||||
chatVBox.getChildren().add(label);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sendMessage() {
|
||||
String message = inputField.getText().trim();
|
||||
if (!message.isEmpty()) {
|
||||
inputField.clear();
|
||||
this.controller.sendMessage(message, username);
|
||||
}
|
||||
}
|
||||
|
||||
public void endController() { timer.cancel(); }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<!--
|
||||
Dieses Fenster wird später mit den Serveranfragen verknüpft,
|
||||
um Nachrichten von Mitspielern und Systemmeldungen in Echtzeit anzuzeigen.
|
||||
|
||||
Der Chat unterscheidet intern zwischen:
|
||||
- Player-to-Player (Privater 2-Personen-Chat)
|
||||
- Lobby-Chat (Aktueller Raum)
|
||||
- Globaler Chat (Gesamter Server)
|
||||
|
||||
Features, die später implementiert werden sollen:
|
||||
- Schicke Chat-Bubbles mit Namen und Uhrzeit.
|
||||
-->
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatViewController"
|
||||
alignment="CENTER"
|
||||
stylesheets="@chatui.css">
|
||||
|
||||
<!-- Innenabstand: Schafft oben, rechts und unten 30 Pixel Platz, links nur 10 Pixel (asymmetrisch) -->
|
||||
<padding>
|
||||
<Insets top="30" right="30" bottom="30" left="10"/>
|
||||
</padding>
|
||||
|
||||
<children>
|
||||
<VBox VBox.vgrow="ALWAYS"
|
||||
styleClass="chat-box"
|
||||
spacing="10"
|
||||
maxWidth="Infinity">
|
||||
|
||||
<children>
|
||||
<Label text="== CHAT ==" styleClass="chat-header" alignment="CENTER" maxWidth="Infinity"/>
|
||||
<Region minHeight="4" styleClass="chat-separator"/>
|
||||
|
||||
<!-- Chatnachrichten werden hier hinzugefügt -->
|
||||
<ScrollPane fx:id="chatScrollPane" fitToWidth="true" vbarPolicy="AS_NEEDED" hbarPolicy="NEVER" VBox.vgrow="ALWAYS" styleClass="chat-scroll-pane">
|
||||
<content>
|
||||
<VBox fx:id="chatVBox" spacing="10" styleClass="chat-VBox"/>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
|
||||
<!-- Eingabebereich -->
|
||||
<HBox spacing="10">
|
||||
<!-- TODO: Größe des TextFields dynamisch anpassen -->
|
||||
<TextField fx:id="inputField" HBox.hgrow="ALWAYS" promptText="Nachricht eingeben..." styleClass="gray-input-field" onAction="#sendMessage"/>
|
||||
<Button fx:id="sendButton" text="SENDEN" onAction="#sendMessage" styleClass="yellow-button"/>
|
||||
<Button fx:id="refreshButton" text="NEU" onAction="#showMessage" styleClass="yellow-button"/>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,155 @@
|
||||
.chat-box {
|
||||
-fx-background-color: #0d9e3b;
|
||||
-fx-background-radius: 58;
|
||||
-fx-border-radius: 50;
|
||||
-fx-border-color: #5c3d10 #3d260a #3d260a #5c3d10;
|
||||
-fx-border-width: 12;
|
||||
-fx-padding: 20;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0,0,0,1), 0, 0, 15, 15);
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-size: 22px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 0 0 10 0;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.chat-separator {
|
||||
-fx-background-color: #ffffff;
|
||||
-fx-min-height: 4px;
|
||||
-fx-effect: dropshadow(one-pass-box, #000000, 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.black-input-field {
|
||||
-fx-background-color: #1a1a1a;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #444444;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.black-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
|
||||
.black-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #000000;
|
||||
}
|
||||
|
||||
.gray-input-field {
|
||||
-fx-background-color: #333333;
|
||||
-fx-text-fill: #ffffff;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-background-radius: 8;
|
||||
-fx-border-radius: 8;
|
||||
-fx-border-color: #666666;
|
||||
-fx-color-color: #cccccc;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 5 12;
|
||||
}
|
||||
|
||||
.gray-input-field:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.gray-input-field:focused {
|
||||
-fx-border-color: #ffffff;
|
||||
-fx-background-color: #333333;
|
||||
}
|
||||
|
||||
.yellow-button {
|
||||
-fx-background-color: #b8860b;
|
||||
-fx-border-color: #ffd700;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
-fx-background-color: #8b0000;
|
||||
-fx-border-color: #ff4444;
|
||||
-fx-text-fill: #ffffff;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
-fx-background-color: #333333;
|
||||
-fx-border-color: #666666;
|
||||
-fx-text-fill: #cccccc;
|
||||
}
|
||||
|
||||
.gray-button, .yellow-button, .red-button {
|
||||
-fx-background-radius: 12;
|
||||
-fx-border-radius: 12;
|
||||
-fx-font-family: "Courier New";
|
||||
-fx-font-weight: bold;
|
||||
-fx-border-width: 2;
|
||||
-fx-padding: 6 15;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.gray-button:hover, .yellow-button:hover, .red-button:hover {
|
||||
-fx-translate-y: -3;
|
||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||
}
|
||||
|
||||
.chat-scroll-pane {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background: transparent;
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.chat-VBox {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background: transparent;
|
||||
}
|
||||
.scroll-pane {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:vertical {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar .track {
|
||||
-fx-background-color: #5c3d10;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar .thumb {
|
||||
-fx-background-color: #3d260a;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 5;
|
||||
-fx-pref-width: 6;
|
||||
-fx-pref-height: 6;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .track {
|
||||
-fx-background-color: #5c3d10;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .thumb {
|
||||
-fx-background-color: #3d260a;
|
||||
-fx-background-radius: 5;
|
||||
-fx-pref-height: 6;
|
||||
}
|
||||
|
||||
.scroll-pane .scroll-bar:horizontal .thumb:hover {
|
||||
-fx-pref-height: 10;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui;
|
||||
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
|
||||
import javafx.application.Application;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ChatControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void teardown() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatController() {
|
||||
Client client = new Client("user1");
|
||||
client.startApplication();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user