Feat: Integrate username change flow across lobby, server and chat #288
@@ -1,15 +1,20 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
package ch.unibas.dmi.dbis.cs108.casono.client.chat;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ChatClient;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.chatui.ChatBoxController;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.RequestParameter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
@@ -24,7 +29,7 @@ public class ChatController {
|
|||||||
private static final Map<ClientService, ChatController> ACTIVE_CONTROLLERS =
|
private static final Map<ClientService, ChatController> ACTIVE_CONTROLLERS =
|
||||||
new WeakHashMap<>();
|
new WeakHashMap<>();
|
||||||
|
|
||||||
private final String username;
|
private volatile String username;
|
||||||
|
|
||||||
private final ClientService clientService;
|
private final ClientService clientService;
|
||||||
|
|
||||||
@@ -37,6 +42,7 @@ public class ChatController {
|
|||||||
private final ChatBoxController chatBoxController;
|
private final ChatBoxController chatBoxController;
|
||||||
private int lobbyId = -1;
|
private int lobbyId = -1;
|
||||||
private final Timer timer;
|
private final Timer timer;
|
||||||
|
private final Consumer<List<String>> serverEventListener;
|
||||||
|
|
||||||
public record ChatKey(ChatType type, @Nullable String targetUser) {
|
public record ChatKey(ChatType type, @Nullable String targetUser) {
|
||||||
public ChatKey(ChatType type) {
|
public ChatKey(ChatType type) {
|
||||||
@@ -71,8 +77,10 @@ public class ChatController {
|
|||||||
localUserList = new ArrayList<>();
|
localUserList = new ArrayList<>();
|
||||||
this.chatBoxController = new ChatBoxController(username, this);
|
this.chatBoxController = new ChatBoxController(username, this);
|
||||||
this.logger = LogManager.getLogger(ChatController.class);
|
this.logger = LogManager.getLogger(ChatController.class);
|
||||||
|
this.serverEventListener = this::handleServerEvent;
|
||||||
|
|
||||||
registerAsActiveController(clientService);
|
registerAsActiveController(clientService);
|
||||||
|
clientService.addEventListener(serverEventListener);
|
||||||
|
|
||||||
this.timer = new Timer(true);
|
this.timer = new Timer(true);
|
||||||
timer.schedule(
|
timer.schedule(
|
||||||
@@ -131,6 +139,7 @@ public class ChatController {
|
|||||||
* <p>All Messages get added to a particular {@link ChatModel}, if the checks passed.
|
* <p>All Messages get added to a particular {@link ChatModel}, if the checks passed.
|
||||||
*/
|
*/
|
||||||
public void receiveMessage() {
|
public void receiveMessage() {
|
||||||
|
String currentUsername = getCurrentUsername();
|
||||||
List<Message> newMessages = chatClient.getMessages();
|
List<Message> newMessages = chatClient.getMessages();
|
||||||
if (!newMessages.isEmpty()) {
|
if (!newMessages.isEmpty()) {
|
||||||
for (Message msg : newMessages) {
|
for (Message msg : newMessages) {
|
||||||
@@ -146,16 +155,17 @@ public class ChatController {
|
|||||||
(_key) ->
|
(_key) ->
|
||||||
new ChatModel(
|
new ChatModel(
|
||||||
ChatType.LOBBY,
|
ChatType.LOBBY,
|
||||||
username,
|
currentUsername,
|
||||||
msg.lobbyId,
|
msg.lobbyId,
|
||||||
null))
|
null))
|
||||||
.addMessage(msg);
|
.addMessage(msg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ChatType.WHISPER:
|
case ChatType.WHISPER:
|
||||||
if (msg.target.equals(username) || msg.sender.equals(username)) {
|
if (msg.target.equals(currentUsername)
|
||||||
|
|| msg.sender.equals(currentUsername)) {
|
||||||
ChatKey key;
|
ChatKey key;
|
||||||
if (msg.target.equals(username)) {
|
if (msg.target.equals(currentUsername)) {
|
||||||
key = new ChatKey(ChatType.WHISPER, msg.sender);
|
key = new ChatKey(ChatType.WHISPER, msg.sender);
|
||||||
} else {
|
} else {
|
||||||
key = new ChatKey(ChatType.WHISPER, msg.target);
|
key = new ChatKey(ChatType.WHISPER, msg.target);
|
||||||
@@ -166,7 +176,7 @@ public class ChatController {
|
|||||||
ChatModel chatModel =
|
ChatModel chatModel =
|
||||||
new ChatModel(
|
new ChatModel(
|
||||||
ChatType.WHISPER,
|
ChatType.WHISPER,
|
||||||
username,
|
currentUsername,
|
||||||
lobbyId,
|
lobbyId,
|
||||||
key.targetUser());
|
key.targetUser());
|
||||||
chatBoxController.addWhisperChat(key.targetUser(), chatModel);
|
chatBoxController.addWhisperChat(key.targetUser(), chatModel);
|
||||||
@@ -203,13 +213,29 @@ public class ChatController {
|
|||||||
*/
|
*/
|
||||||
public synchronized void checkWhisperUsers() {
|
public synchronized void checkWhisperUsers() {
|
||||||
List<String> users = chatClient.getUsers();
|
List<String> users = chatClient.getUsers();
|
||||||
logger.info(users);
|
Set<String> remoteUsers = new HashSet<>();
|
||||||
if (!users.isEmpty()) {
|
String currentUsername = getCurrentUsername();
|
||||||
for (String user : users) {
|
|
||||||
String value = user.split("\\=")[1];
|
for (String user : users) {
|
||||||
logger.info(value);
|
if (user == null || !user.contains("=")) {
|
||||||
addWhisperUser(value);
|
continue;
|
||||||
}
|
}
|
||||||
|
String value = user.split("\\=", 2)[1].trim();
|
||||||
|
if (value.isBlank() || value.equals(currentUsername)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
remoteUsers.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String known : new ArrayList<>(localUserList)) {
|
||||||
|
if (!remoteUsers.contains(known)) {
|
||||||
|
localUserList.remove(known);
|
||||||
|
chatBoxController.removeWhisperUser(known);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String remote : remoteUsers) {
|
||||||
|
addWhisperUser(remote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,16 +248,110 @@ public class ChatController {
|
|||||||
if (user == null || user.isBlank()) {
|
if (user == null || user.isBlank()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(localUserList.contains(user) || user.equals(username))) {
|
if (!(localUserList.contains(user) || user.equals(getCurrentUsername()))) {
|
||||||
localUserList.add(user);
|
localUserList.add(user);
|
||||||
logger.info("adding new whisper user");
|
logger.info("adding new whisper user");
|
||||||
chatBoxController.addWhisperUser(user);
|
chatBoxController.addWhisperUser(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void updateUsername(String newUsername) {
|
||||||
|
if (newUsername == null || newUsername.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String oldUsername = this.username;
|
||||||
|
this.username = newUsername.trim();
|
||||||
|
chatBoxController.setUsername(this.username);
|
||||||
|
|
||||||
|
if (oldUsername != null && !oldUsername.equals(this.username)) {
|
||||||
|
localUserList.remove(oldUsername);
|
||||||
|
chatBoxController.removeWhisperUser(oldUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleServerEvent(List<String> lines) {
|
||||||
|
if (lines == null || lines.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RequestParameter> params;
|
||||||
|
try {
|
||||||
|
params = ClientService.convertToRequestParameters(lines);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String event = null;
|
||||||
|
String oldUsername = null;
|
||||||
|
String newUsername = null;
|
||||||
|
|
||||||
|
for (RequestParameter p : params) {
|
||||||
|
if ("EVENT".equalsIgnoreCase(p.key())) {
|
||||||
|
event = p.value();
|
||||||
|
} else if ("OLD_USERNAME".equalsIgnoreCase(p.key())) {
|
||||||
|
oldUsername = p.value();
|
||||||
|
} else if ("NEW_USERNAME".equalsIgnoreCase(p.key())) {
|
||||||
|
newUsername = p.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"USERNAME_CHANGED".equalsIgnoreCase(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyUsernameMigration(oldUsername, newUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void applyUsernameMigration(String oldUsername, String newUsername) {
|
||||||
|
if (oldUsername == null
|
||||||
|
|| newUsername == null
|
||||||
|
|| oldUsername.isBlank()
|
||||||
|
|| newUsername.isBlank()
|
||||||
|
|| oldUsername.equals(newUsername)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentUsername = getCurrentUsername();
|
||||||
|
if (oldUsername.equals(currentUsername)) {
|
||||||
|
updateUsername(newUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatKey oldKey = new ChatKey(ChatType.WHISPER, oldUsername);
|
||||||
|
ChatKey newKey = new ChatKey(ChatType.WHISPER, newUsername);
|
||||||
|
|
||||||
|
ChatModel oldModel = chatModelMap.remove(oldKey);
|
||||||
|
ChatModel existingNewModel = chatModelMap.get(newKey);
|
||||||
|
if (oldModel != null) {
|
||||||
|
oldModel.setTarget(newUsername);
|
||||||
|
if (existingNewModel == null) {
|
||||||
|
chatModelMap.put(newKey, oldModel);
|
||||||
|
} else {
|
||||||
|
// Merge possible parallel history into the already existing new-key model.
|
||||||
|
for (Message msg : oldModel.messages) {
|
||||||
|
existingNewModel.addMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatBoxController.renameWhisperUser(oldUsername, newUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
localUserList.remove(oldUsername);
|
||||||
|
chatBoxController.removeWhisperUser(oldUsername);
|
||||||
|
if (!newUsername.equals(getCurrentUsername())) {
|
||||||
|
addWhisperUser(newUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentUsername() {
|
||||||
|
String shared = ClientApp.getSharedUsername();
|
||||||
|
if (shared != null && !shared.isBlank()) {
|
||||||
|
return shared.trim();
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
/** Stops polling background tasks for this chat controller instance. */
|
/** Stops polling background tasks for this chat controller instance. */
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
|
clientService.removeEventListener(serverEventListener);
|
||||||
synchronized (ACTIVE_CONTROLLERS) {
|
synchronized (ACTIVE_CONTROLLERS) {
|
||||||
if (ACTIVE_CONTROLLERS.get(clientService) == this) {
|
if (ACTIVE_CONTROLLERS.get(clientService) == this) {
|
||||||
ACTIVE_CONTROLLERS.remove(clientService);
|
ACTIVE_CONTROLLERS.remove(clientService);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class ChatModel {
|
|||||||
public final String username;
|
public final String username;
|
||||||
|
|
||||||
/** The person to send the message to If the chat is a whisper chat */
|
/** The person to send the message to If the chat is a whisper chat */
|
||||||
private final String target;
|
private String target;
|
||||||
|
|
||||||
private final IntegerProperty count;
|
private final IntegerProperty count;
|
||||||
|
|
||||||
@@ -82,4 +82,8 @@ public class ChatModel {
|
|||||||
public String getTarget() {
|
public String getTarget() {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTarget(String target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ public class ChatBoxController {
|
|||||||
usernameTabMap = new HashMap<>();
|
usernameTabMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
if (username != null && !username.isBlank()) {
|
||||||
|
this.username = username.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the chat interface by creating the global chat model and adding the corresponding
|
* Initializes the chat interface by creating the global chat model and adding the corresponding
|
||||||
* "GLOBAL" tab to the interface. It also registers the global chat in the {@link
|
* "GLOBAL" tab to the interface. It also registers the global chat in the {@link
|
||||||
@@ -84,6 +90,11 @@ public class ChatBoxController {
|
|||||||
public void addWhisperUser(String targetUserName) {
|
public void addWhisperUser(String targetUserName) {
|
||||||
Platform.runLater(
|
Platform.runLater(
|
||||||
() -> {
|
() -> {
|
||||||
|
for (MenuItem existing : addWhisperChatButton.getItems()) {
|
||||||
|
if (targetUserName.equals(existing.getText())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
MenuItem menuItem = new MenuItem(targetUserName);
|
MenuItem menuItem = new MenuItem(targetUserName);
|
||||||
addWhisperChatButton.getItems().add(menuItem);
|
addWhisperChatButton.getItems().add(menuItem);
|
||||||
menuItem.setOnAction(
|
menuItem.setOnAction(
|
||||||
@@ -98,6 +109,54 @@ public class ChatBoxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeWhisperUser(String targetUserName) {
|
||||||
|
Platform.runLater(
|
||||||
|
() ->
|
||||||
|
addWhisperChatButton
|
||||||
|
.getItems()
|
||||||
|
.removeIf(item -> targetUserName.equals(item.getText())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renameWhisperUser(String oldUsername, String newUsername) {
|
||||||
|
if (oldUsername == null
|
||||||
|
|| newUsername == null
|
||||||
|
|| oldUsername.isBlank()
|
||||||
|
|| newUsername.isBlank()
|
||||||
|
|| oldUsername.equals(newUsername)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(
|
||||||
|
() -> {
|
||||||
|
for (MenuItem item : addWhisperChatButton.getItems()) {
|
||||||
|
if (oldUsername.equals(item.getText())) {
|
||||||
|
item.setText(newUsername);
|
||||||
|
item.setOnAction(
|
||||||
|
event ->
|
||||||
|
addWhisperChat(
|
||||||
|
newUsername,
|
||||||
|
new ChatModel(
|
||||||
|
ChatType.WHISPER,
|
||||||
|
username,
|
||||||
|
-1,
|
||||||
|
newUsername)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab tab = usernameTabMap.remove(oldUsername);
|
||||||
|
if (tab != null) {
|
||||||
|
tab.setText(newUsername);
|
||||||
|
usernameTabMap.put(newUsername, tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = activeWhisperChats.indexOf(oldUsername);
|
||||||
|
if (idx >= 0) {
|
||||||
|
activeWhisperChats.set(idx, newUsername);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new private (whisper) chat model for a specific target user, registers it within
|
* Creates a new private (whisper) chat model for a specific target user, registers it within
|
||||||
* the chat system, and opens a new chat tab.
|
* the chat system, and opens a new chat tab.
|
||||||
|
|||||||
Reference in New Issue
Block a user