diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java index 3a69a45..4783499 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/ChatController.java @@ -63,13 +63,17 @@ public class ChatController { localUserList = new ArrayList<>(); this.chatBoxController = new ChatBoxController(username, this); this.logger = LogManager.getLogger(ChatController.class); - this.timer = new Timer(); + this.timer = new Timer(true); timer.schedule( new TimerTask() { @Override public void run() { - receiveMessage(); - checkWhisperUsers(); + try { + receiveMessage(); + checkWhisperUsers(); + } catch (RuntimeException e) { + logger.warn("Chat refresh failed: {}", e.getMessage()); + } } }, 0, @@ -84,8 +88,12 @@ public class ChatController { */ public void setLobbyChat(int lobbyId) { this.lobbyId = lobbyId; + ChatKey key = new ChatKey(ChatType.LOBBY); + if (chatModelMap.containsKey(key)) { + return; + } ChatModel lobbyChatModel = new ChatModel(ChatType.LOBBY, username, lobbyId, null); - chatModelMap.put(new ChatKey(ChatType.LOBBY), lobbyChatModel); + chatModelMap.put(key, lobbyChatModel); this.chatBoxController.addChatTab("Lobby", lobbyChatModel); } @@ -162,15 +170,32 @@ public class ChatController { for (String user : users) { String value = user.split("\\=")[1]; logger.info(value); - if (!(localUserList.contains(value) || value.equals(username))) { - localUserList.add(value); - logger.info("adding new whisper user"); - chatBoxController.addWhisperUser(value); - } + addWhisperUser(value); } } } + /** + * Registers a whisper target locally and updates the UI if it is a new user. + * + * @param user target username + */ + public synchronized void addWhisperUser(String user) { + if (user == null || user.isBlank()) { + return; + } + if (!(localUserList.contains(user) || user.equals(username))) { + localUserList.add(user); + logger.info("adding new whisper user"); + chatBoxController.addWhisperUser(user); + } + } + + /** Stops polling background tasks for this chat controller instance. */ + public void shutdown() { + timer.cancel(); + } + /** * method to send a message to the server * diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java index a250344..9fafa31 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameController.java @@ -1,19 +1,27 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.game.Card; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameService; import ch.unibas.dmi.dbis.cs108.casono.client.game.GameState; import ch.unibas.dmi.dbis.cs108.casono.client.game.Player; import ch.unibas.dmi.dbis.cs108.casono.client.game.PlayerId; +import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.PlayerStatusController; import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; +import java.io.IOException; +import java.net.URL; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.logging.Logger; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; @@ -44,6 +52,7 @@ public class CasinoGameController { @FXML private VBox player2; @FXML private VBox player3; @FXML private ImageView myDealerIcon; + @FXML private AnchorPane chatContainer; private GameService gameService; private PlayerId myPlayerId; @@ -125,6 +134,12 @@ public class CasinoGameController { private static final long CHIP_DROP_DURATION_MS = 250; private static final long CHIP_STAGGER_DELAY_MULTIPLIER = 35L; + private ChatController chatController; + private String chatUsername; + private ClientService chatClientService; + private int chatLobbyId = -1; + private boolean chatInitialized; + /** Standard constructor. Used by FXML. */ public CasinoGameController() { // default constructor for FXML @@ -203,6 +218,38 @@ public class CasinoGameController { // empty display only (optional) renderCommunityCards(List.of()); renderPlayerCards(List.of()); + initializeChatIfPossible(); + } + + public void setChatContext(String username, ClientService clientService, int lobbyId) { + this.chatUsername = username; + this.chatClientService = clientService; + this.chatLobbyId = lobbyId; + initializeChatIfPossible(); + } + + private void initializeChatIfPossible() { + if (chatInitialized || chatContainer == null || chatClientService == null || chatUsername == null) { + return; + } + try { + chatController = new ChatController(chatUsername, chatClientService); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); + FXMLLoader loader = new FXMLLoader(resource); + loader.setController(chatController.getChatBoxController()); + javafx.scene.Node chatNode = loader.load(); + chatContainer.getChildren().setAll(chatNode); + AnchorPane.setTopAnchor(chatNode, 0.0); + AnchorPane.setBottomAnchor(chatNode, 0.0); + AnchorPane.setLeftAnchor(chatNode, 0.0); + AnchorPane.setRightAnchor(chatNode, 0.0); + if (chatLobbyId >= 0) { + chatController.setLobbyChat(chatLobbyId); + } + chatInitialized = true; + } catch (IOException e) { + LOGGER.warning("Could not initialize game chat UI: " + e.getMessage()); + } } // Test method to demonstrate the UI functionality with sample data. @@ -329,6 +376,9 @@ public class CasinoGameController { if (timeline != null) { timeline.stop(); } + if (chatController != null) { + chatController.shutdown(); + } } /** @@ -405,6 +455,7 @@ public class CasinoGameController { } updatePlayers(players); + syncWhisperTargetsFromPlayers(players); updateGameInfo(s); highlightDealer(s); updateTaskbar(s); @@ -419,6 +470,22 @@ public class CasinoGameController { } } + private void syncWhisperTargetsFromPlayers(List players) { + if (chatController == null || players == null || players.isEmpty()) { + return; + } + Set seen = new HashSet<>(); + for (Player player : players) { + if (player == null) { + continue; + } + String name = player.getName(); + if (name != null && !name.isBlank() && seen.add(name)) { + chatController.addWhisperUser(name); + } + } + } + private List getMyCards(List players) { if (myPlayerId == null || players == null) { diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java index 1d013e2..6da6fd0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameUI.java @@ -26,6 +26,7 @@ public class CasinoGameUI extends Application { private static ClientService clientService; private static String username; + private static int lobbyId = -1; private static final int DEFAULT_WIDTH = 1200; private static final int DEFAULT_HEIGHT = 800; @@ -42,6 +43,10 @@ public class CasinoGameUI extends Application { CasinoGameUI.username = username; } + public static void setLobbyId(int lobbyId) { + CasinoGameUI.lobbyId = lobbyId; + } + @Override public void start(Stage stage) throws IOException { @@ -81,6 +86,7 @@ public class CasinoGameUI extends Application { controller.setGameService(gameService); controller.setMyPlayerId(PlayerId.of(effectiveUsername)); + controller.setChatContext(effectiveUsername, clientService, lobbyId); Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT); stage.setTitle("Casono"); @@ -90,6 +96,7 @@ public class CasinoGameUI extends Application { stage.setScene(scene); stage.setFullScreen(true); + stage.setOnHidden(e -> controller.stop()); stage.show(); controller.start(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java index c06315f..be5f8d0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/CasinomainuiController.java @@ -1,10 +1,14 @@ package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui; import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp; +import ch.unibas.dmi.dbis.cs108.casono.client.chat.ChatController; import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; import ch.unibas.dmi.dbis.cs108.casono.client.network.LobbyClient; +import java.io.IOException; +import java.net.URL; import javafx.application.Platform; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; @@ -13,6 +17,7 @@ import javafx.scene.control.TextField; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; +import javafx.scene.Node; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import org.apache.logging.log4j.LogManager; @@ -31,11 +36,13 @@ public class CasinomainuiController { @FXML private VBox casinoTable; @FXML private TextField usernameField; @FXML private Button loginButton; + @FXML private AnchorPane chatContainer; private LobbyButtonTranslationManager translationManager; private LobbyButtonGridManager gridManager; private int nextButtonId = 1; private LobbyClient lobbyClient; + private ChatController chatController; /** Default constructor used by FXMLLoader. */ public CasinomainuiController() { @@ -96,6 +103,40 @@ public class CasinomainuiController { casinoTable.getChildren().clear(); casinoTable.getChildren().add(gridManager.getGridPane()); gridManager.renderLobbyButtons(); + + initializeChat(clientService); + } + + private void initializeChat(ClientService clientService) { + if (clientService == null || clientService.isOffline() || chatContainer == null) { + return; + } + try { + String username = resolveChatUsername(); + chatController = new ChatController(username, clientService); + URL resource = getClass().getResource("/ui-structure/components/chatui/chatbox.fxml"); + FXMLLoader loader = new FXMLLoader(resource); + loader.setController(chatController.getChatBoxController()); + Node chatNode = loader.load(); + chatContainer.getChildren().setAll(chatNode); + AnchorPane.setTopAnchor(chatNode, 0.0); + AnchorPane.setBottomAnchor(chatNode, 0.0); + AnchorPane.setLeftAnchor(chatNode, 0.0); + AnchorPane.setRightAnchor(chatNode, 0.0); + } catch (IOException e) { + LOGGER.warn("Could not initialize lobby chat UI: {}", e.getMessage()); + } + } + + private String resolveChatUsername() { + String shared = ClientApp.getSharedUsername(); + if (shared != null && !shared.isBlank()) { + return shared.trim(); + } + if (usernameField != null && usernameField.getText() != null && !usernameField.getText().isBlank()) { + return usernameField.getText().trim(); + } + return "Guest"; } /** Handles the login button action. Validates input and calls LobbyClient.login(). */ @@ -139,6 +180,9 @@ public class CasinomainuiController { /** Handles the exit button action to close the application. */ @FXML public void handleexitbutton() { + if (chatController != null) { + chatController.shutdown(); + } Platform.exit(); } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java index 2736437..e875f43 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/lobbyui/LobbyButtonGridManager.java @@ -554,6 +554,7 @@ public class LobbyButtonGridManager { var cs = lobbyClient.getClientService(); ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setClientService(cs); + ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI.setLobbyId(lobbyId); String username = ch.unibas.dmi.dbis.cs108.casono.client.ClientApp.getSharedUsername(); if (username == null || username.isBlank()) {