Merge remote-tracking branch 'origin/Main-UI' into Main-UI
# Conflicts: # src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java
This commit is contained in:
@@ -15,8 +15,13 @@ module org.to.telegramfinalproject {
|
|||||||
requires spark.core;
|
requires spark.core;
|
||||||
requires javax.servlet.api;
|
requires javax.servlet.api;
|
||||||
requires mp3agic;
|
requires mp3agic;
|
||||||
opens org.to.telegramfinalproject to javafx.fxml;
|
|
||||||
exports org.to.telegramfinalproject;
|
// FXML کنترلرها در این پکیجاند:
|
||||||
exports org.to.telegramfinalproject.Client;
|
opens org.to.telegramfinalproject.UI to javafx.fxml;
|
||||||
|
// اگر FXML از کلاسهای Client هم استفاده میکند:
|
||||||
opens org.to.telegramfinalproject.Client to javafx.fxml;
|
opens org.to.telegramfinalproject.Client to javafx.fxml;
|
||||||
|
|
||||||
|
// فقط اگر لازم است از بیرون به این APIها کامپایل شود:
|
||||||
|
exports org.to.telegramfinalproject.Client;
|
||||||
|
// ⚠️ عمداً NOT exporting/opens ریشهی پکیج، چون کلاس ندارد.
|
||||||
}
|
}
|
||||||
@@ -138,22 +138,22 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
JSONObject finalMsg = msg;
|
JSONObject finalMsg = msg;
|
||||||
JSONObject finalMsg1 = msg;
|
JSONObject finalMsg1 = msg;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "added_to_group", "added_to_channel",
|
// case "added_to_group", "added_to_channel",
|
||||||
"removed_from_group", "removed_from_channel",
|
// "removed_from_group", "removed_from_channel",
|
||||||
"chat_deleted", "created_private_chat" -> {
|
// "chat_deleted", "created_private_chat" -> {
|
||||||
// این قسمت مستقل از UI/کنسول است
|
// // این قسمت مستقل از UI/کنسول است
|
||||||
System.out.println("🔄 Chat list changed. Updating...");
|
// System.out.println("🔄 Chat list changed. Updating...");
|
||||||
Session.forceRefreshChatList = true;
|
// Session.forceRefreshChatList = true;
|
||||||
|
//
|
||||||
String chatId = msg.getString("chat_id");
|
// String chatId = msg.getString("chat_id");
|
||||||
String chatType = msg.getString("chat_type");
|
// String chatType = msg.getString("chat_type");
|
||||||
ActionHandler.requestChatInfo(chatId, chatType);
|
// ActionHandler.requestChatInfo(chatId, chatType);
|
||||||
|
//
|
||||||
if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) {
|
// if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) {
|
||||||
System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting...");
|
// System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting...");
|
||||||
ActionHandler.forceExitChat = true;
|
// ActionHandler.forceExitChat = true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// case "chat_updated" -> {
|
// case "chat_updated" -> {
|
||||||
// if (uiMode == UIMode.UI) {
|
// if (uiMode == UIMode.UI) {
|
||||||
@@ -163,7 +163,89 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
case "became_admin", "removed_admin", "ownership_transferred", "admin_permissions_updated" -> {
|
case "added_to_group":
|
||||||
|
case "added_to_channel":
|
||||||
|
case "created_private_chat": {
|
||||||
|
// دادهها
|
||||||
|
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||||
|
String type = msg.getString("chat_type"); // "group" | "channel" | "private"
|
||||||
|
String name = msg.optString("name", "");
|
||||||
|
String imgUrl = msg.optString("image_url", "");
|
||||||
|
|
||||||
|
// یک ChatEntry مینیمال بساز (تا UI سریع واکنش بده)
|
||||||
|
ChatEntry ce = new ChatEntry();
|
||||||
|
ce.setId(chatId.toString());
|
||||||
|
ce.setType(type);
|
||||||
|
ce.setName(name);
|
||||||
|
ce.setImageUrl(imgUrl);
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var mc = MainController.getInstance();
|
||||||
|
if (mc == null) return;
|
||||||
|
|
||||||
|
// به لیستها اضافه و UI را رفرش میکند (متد خودت)
|
||||||
|
mc.onJoinedOrAdded(ce);
|
||||||
|
|
||||||
|
// اگر همین چت الان بازه، مود مناسب را اعمال کن
|
||||||
|
var cpc = mc.getChatPageController();
|
||||||
|
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||||
|
// ❗ اگر applyMode در ChatPageController private است،
|
||||||
|
// یا publicش کن یا این دو خط را حذف کن.
|
||||||
|
// گروه → NORMAL ، کانال → READ_ONLY (مگر اینکه اجازه پست داشته باشی)
|
||||||
|
// cpc.applyMode("group".equalsIgnoreCase(type) ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY);
|
||||||
|
// cpc.fetchAndRenderHeader(ce); // اختیاری: هدر را تازه کن
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "removed_from_group":
|
||||||
|
case "removed_from_channel": {
|
||||||
|
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||||
|
String type = msg.getString("chat_type");
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var mc = MainController.getInstance();
|
||||||
|
if (mc == null) return;
|
||||||
|
|
||||||
|
removeFromAllLists(chatId);
|
||||||
|
mc.refreshChatListUI();
|
||||||
|
|
||||||
|
// اگر همین چت باز است → به حالت نیاز به Join برگرد
|
||||||
|
var cpc = mc.getChatPageController();
|
||||||
|
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||||
|
// اگر applyMode private است، این خط را کامنت کن یا publicش کن
|
||||||
|
// cpc.applyMode(ChatViewMode.NEEDS_JOIN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "chat_deleted": {
|
||||||
|
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||||
|
String type = msg.getString("chat_type");
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var mc = MainController.getInstance();
|
||||||
|
if (mc == null) return;
|
||||||
|
|
||||||
|
removeFromAllLists(chatId);
|
||||||
|
mc.refreshChatListUI();
|
||||||
|
|
||||||
|
var cpc = mc.getChatPageController();
|
||||||
|
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||||
|
// حداقل ورودی را ببندیم/غیرفعال کنیم
|
||||||
|
// اگر applyMode private است، این خط را کامنت کن یا publicش کن
|
||||||
|
// cpc.applyMode(ChatViewMode.READ_ONLY);
|
||||||
|
// و یک پیام سیستمی هم نشان بده
|
||||||
|
cpc.addSystemMessage("This chat was deleted.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case "became_admin", "removed_admin", "ownership_transferred", "admin_permissions_updated" : {
|
||||||
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
|
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -174,7 +256,7 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
case "new_message" -> {
|
case "new_message" :{
|
||||||
JSONObject data = response.optJSONObject("data");
|
JSONObject data = response.optJSONObject("data");
|
||||||
if (data == null) break;
|
if (data == null) break;
|
||||||
|
|
||||||
@@ -198,7 +280,7 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case "message_edited" -> {
|
case "message_edited": {
|
||||||
JSONObject ui = normalizeMessageId(msg);
|
JSONObject ui = normalizeMessageId(msg);
|
||||||
// (اختیاری) اگر ایونت زمان و چت را هم میدهد، میتوانی چتلیست را آپدیت کنی
|
// (اختیاری) اگر ایونت زمان و چت را هم میدهد، میتوانی چتلیست را آپدیت کنی
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -208,7 +290,7 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case "message_deleted_global", "message_deleted_one_sided", "message_deleted" -> {
|
case "message_deleted_global", "message_deleted_one_sided", "message_deleted" : {
|
||||||
JSONObject ui = normalizeMessageId(msg);
|
JSONObject ui = normalizeMessageId(msg);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
var mc = MainController.getInstance();
|
var mc = MainController.getInstance();
|
||||||
@@ -217,7 +299,7 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case "message_reacted", "message_unreacted" -> {
|
case "message_reacted", "message_unreacted" : {
|
||||||
JSONObject ui = normalizeMessageId(msg);
|
JSONObject ui = normalizeMessageId(msg);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
var mc = MainController.getInstance();
|
var mc = MainController.getInstance();
|
||||||
@@ -228,12 +310,12 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
case "chat_updated" -> {
|
case "chat_updated": {
|
||||||
var data = response.getJSONObject("data");
|
var data = response.getJSONObject("data");
|
||||||
bumpChatListFromUpdate(data);
|
bumpChatListFromUpdate(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
case "user_status_changed" -> {
|
case "user_status_changed" : {
|
||||||
|
|
||||||
displayRealTimeMessage(action, msg);
|
displayRealTimeMessage(action, msg);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -250,11 +332,11 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
case "blocked_by_user", "unblocked_by_user", "message_seen" -> {
|
case "blocked_by_user", "unblocked_by_user", "message_seen" : {
|
||||||
displayRealTimeMessage(action, msg);
|
displayRealTimeMessage(action, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
default -> {
|
default :{
|
||||||
System.out.println("\n❓ Unknown real-time action: " + action);
|
System.out.println("\n❓ Unknown real-time action: " + action);
|
||||||
System.out.println(msg.toString(2));
|
System.out.println(msg.toString(2));
|
||||||
}
|
}
|
||||||
@@ -550,4 +632,17 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void removeFromAllLists(UUID chatId) {
|
||||||
|
if (Session.chatList != null) {
|
||||||
|
Session.chatList.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||||
|
}
|
||||||
|
if (Session.activeChats != null) {
|
||||||
|
Session.activeChats.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||||
|
}
|
||||||
|
if (Session.archivedChats != null) {
|
||||||
|
Session.archivedChats.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,6 @@ public class Session {
|
|||||||
resortAndRefresh();
|
resortAndRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** سورت بر اساس lastMessageTime (نزولی) و آپدیت active/archived */
|
|
||||||
public static void resortAndRefresh() {
|
public static void resortAndRefresh() {
|
||||||
chatList.sort((c1, c2) -> {
|
chatList.sort((c1, c2) -> {
|
||||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ public class ChatPageController {
|
|||||||
@FXML
|
@FXML
|
||||||
private ImageView searchIcon;
|
private ImageView searchIcon;
|
||||||
|
|
||||||
|
@FXML private VBox blockedPane;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button moreButton; // 3-dots button
|
private Button moreButton; // 3-dots button
|
||||||
@FXML
|
@FXML
|
||||||
@@ -118,6 +120,8 @@ public class ChatPageController {
|
|||||||
|
|
||||||
private boolean blockedByMeFlag = false;
|
private boolean blockedByMeFlag = false;
|
||||||
private boolean blockedMeFlag = false;
|
private boolean blockedMeFlag = false;
|
||||||
|
private volatile boolean justJoinedThisChat = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ===== state =====
|
// ===== state =====
|
||||||
@@ -385,7 +389,6 @@ public class ChatPageController {
|
|||||||
// if (!text.isEmpty()) {
|
// if (!text.isEmpty()) {
|
||||||
// addMessage("You", text);
|
// addMessage("You", text);
|
||||||
// messageInput.clear();
|
// messageInput.clear();
|
||||||
// // TODO: send to server
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -704,7 +707,7 @@ public class ChatPageController {
|
|||||||
// messageContainer.getChildren().add(msg);
|
// messageContainer.getChildren().add(msg);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private void addSystemMessage(String content) {
|
public void addSystemMessage(String content) {
|
||||||
Label sys = new Label(content);
|
Label sys = new Label(content);
|
||||||
sys.setStyle("-fx-text-fill: gray; -fx-font-size: 11;");
|
sys.setStyle("-fx-text-fill: gray; -fx-font-size: 11;");
|
||||||
messageContainer.getChildren().add(sys);
|
messageContainer.getChildren().add(sys);
|
||||||
@@ -712,38 +715,38 @@ public class ChatPageController {
|
|||||||
messageScrollPane.setVvalue(1.0);
|
messageScrollPane.setVvalue(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Update all header/footer icons according to current theme.
|
// * Update all header/footer icons according to current theme.
|
||||||
*/
|
// */
|
||||||
private void syncIconsWithTheme() {
|
// private void syncIconsWithTheme() {
|
||||||
boolean dark = themeManager.isDarkMode();
|
// boolean dark = themeManager.isDarkMode();
|
||||||
// We use “_light” icons on dark backgrounds, and “_dark” on light backgrounds.
|
// // We use “_light” icons on dark backgrounds, and “_dark” on light backgrounds.
|
||||||
String suffix = dark ? "_light.png" : "_dark.png";
|
// String suffix = dark ? "_light.png" : "_dark.png";
|
||||||
|
//
|
||||||
// attachment
|
// // attachment
|
||||||
if (attachmentIcon != null) {
|
// if (attachmentIcon != null) {
|
||||||
attachmentIcon.setImage(loadIcon("attachment" + suffix));
|
// attachmentIcon.setImage(loadIcon("attachment" + suffix));
|
||||||
}
|
// }
|
||||||
// send
|
// // send
|
||||||
if (sendIcon != null) {
|
// if (sendIcon != null) {
|
||||||
sendIcon.setImage(loadIcon("send_cyan2.png"));
|
// sendIcon.setImage(loadIcon("send_cyan2.png"));
|
||||||
}
|
// }
|
||||||
// header icons
|
// // header icons
|
||||||
if (searchIcon != null) {
|
// if (searchIcon != null) {
|
||||||
searchIcon.setImage(loadIcon("search" + suffix));
|
// searchIcon.setImage(loadIcon("search" + suffix));
|
||||||
}
|
// }
|
||||||
if (moreIcon != null) {
|
// if (moreIcon != null) {
|
||||||
moreIcon.setImage(loadIcon("more" + suffix));
|
// moreIcon.setImage(loadIcon("more" + suffix));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// header text tint (if you’re not fully relying on CSS)
|
// // header text tint (if you’re not fully relying on CSS)
|
||||||
if (chatTitle != null) chatTitle.setStyle(dark ? "-fx-text-fill:#e8f1f8;" : "-fx-text-fill:#0f141a;");
|
// if (chatTitle != null) chatTitle.setStyle(dark ? "-fx-text-fill:#e8f1f8;" : "-fx-text-fill:#0f141a;");
|
||||||
if (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
|
// if (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
|
||||||
|
//
|
||||||
// View profile icon in more button
|
// // View profile icon in more button
|
||||||
((ImageView) viewProfileItem.getGraphic())
|
// ((ImageView) viewProfileItem.getGraphic())
|
||||||
.setImage(loadIcon("view_profile" + suffix));
|
// .setImage(loadIcon("view_profile" + suffix));
|
||||||
}
|
// }
|
||||||
|
|
||||||
private Image loadIcon(String filename) {
|
private Image loadIcon(String filename) {
|
||||||
var url = getClass().getResource(ICON_BASE + filename);
|
var url = getClass().getResource(ICON_BASE + filename);
|
||||||
@@ -772,7 +775,7 @@ public class ChatPageController {
|
|||||||
// } else {
|
// } else {
|
||||||
// setDefaultHeaderAvatarByType(entry.getType());
|
// setDefaultHeaderAvatarByType(entry.getType());
|
||||||
// }
|
// }
|
||||||
//// userAvatar.setClip(new Circle(20, 20, 20));
|
//// userAvatar.setClip(new Circle(20, 20, 20));
|
||||||
// AvatarFX.circleClip(userAvatar, 36);
|
// AvatarFX.circleClip(userAvatar, 36);
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
@@ -813,34 +816,35 @@ public class ChatPageController {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
public void showChat(ChatEntry entry) {
|
public void showChat(ChatEntry entry) {
|
||||||
this.currentChat = entry;
|
this.currentChat = entry;
|
||||||
this.chatName = entry.getName();
|
this.chatName = entry.getName();
|
||||||
|
|
||||||
chatTitle.setText(entry.getName());
|
chatTitle.setText(entry.getName());
|
||||||
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
||||||
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
||||||
if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType());
|
if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType());
|
||||||
} else {
|
} else {
|
||||||
setDefaultHeaderAvatarByType(entry.getType());
|
setDefaultHeaderAvatarByType(entry.getType());
|
||||||
}
|
}
|
||||||
AvatarFX.circleClip(userAvatar, 36);
|
AvatarFX.circleClip(userAvatar, 36);
|
||||||
|
|
||||||
// حالت اولیه (بدون انتظار هدر)
|
|
||||||
if ("channel".equalsIgnoreCase(entry.getType())) {
|
|
||||||
boolean canPostLocal = entry.isOwner() || entry.isAdmin()
|
|
||||||
|| (entry.getPermissions()!=null && entry.getPermissions().optBoolean("can_post", false));
|
|
||||||
applyMode(canPostLocal ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY);
|
|
||||||
} else {
|
|
||||||
applyMode(ChatViewMode.NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageContainer.getChildren().clear();
|
// حالت اولیه (بدون انتظار هدر)
|
||||||
loadMessages(entry);
|
if ("channel".equalsIgnoreCase(entry.getType())) {
|
||||||
markAsRead(entry);
|
boolean canPostLocal = entry.isOwner() || entry.isAdmin()
|
||||||
|
|| (entry.getPermissions()!=null && entry.getPermissions().optBoolean("can_post", false));
|
||||||
|
applyMode(canPostLocal ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY);
|
||||||
|
} else {
|
||||||
|
applyMode(ChatViewMode.NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
// حالا هدر بیاد، دوباره نهاییاش میکنیم
|
messageContainer.getChildren().clear();
|
||||||
fetchAndRenderHeader(entry);
|
loadMessages(entry);
|
||||||
|
markAsRead(entry);
|
||||||
|
|
||||||
|
// حالا هدر بیاد، دوباره نهاییاش میکنیم
|
||||||
|
fetchAndRenderHeader(entry);
|
||||||
|
|
||||||
// === (3-dot menu + header click) ===
|
// === (3-dot menu + header click) ===
|
||||||
configureHeaderActions(entry);
|
configureHeaderActions(entry);
|
||||||
@@ -897,7 +901,7 @@ public void showChat(ChatEntry entry) {
|
|||||||
public void showChat(ChatEntry entry, ChatViewMode mode) {
|
public void showChat(ChatEntry entry, ChatViewMode mode) {
|
||||||
this.currentChat = entry;
|
this.currentChat = entry;
|
||||||
|
|
||||||
// --- Header (fallback until server responds) ---
|
// --- Header ---
|
||||||
chatTitle.setText(entry.getName());
|
chatTitle.setText(entry.getName());
|
||||||
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
||||||
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
||||||
@@ -1400,7 +1404,7 @@ public void showChat(ChatEntry entry) {
|
|||||||
// menu.show(row, ev.getScreenX(), ev.getScreenY());
|
// menu.show(row, ev.getScreenX(), ev.getScreenY());
|
||||||
// ev.consume();
|
// ev.consume();
|
||||||
// });
|
// });
|
||||||
//// با کلیک معمولی هم اگر دوست داری:
|
//// با کلیک معمولی هم اگر دوست داری:
|
||||||
// row.setOnMouseClicked(ev -> {
|
// row.setOnMouseClicked(ev -> {
|
||||||
// if (ev.getButton() == javafx.scene.input.MouseButton.PRIMARY && ev.getClickCount() == 1) {
|
// if (ev.getButton() == javafx.scene.input.MouseButton.PRIMARY && ev.getClickCount() == 1) {
|
||||||
// menu.show(row, ev.getScreenX(), ev.getScreenY());
|
// menu.show(row, ev.getScreenX(), ev.getScreenY());
|
||||||
@@ -1411,110 +1415,110 @@ public void showChat(ChatEntry entry) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void addBubble(
|
private void addBubble(
|
||||||
boolean outgoing,
|
boolean outgoing,
|
||||||
String displayName,
|
String displayName,
|
||||||
String type,
|
String type,
|
||||||
String content,
|
String content,
|
||||||
java.time.LocalDateTime sentAt,
|
java.time.LocalDateTime sentAt,
|
||||||
String messageId,
|
String messageId,
|
||||||
String forwardedFrom,
|
String forwardedFrom,
|
||||||
String forwardedBy,
|
String forwardedBy,
|
||||||
String replyToId,
|
String replyToId,
|
||||||
boolean edited,
|
boolean edited,
|
||||||
org.json.JSONArray reactions
|
org.json.JSONArray reactions
|
||||||
) {
|
) {
|
||||||
// === Meta (نام + زمان) ===
|
// === Meta (نام + زمان) ===
|
||||||
String metaText = (displayName == null ? "" : displayName) + " • " + formatWhen(sentAt);
|
String metaText = (displayName == null ? "" : displayName) + " • " + formatWhen(sentAt);
|
||||||
if (edited) metaText += " (edited)";
|
if (edited) metaText += " (edited)";
|
||||||
Label meta = new Label(metaText);
|
Label meta = new Label(metaText);
|
||||||
meta.setStyle("-fx-font-size: 11; -fx-text-fill: #7e8a97;");
|
meta.setStyle("-fx-font-size: 11; -fx-text-fill: #7e8a97;");
|
||||||
meta.setWrapText(true);
|
meta.setWrapText(true);
|
||||||
// برچسب برای آپدیتهای بعدی (edit)
|
// برچسب برای آپدیتهای بعدی (edit)
|
||||||
meta.getProperties().put("role", "metaLabel");
|
meta.getProperties().put("role", "metaLabel");
|
||||||
|
|
||||||
// === متن/نوع پیام ===
|
// === متن/نوع پیام ===
|
||||||
String t = type == null ? "" : type.trim().toUpperCase();
|
String t = type == null ? "" : type.trim().toUpperCase();
|
||||||
boolean isText = t.isEmpty() ? (content != null && !content.isBlank()) : "TEXT".equals(t);
|
boolean isText = t.isEmpty() ? (content != null && !content.isBlank()) : "TEXT".equals(t);
|
||||||
String bodyText = isText ? (content == null ? "" : content) : bracketLabel(t);
|
String bodyText = isText ? (content == null ? "" : content) : bracketLabel(t);
|
||||||
|
|
||||||
Label msg = new Label(bodyText);
|
Label msg = new Label(bodyText);
|
||||||
msg.setWrapText(true);
|
msg.setWrapText(true);
|
||||||
msg.setMinHeight(Region.USE_PREF_SIZE);
|
msg.setMinHeight(Region.USE_PREF_SIZE);
|
||||||
// برچسب برای آپدیتهای بعدی (edit)
|
// برچسب برای آپدیتهای بعدی (edit)
|
||||||
msg.getProperties().put("role", "msgLabel");
|
msg.getProperties().put("role", "msgLabel");
|
||||||
|
|
||||||
// === رنگ بابلها
|
// === رنگ بابلها
|
||||||
boolean dark = themeManager.isDarkMode();
|
boolean dark = themeManager.isDarkMode();
|
||||||
String mine = dark ? "#2b7cff" : "#d8ecff"; // outgoing (من)
|
String mine = dark ? "#2b7cff" : "#d8ecff"; // outgoing (من)
|
||||||
String theirs = dark ? "#2c333a" : "#f2f4f7"; // incoming (خیلی روشن بهجای سفید)
|
String theirs = dark ? "#2c333a" : "#f2f4f7"; // incoming (خیلی روشن بهجای سفید)
|
||||||
String bg = outgoing ? mine : theirs;
|
String bg = outgoing ? mine : theirs;
|
||||||
|
|
||||||
msg.setStyle(
|
msg.setStyle(
|
||||||
"-fx-background-color:" + bg + ";" +
|
"-fx-background-color:" + bg + ";" +
|
||||||
"-fx-padding:8 12;" +
|
"-fx-padding:8 12;" +
|
||||||
"-fx-background-radius:12;" +
|
"-fx-background-radius:12;" +
|
||||||
"-fx-max-width: 520;"
|
"-fx-max-width: 520;"
|
||||||
);
|
);
|
||||||
|
|
||||||
// === بدنهی بابل ===
|
// === بدنهی بابل ===
|
||||||
VBox bubble = new VBox(4);
|
VBox bubble = new VBox(4);
|
||||||
bubble.getChildren().add(meta);
|
bubble.getChildren().add(meta);
|
||||||
|
|
||||||
// برچسبگذاری بابل برای پیدا کردنش در آپدیتهای realtime
|
// برچسبگذاری بابل برای پیدا کردنش در آپدیتهای realtime
|
||||||
if (messageId != null && !messageId.isBlank()) {
|
if (messageId != null && !messageId.isBlank()) {
|
||||||
bubble.getProperties().put("messageId", messageId);
|
bubble.getProperties().put("messageId", messageId);
|
||||||
}
|
|
||||||
|
|
||||||
// Forward header (اختیاری)
|
|
||||||
if (hasVal(forwardedFrom) || hasVal(forwardedBy)) {
|
|
||||||
bubble.getChildren().add(buildForwardHeader(forwardedFrom, forwardedBy));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply preview (اختیاری)
|
|
||||||
if (hasVal(replyToId)) {
|
|
||||||
bubble.getChildren().add(buildReplyBoxFromIndex(replyToId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// متن اصلی
|
|
||||||
bubble.getChildren().add(msg);
|
|
||||||
|
|
||||||
// Reactions (اختیاری) + برچسب برای تعویض سریع در ریِلتایم
|
|
||||||
if (reactions != null && reactions.length() > 0) {
|
|
||||||
Node rxBar = buildReactionsBarFromJson(reactions, dark);
|
|
||||||
rxBar.getProperties().put("role", "reactionsBar");
|
|
||||||
bubble.getChildren().add(rxBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === ردیف چیدمان راست/چپ ===
|
|
||||||
HBox row = new HBox(bubble);
|
|
||||||
row.setFillHeight(true);
|
|
||||||
row.setSpacing(4);
|
|
||||||
row.setAlignment(outgoing ? Pos.CENTER_RIGHT : Pos.CENTER_LEFT);
|
|
||||||
row.setPadding(new Insets(2, 6, 2, 6));
|
|
||||||
|
|
||||||
// اضافه به کانتینر
|
|
||||||
messageContainer.getChildren().add(row);
|
|
||||||
|
|
||||||
// ایندکس نود برای آپدیت/حذف realtime
|
|
||||||
if (messageId != null && !messageId.isBlank()) {
|
|
||||||
messageNodes.put(messageId, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isMine = outgoing;
|
|
||||||
|
|
||||||
// منوی راستکلیک/کلیک (بدون تغییر در ساختار کدت)
|
|
||||||
ContextMenu menu = buildMessageMenu(isMine, messageId, type, content);
|
|
||||||
row.setOnContextMenuRequested(ev -> {
|
|
||||||
menu.show(row, ev.getScreenX(), ev.getScreenY());
|
|
||||||
ev.consume();
|
|
||||||
});
|
|
||||||
row.setOnMouseClicked(ev -> {
|
|
||||||
if (ev.getButton() == javafx.scene.input.MouseButton.PRIMARY && ev.getClickCount() == 1) {
|
|
||||||
menu.show(row, ev.getScreenX(), ev.getScreenY());
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
// Forward header (اختیاری)
|
||||||
|
if (hasVal(forwardedFrom) || hasVal(forwardedBy)) {
|
||||||
|
bubble.getChildren().add(buildForwardHeader(forwardedFrom, forwardedBy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply preview (اختیاری)
|
||||||
|
if (hasVal(replyToId)) {
|
||||||
|
bubble.getChildren().add(buildReplyBoxFromIndex(replyToId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// متن اصلی
|
||||||
|
bubble.getChildren().add(msg);
|
||||||
|
|
||||||
|
// Reactions (اختیاری) + برچسب برای تعویض سریع در ریِلتایم
|
||||||
|
if (reactions != null && reactions.length() > 0) {
|
||||||
|
Node rxBar = buildReactionsBarFromJson(reactions, dark);
|
||||||
|
rxBar.getProperties().put("role", "reactionsBar");
|
||||||
|
bubble.getChildren().add(rxBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ردیف چیدمان راست/چپ ===
|
||||||
|
HBox row = new HBox(bubble);
|
||||||
|
row.setFillHeight(true);
|
||||||
|
row.setSpacing(4);
|
||||||
|
row.setAlignment(outgoing ? Pos.CENTER_RIGHT : Pos.CENTER_LEFT);
|
||||||
|
row.setPadding(new Insets(2, 6, 2, 6));
|
||||||
|
|
||||||
|
// اضافه به کانتینر
|
||||||
|
messageContainer.getChildren().add(row);
|
||||||
|
|
||||||
|
// ایندکس نود برای آپدیت/حذف realtime
|
||||||
|
if (messageId != null && !messageId.isBlank()) {
|
||||||
|
messageNodes.put(messageId, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMine = outgoing;
|
||||||
|
|
||||||
|
// منوی راستکلیک/کلیک (بدون تغییر در ساختار کدت)
|
||||||
|
ContextMenu menu = buildMessageMenu(isMine, messageId, type, content);
|
||||||
|
row.setOnContextMenuRequested(ev -> {
|
||||||
|
menu.show(row, ev.getScreenX(), ev.getScreenY());
|
||||||
|
ev.consume();
|
||||||
|
});
|
||||||
|
row.setOnMouseClicked(ev -> {
|
||||||
|
if (ev.getButton() == javafx.scene.input.MouseButton.PRIMARY && ev.getClickCount() == 1) {
|
||||||
|
menu.show(row, ev.getScreenX(), ev.getScreenY());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private ContextMenu buildMessageMenu(boolean isMine, String messageId, String type, String content) {
|
private ContextMenu buildMessageMenu(boolean isMine, String messageId, String type, String content) {
|
||||||
ContextMenu menu = new ContextMenu();
|
ContextMenu menu = new ContextMenu();
|
||||||
@@ -1973,37 +1977,30 @@ private void addBubble(
|
|||||||
data.optString("last_seen", null)
|
data.optString("last_seen", null)
|
||||||
));
|
));
|
||||||
|
|
||||||
// ⭐️ بلاک؟
|
// ❌ هیچ applyMode اینجا نزن!
|
||||||
boolean blocked = data.optBoolean("blocked", false)
|
// حتی اگر blocked آمد، تصمیم مود از بیرون میآید.
|
||||||
|| data.optBoolean("is_blocked", false)
|
|
||||||
|| data.optBoolean("blocked_by_me", false);
|
|
||||||
|
|
||||||
if (blocked) {
|
|
||||||
if (readOnlyLabel != null) readOnlyLabel.setText(""); // فقط UNBLOCK را نشان بده
|
|
||||||
applyMode(ChatViewMode.BLOCKED);
|
|
||||||
} else {
|
|
||||||
applyMode(ChatViewMode.NORMAL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void updateGroupHeader(ChatEntry entry, JSONObject data) {
|
private void updateGroupHeader(ChatEntry entry, JSONObject data) {
|
||||||
chatTitle.setText(nz(data.optString("group_name", entry.getName())));
|
chatTitle.setText(nz(data.optString("group_name", entry.getName())));
|
||||||
|
|
||||||
String img = data.optString("image_url", "");
|
String img = data.optString("image_url", "");
|
||||||
if (hasVal(img)) {
|
if (hasVal(img)) {
|
||||||
try {
|
try {
|
||||||
Image im = AvatarLocalResolver.load(img); // ⬅️
|
Image im = AvatarLocalResolver.load(img);
|
||||||
if (im != null) userAvatar.setImage(im);
|
if (im != null) userAvatar.setImage(im);
|
||||||
userAvatar.setClip(new Circle(20, 20, 20));
|
userAvatar.setClip(new Circle(20, 20, 20));
|
||||||
} catch (Exception ignore) {}
|
} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int members = data.optInt("member_count", 0);
|
int members = data.optInt("member_count", 0);
|
||||||
int online = data.optInt("online_count", -1);
|
int online = data.optInt("online_count", -1);
|
||||||
chatStatus.setText(online >= 0 ? (members + " members, " + online + " online")
|
chatStatus.setText(online >= 0 ? (members + " members, " + online + " online")
|
||||||
: (members + " members"));
|
: (members + " members"));
|
||||||
|
|
||||||
|
// ❌ هیچ applyMode اینجا نزن!
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void updateChannelHeader(ChatEntry entry, JSONObject data) {
|
// private void updateChannelHeader(ChatEntry entry, JSONObject data) {
|
||||||
@@ -2039,14 +2036,8 @@ private void addBubble(
|
|||||||
int subs = data.optInt("member_count", 0);
|
int subs = data.optInt("member_count", 0);
|
||||||
chatStatus.setText(subs + " subscribers");
|
chatStatus.setText(subs + " subscribers");
|
||||||
|
|
||||||
boolean canPost = canPostToChannel(entry, data);
|
// ❌ هیچ applyMode اینجا نزن!
|
||||||
if (canPost) {
|
// حتی اگر can_post را بده، به مود دست نزن.
|
||||||
applyMode(ChatViewMode.NORMAL);
|
|
||||||
Platform.runLater(() -> messageInput.requestFocus());
|
|
||||||
} else {
|
|
||||||
if (readOnlyLabel != null) readOnlyLabel.setText("YOU CAN’T SEND MESSAGES IN THIS CHANNEL");
|
|
||||||
applyMode(ChatViewMode.READ_ONLY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2101,11 +2092,46 @@ private void addBubble(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @FXML
|
||||||
|
// private void onJoinClicked() {
|
||||||
|
// if (currentChat == null) return;
|
||||||
|
//
|
||||||
|
// // 1) internal_uuid کاربر فعلی (UUID)
|
||||||
|
// String myInternalUuid = Session.currentUser != null
|
||||||
|
// ? Session.currentUser.optString("internal_uuid", "")
|
||||||
|
// : "";
|
||||||
|
// if (myInternalUuid.isBlank()) {
|
||||||
|
// addSystemMessage("Join failed: missing current user internal_uuid.");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 2) internal_uuid مقصد (گروه/کانال)
|
||||||
|
// String targetId = currentChat.getId().toString();
|
||||||
|
//
|
||||||
|
// // 3) نوع و نام اکشن
|
||||||
|
// String t = currentChat.getType();
|
||||||
|
// String action = "group".equalsIgnoreCase(t) ? "join_group" : "join_channel";
|
||||||
|
//
|
||||||
|
// // 4) درخواست طبق قرارداد سرور (کلیدها: user_id = UUID کاربر، id = UUID مقصد)
|
||||||
|
// JSONObject req = new JSONObject()
|
||||||
|
// .put("action", action)
|
||||||
|
// .put("user_id", myInternalUuid) // ← UUID
|
||||||
|
// .put("id", targetId); // ← UUID گروه/کانال
|
||||||
|
//
|
||||||
|
// JSONObject res = ActionHandler.sendWithResponse(req);
|
||||||
|
// if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||||
|
// MainController.getInstance().onJoinedOrAdded(currentChat);
|
||||||
|
// applyMode(ChatViewMode.NORMAL);
|
||||||
|
// Platform.runLater(() -> messageInput.requestFocus());
|
||||||
|
// } else {
|
||||||
|
// addSystemMessage("Join failed: " + (res != null ? res.optString("message","") : "no response"));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void onJoinClicked() {
|
private void onJoinClicked() {
|
||||||
if (currentChat == null) return;
|
if (currentChat == null) return;
|
||||||
|
|
||||||
// 1) internal_uuid کاربر فعلی (UUID)
|
|
||||||
String myInternalUuid = Session.currentUser != null
|
String myInternalUuid = Session.currentUser != null
|
||||||
? Session.currentUser.optString("internal_uuid", "")
|
? Session.currentUser.optString("internal_uuid", "")
|
||||||
: "";
|
: "";
|
||||||
@@ -2114,27 +2140,71 @@ private void addBubble(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) internal_uuid مقصد (گروه/کانال)
|
final String targetId = currentChat.getId().toString();
|
||||||
String targetId = currentChat.getId().toString();
|
final String t = currentChat.getType();
|
||||||
|
final String action = "group".equalsIgnoreCase(t) ? "join_group" : "join_channel";
|
||||||
|
|
||||||
// 3) نوع و نام اکشن
|
|
||||||
String t = currentChat.getType();
|
|
||||||
String action = "group".equalsIgnoreCase(t) ? "join_group" : "join_channel";
|
|
||||||
|
|
||||||
// 4) درخواست طبق قرارداد سرور (کلیدها: user_id = UUID کاربر، id = UUID مقصد)
|
|
||||||
JSONObject req = new JSONObject()
|
JSONObject req = new JSONObject()
|
||||||
.put("action", action)
|
.put("action", action)
|
||||||
.put("user_id", myInternalUuid) // ← UUID
|
.put("user_id", myInternalUuid) // UUID من
|
||||||
.put("id", targetId); // ← UUID گروه/کانال
|
.put("id", targetId); // UUID مقصد
|
||||||
|
|
||||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
// ⛔ روی ترد FX کال شبکه نزن!
|
||||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
new Thread(() -> {
|
||||||
MainController.getInstance().onJoinedOrAdded(currentChat);
|
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||||
applyMode(ChatViewMode.NORMAL);
|
boolean ok = (res != null && "success".equalsIgnoreCase(res.optString("status")));
|
||||||
Platform.runLater(() -> messageInput.requestFocus());
|
|
||||||
} else {
|
Platform.runLater(() -> {
|
||||||
addSystemMessage("Join failed: " + (res != null ? res.optString("message","") : "no response"));
|
if (!ok) {
|
||||||
}
|
addSystemMessage("Join failed: " + (res != null ? res.optString("message","") : "no response"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// برای جلوگیری از داونگِرید مود توسط هِدرِ بعدی:
|
||||||
|
justJoinedThisChat = true;
|
||||||
|
|
||||||
|
// اگر MainController نال نبود، لیست چتها را آپدیت کن
|
||||||
|
var mc = MainController.getInstance();
|
||||||
|
if (mc != null) {
|
||||||
|
mc.onJoinedOrAdded(currentChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⚠️ منطق: گروه → همیشه Composer باز شود.
|
||||||
|
// کانال → اگر اجازهی پست داری، Composer؛ وگرنه READ_ONLY با پیام.
|
||||||
|
// if ("group".equalsIgnoreCase(t)) {
|
||||||
|
// applyMode(ChatViewMode.NORMAL);
|
||||||
|
// messageInput.requestFocus();
|
||||||
|
// } else if ("channel".equalsIgnoreCase(t)) {
|
||||||
|
// // اگر میخواهی «همانجا» Composer فعال شود، باید اجازهی پست را
|
||||||
|
// // یا از سرور بگیری یا لوکال ست کنی (طبق بیزینسلاکت).
|
||||||
|
// // اینجا منطقیتر: فقط اگر واقعاً اجازه داری.
|
||||||
|
// boolean canPost =
|
||||||
|
// (currentChat.isOwner() || currentChat.isAdmin()) ||
|
||||||
|
// (currentChat.getPermissions()!=null && currentChat.getPermissions().optBoolean("can_post", false));
|
||||||
|
// if (canPost) {
|
||||||
|
// applyMode(ChatViewMode.NORMAL);
|
||||||
|
// messageInput.requestFocus();
|
||||||
|
// } else {
|
||||||
|
// if (readOnlyLabel != null)
|
||||||
|
// readOnlyLabel.setText("YOU CAN’T SEND MESSAGES IN THIS CHANNEL");
|
||||||
|
// applyMode(ChatViewMode.READ_ONLY);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// applyMode(ChatViewMode.NORMAL);
|
||||||
|
// messageInput.requestFocus();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if ("group".equalsIgnoreCase(t)) {
|
||||||
|
applyMode(ChatViewMode.NORMAL);
|
||||||
|
} else if ("channel".equalsIgnoreCase(t)) {
|
||||||
|
// تصمیم بیزینسی: ثبت کن.
|
||||||
|
applyMode(ChatViewMode.READ_ONLY); // یا NORMAL اگر همین را میخواهی
|
||||||
|
}
|
||||||
|
|
||||||
|
// هدر را دوباره بگیر (ولی نگذار مود را خراب کند)
|
||||||
|
fetchAndRenderHeader(currentChat);
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -2266,6 +2336,55 @@ private void addBubble(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// private void applyMode(ChatViewMode mode) {
|
||||||
|
// currentMode = mode;
|
||||||
|
//
|
||||||
|
// boolean normal = (mode == ChatViewMode.NORMAL);
|
||||||
|
// boolean needsJoin = (mode == ChatViewMode.NEEDS_JOIN);
|
||||||
|
// boolean needsAdd = (mode == ChatViewMode.NEEDS_ADD_CONTACT);
|
||||||
|
// boolean readOnly = (mode == ChatViewMode.READ_ONLY);
|
||||||
|
// boolean blocked = (mode == ChatViewMode.BLOCKED);
|
||||||
|
//
|
||||||
|
// // Composer فقط در حالت نرمال
|
||||||
|
// composerPane.setVisible(normal);
|
||||||
|
// composerPane.setManaged(normal);
|
||||||
|
//
|
||||||
|
// // Join / Add
|
||||||
|
// joinPane.setVisible(needsJoin);
|
||||||
|
// joinPane.setManaged(needsJoin);
|
||||||
|
// addContactPane.setVisible(needsAdd);
|
||||||
|
// addContactPane.setManaged(needsAdd);
|
||||||
|
//
|
||||||
|
// // پنل پایین برای READ_ONLY/BLOCKED
|
||||||
|
// boolean showRO = readOnly || blocked;
|
||||||
|
// if (readOnlyPane != null) {
|
||||||
|
// readOnlyPane.setVisible(showRO);
|
||||||
|
// readOnlyPane.setManaged(showRO);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // متن آبی برای READ_ONLY
|
||||||
|
// if (readOnlyLabel != null) {
|
||||||
|
// readOnlyLabel.setVisible(readOnly);
|
||||||
|
// readOnlyLabel.setManaged(readOnly);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // دکمهٔ قرمز UNBLOCK فقط در BLOCKED
|
||||||
|
// if (unblockBtn != null) {
|
||||||
|
// unblockBtn.setVisible(blocked);
|
||||||
|
// unblockBtn.setManaged(blocked);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // متن دکمههای Join/Add
|
||||||
|
// if (needsJoin && joinButton != null && currentChat != null) {
|
||||||
|
// String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP";
|
||||||
|
// joinButton.setText(("Join " + what).toUpperCase());
|
||||||
|
// }
|
||||||
|
// if (needsAdd && addContactButton != null) {
|
||||||
|
// addContactButton.setText("ADD CONTACT");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
private void applyMode(ChatViewMode mode) {
|
private void applyMode(ChatViewMode mode) {
|
||||||
currentMode = mode;
|
currentMode = mode;
|
||||||
|
|
||||||
@@ -2275,36 +2394,27 @@ private void addBubble(
|
|||||||
boolean readOnly = (mode == ChatViewMode.READ_ONLY);
|
boolean readOnly = (mode == ChatViewMode.READ_ONLY);
|
||||||
boolean blocked = (mode == ChatViewMode.BLOCKED);
|
boolean blocked = (mode == ChatViewMode.BLOCKED);
|
||||||
|
|
||||||
// Composer فقط در حالت نرمال
|
// فقط در حالت نرمال: کامپوزر
|
||||||
composerPane.setVisible(normal);
|
composerPane.setVisible(normal);
|
||||||
composerPane.setManaged(normal);
|
composerPane.setManaged(normal);
|
||||||
|
|
||||||
// Join / Add
|
// پنلهای Join / Add
|
||||||
joinPane.setVisible(needsJoin);
|
joinPane.setVisible(needsJoin);
|
||||||
joinPane.setManaged(needsJoin);
|
joinPane.setManaged(needsJoin);
|
||||||
addContactPane.setVisible(needsAdd);
|
addContactPane.setVisible(needsAdd);
|
||||||
addContactPane.setManaged(needsAdd);
|
addContactPane.setManaged(needsAdd);
|
||||||
|
|
||||||
// پنل پایین برای READ_ONLY/BLOCKED
|
// پنلهای پایین
|
||||||
boolean showRO = readOnly || blocked;
|
|
||||||
if (readOnlyPane != null) {
|
if (readOnlyPane != null) {
|
||||||
readOnlyPane.setVisible(showRO);
|
readOnlyPane.setVisible(readOnly);
|
||||||
readOnlyPane.setManaged(showRO);
|
readOnlyPane.setManaged(readOnly);
|
||||||
|
}
|
||||||
|
if (blockedPane != null) {
|
||||||
|
blockedPane.setVisible(blocked);
|
||||||
|
blockedPane.setManaged(blocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
// متن آبی برای READ_ONLY
|
// متن دکمهها
|
||||||
if (readOnlyLabel != null) {
|
|
||||||
readOnlyLabel.setVisible(readOnly);
|
|
||||||
readOnlyLabel.setManaged(readOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// دکمهٔ قرمز UNBLOCK فقط در BLOCKED
|
|
||||||
if (unblockBtn != null) {
|
|
||||||
unblockBtn.setVisible(blocked);
|
|
||||||
unblockBtn.setManaged(blocked);
|
|
||||||
}
|
|
||||||
|
|
||||||
// متن دکمههای Join/Add
|
|
||||||
if (needsJoin && joinButton != null && currentChat != null) {
|
if (needsJoin && joinButton != null && currentChat != null) {
|
||||||
String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP";
|
String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP";
|
||||||
joinButton.setText(("Join " + what).toUpperCase());
|
joinButton.setText(("Join " + what).toUpperCase());
|
||||||
@@ -2318,25 +2428,43 @@ private void addBubble(
|
|||||||
@FXML
|
@FXML
|
||||||
private void onUnblockClicked() {
|
private void onUnblockClicked() {
|
||||||
if (currentChat == null) return;
|
if (currentChat == null) return;
|
||||||
UUID other = currentChat.getOtherUserId();
|
|
||||||
if (other == null && currentChat.getDisplayId() == null) return;
|
|
||||||
|
|
||||||
|
final String viewerUuid = org.to.telegramfinalproject.Client.Session.getUserUUID(); // internal_uuid
|
||||||
org.json.JSONObject req = new org.json.JSONObject()
|
if (viewerUuid == null || viewerUuid.isBlank()) {
|
||||||
.put("action", "toggle_block")
|
addSystemMessage("Missing viewer UUID");
|
||||||
.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"))
|
return;
|
||||||
.put("target_id", (other != null) ? other.toString() : currentChat.getDisplayId());
|
|
||||||
|
|
||||||
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
|
||||||
boolean ok = (res != null) && ("ok".equalsIgnoreCase(res.optString("status"))
|
|
||||||
|| "success".equalsIgnoreCase(res.optString("status")));
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
applyMode(ChatViewMode.NORMAL);
|
|
||||||
Platform.runLater(() -> messageInput.requestFocus());
|
|
||||||
} else {
|
|
||||||
addSystemMessage("Unblock failed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// شبکه روی بکگراند
|
||||||
|
new Thread(() -> {
|
||||||
|
// 1) اگر otherUserId نداشتیم، از سرور بگیر
|
||||||
|
java.util.UUID other = currentChat.getOtherUserId();
|
||||||
|
if (other == null) {
|
||||||
|
other = resolvePeerUuidFromServer(currentChat);
|
||||||
|
}
|
||||||
|
if (other == null) {
|
||||||
|
Platform.runLater(() -> addSystemMessage("Could not resolve peer UUID."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) حالا درخواست آنبلاک
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject()
|
||||||
|
.put("action", "toggle_block")
|
||||||
|
.put("user_id", viewerUuid) // internal_uuid خودت
|
||||||
|
.put("target_id", other.toString()); // internal_uuid طرف مقابل
|
||||||
|
|
||||||
|
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||||
|
boolean ok = res != null && "success".equalsIgnoreCase(res.optString("status"));
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (ok) {
|
||||||
|
applyMode(ChatViewMode.NORMAL);
|
||||||
|
messageInput.requestFocus();
|
||||||
|
} else {
|
||||||
|
addSystemMessage("Unblock failed: " + (res != null ? res.optString("message","") : ""));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2812,6 +2940,62 @@ private void addBubble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void syncIconsWithTheme() {
|
||||||
|
boolean dark = themeManager.isDarkMode();
|
||||||
|
String suffix = dark ? "_light.png" : "_dark.png";
|
||||||
|
|
||||||
|
if (attachmentIcon != null) attachmentIcon.setImage(loadIcon("attachment" + suffix));
|
||||||
|
if (sendIcon != null) sendIcon.setImage(loadIcon("send_cyan2.png"));
|
||||||
|
if (searchIcon != null) searchIcon.setImage(loadIcon("search" + suffix));
|
||||||
|
if (moreIcon != null) moreIcon.setImage(loadIcon("more" + suffix));
|
||||||
|
|
||||||
|
if (chatTitle != null) chatTitle.setStyle(dark ? "-fx-text-fill:#e8f1f8;" : "-fx-text-fill:#0f141a;");
|
||||||
|
if (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
|
||||||
|
|
||||||
|
// 👇 این خط قبلاً ممکن بود NPE بده
|
||||||
|
if (viewProfileItem != null && viewProfileItem.getGraphic() instanceof ImageView iv) {
|
||||||
|
iv.setImage(loadIcon("view_profile" + suffix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ChatPageController
|
||||||
|
|
||||||
|
private UUID resolvePeerUuidFromServer(ChatEntry chat) {
|
||||||
|
if (chat == null || !"private".equalsIgnoreCase(chat.getType())) return null;
|
||||||
|
|
||||||
|
// اگر از قبل ست شده بود از همون استفاده کن
|
||||||
|
try {
|
||||||
|
UUID cached = chat.getOtherUserId();
|
||||||
|
if (cached != null) return cached;
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
|
// درخواست به سرور برای گرفتن target_id
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject()
|
||||||
|
.put("action", "get_private_chat_target")
|
||||||
|
.put("chat_id", chat.getId().toString());
|
||||||
|
// اگر سمت سرور لازم دارد، میتوانی viewer را هم بفرستی:
|
||||||
|
// .put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||||
|
if (res == null || !"success".equalsIgnoreCase(res.optString("status"))) return null;
|
||||||
|
|
||||||
|
org.json.JSONObject data = res.optJSONObject("data");
|
||||||
|
if (data == null) return null;
|
||||||
|
|
||||||
|
String tid = data.optString("target_id", "");
|
||||||
|
if (tid == null || tid.isBlank()) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.util.UUID target = java.util.UUID.fromString(tid);
|
||||||
|
chat.setOtherUserId(target); // کش محلی کن که دفعات بعد لازم نشه
|
||||||
|
return target;
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class MainController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void refreshChatListUI() {
|
public void refreshChatListUI() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
chatListContainer.getChildren().clear();
|
chatListContainer.getChildren().clear();
|
||||||
itemControllers.clear();
|
itemControllers.clear();
|
||||||
@@ -430,31 +430,81 @@ public class MainController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// void openChat(ChatEntry chat) {
|
||||||
|
// try {
|
||||||
|
// FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
||||||
|
// Node chatPage = loader.load();
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// ChatPageController controller = loader.getController();
|
||||||
|
// controller.showChat(chat);
|
||||||
|
//
|
||||||
|
// this.chatPageController = controller;
|
||||||
|
// Session.currentChatId = chat.getId().toString();
|
||||||
|
//
|
||||||
|
// chatDisplayArea.getChildren().setAll(chatPage);
|
||||||
|
// chat.setUnreadCount(0);
|
||||||
|
// ChatItemController item = itemControllers.get(chat.getId());
|
||||||
|
// if (item != null) item.setUnread(0);
|
||||||
|
//
|
||||||
|
// } catch (IOException ex) {
|
||||||
|
// ex.printStackTrace();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
void openChat(ChatEntry chat) {
|
void openChat(ChatEntry chat) {
|
||||||
try {
|
// فقط برای پرایوت: اول بلاک/بلاکشدن را چک کن، بعد مود را تعیین کن
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
if ("private".equalsIgnoreCase(chat.getType())) {
|
||||||
Node chatPage = loader.load();
|
final String viewerId = org.to.telegramfinalproject.Client.Session.getUserUUID(); // internal_uuid کاربر فعلی
|
||||||
|
if (viewerId != null && !viewerId.isBlank()) {
|
||||||
|
new Thread(() -> {
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject()
|
||||||
|
.put("action", "check_block_status_by_chat")
|
||||||
|
.put("viewer_id", viewerId)
|
||||||
|
.put("chat_id", chat.getId().toString());
|
||||||
|
|
||||||
|
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||||
|
|
||||||
|
boolean blockedByMe = false;
|
||||||
|
boolean blockedMe = false;
|
||||||
|
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||||
|
org.json.JSONObject data = res.optJSONObject("data");
|
||||||
|
if (data != null) {
|
||||||
|
// اسم فیلدها را با سرور خودت یکی کن
|
||||||
|
blockedByMe = data.optBoolean("blocked_by_me", false) || data.optBoolean("is_blocked", false);
|
||||||
|
blockedMe = data.optBoolean("blocked_me", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ChatViewMode mode = blockedByMe
|
||||||
|
? ChatViewMode.BLOCKED
|
||||||
|
: (blockedMe ? ChatViewMode.READ_ONLY : ChatViewMode.NORMAL);
|
||||||
|
|
||||||
|
Platform.runLater(() -> openChatWithMode(chat, mode));
|
||||||
|
}).start();
|
||||||
|
return; // نذار پایین دوباره باز شود
|
||||||
ChatPageController controller = loader.getController();
|
}
|
||||||
controller.showChat(chat);
|
|
||||||
|
|
||||||
this.chatPageController = controller;
|
|
||||||
Session.currentChatId = chat.getId().toString();
|
|
||||||
|
|
||||||
chatDisplayArea.getChildren().setAll(chatPage);
|
|
||||||
chat.setUnreadCount(0);
|
|
||||||
ChatItemController item = itemControllers.get(chat.getId());
|
|
||||||
if (item != null) item.setUnread(0);
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("channel".equalsIgnoreCase(chat.getType())) {
|
||||||
|
boolean canPost =
|
||||||
|
chat.isOwner() || chat.isAdmin() ||
|
||||||
|
(chat.getPermissions() != null &&
|
||||||
|
chat.getPermissions().optBoolean("can_post", false));
|
||||||
|
// اگر فقط owner/admin ملاک توست، خط بالا را به این تغییر بده:
|
||||||
|
// boolean canPost = chat.isOwner() || chat.isAdmin();
|
||||||
|
|
||||||
|
openChatWithMode(chat, canPost ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// غیرپرایوت یا اگر viewerId نبود → نرمال
|
||||||
|
openChatWithMode(chat, ChatViewMode.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import javafx.scene.image.ImageView;
|
|||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.shape.Circle;
|
import javafx.scene.shape.Circle;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -48,7 +51,7 @@ public class SettingsController {
|
|||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
editProfileItem.setOnAction(e -> openEditProfile());
|
editProfileItem.setOnAction(e -> openEditProfile());
|
||||||
logoutItem.setOnAction(e -> System.out.println("Log Out clicked"));
|
logoutItem.setOnAction(e -> onLogoutClicked());
|
||||||
|
|
||||||
populateFromSession();
|
populateFromSession();
|
||||||
|
|
||||||
@@ -230,4 +233,33 @@ public class SettingsController {
|
|||||||
Circle clip = new Circle(40, 30, 38); // centerX, centerY, radius
|
Circle clip = new Circle(40, 30, 38); // centerX, centerY, radius
|
||||||
profileImage.setClip(clip);
|
profileImage.setClip(clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// جایی مثل SidebarMenuController یا MainController
|
||||||
|
private void onLogoutClicked() {
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject req = new JSONObject().put("action","logout"); // user_id لازم نیست
|
||||||
|
req.put("user_id",Session.getUserUUID()); // user_id لازم نیست
|
||||||
|
|
||||||
|
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||||
|
try {
|
||||||
|
// قطع ارتباط/لیسنر (اگر متد داری)
|
||||||
|
// TelegramClient.disconnect();
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
|
// پاکسازی امن سشن (ترجیحاً clear بهجای null)
|
||||||
|
Session.currentUser = null;
|
||||||
|
Session.chatList = null;
|
||||||
|
AppRouter.showIntro(); // intro.fxml
|
||||||
|
} else {
|
||||||
|
new Alert(Alert.AlertType.ERROR,
|
||||||
|
"Logout not successful: " + (res != null ? res.optString("message") : "No response")
|
||||||
|
).showAndWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,20 +56,25 @@
|
|||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* لینکاستایل دکمهها در بنر */
|
|
||||||
.chat-footer-banner .footer-link-btn {
|
|
||||||
|
.button.footer-link-btn {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-text-fill: #1E88E5; /* آبی پیشفرض */
|
|
||||||
-fx-font-weight: 700;
|
|
||||||
-fx-padding: 6 12;
|
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
|
-fx-background-radius: 0;
|
||||||
|
-fx-border-color: transparent;
|
||||||
|
-fx-border-width: 0;
|
||||||
|
-fx-padding: 6 12;
|
||||||
|
-fx-font-weight: 700;
|
||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
|
-fx-effect: null;
|
||||||
|
-fx-text-fill: #1E88E5; /* پیشفرض آبی */
|
||||||
}
|
}
|
||||||
.chat-footer-banner .footer-link-btn:hover {
|
.button.footer-link-btn:hover {
|
||||||
-fx-underline: true;
|
-fx-underline: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* نسخه قرمز برای UNBLOCK */
|
/* نسخهی قرمز برای UNBLOCK */
|
||||||
.chat-footer-banner .footer-link-btn.danger {
|
.button.footer-link-btn.danger {
|
||||||
-fx-text-fill: #D32F2F; /* قرمز */
|
-fx-text-fill: #D32F2F;
|
||||||
}
|
}
|
||||||
@@ -147,10 +147,14 @@
|
|||||||
visible="false" managed="false" styleClass="chat-footer-banner">
|
visible="false" managed="false" styleClass="chat-footer-banner">
|
||||||
<padding><Insets top="12" right="12" bottom="12" left="12"/></padding>
|
<padding><Insets top="12" right="12" bottom="12" left="12"/></padding>
|
||||||
|
|
||||||
<Button fx:id="unblockBtn"
|
<Button
|
||||||
|
fx:id="unblockBtn"
|
||||||
|
id="unblockAction"
|
||||||
text="UNBLOCK"
|
text="UNBLOCK"
|
||||||
onAction="#onUnblockClicked"
|
onAction="#onUnblockClicked"
|
||||||
styleClass="footer-link-btn danger"/>
|
styleClass="footer-link-btn danger"
|
||||||
|
style="-fx-background-color: transparent; -fx-background-insets: 0; -fx-background-radius: 0; -fx-border-color: transparent; -fx-border-width: 0; -fx-text-fill: #D32F2F; -fx-font-weight: 700; -fx-padding: 6 12; -fx-effect: null;"/>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
Reference in New Issue
Block a user