add to group or channel real time

This commit is contained in:
2025-09-05 13:12:22 +03:30
parent fd18c75cd9
commit 106cfbbf3d
13 changed files with 644 additions and 300 deletions
+8 -3
View File
@@ -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;
@@ -64,6 +64,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
@@ -114,6 +116,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 =====
@@ -372,7 +376,6 @@ public class ChatPageController {
// if (!text.isEmpty()) { // if (!text.isEmpty()) {
// addMessage("You", text); // addMessage("You", text);
// messageInput.clear(); // messageInput.clear();
// // TODO: send to server
// } // }
// } // }
@@ -691,7 +694,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);
@@ -699,38 +702,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 youre not fully relying on CSS) // // header text tint (if youre 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);
@@ -759,7 +762,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);
// //
// //
@@ -800,7 +803,7 @@ 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();
@@ -830,7 +833,7 @@ public void showChat(ChatEntry entry) {
// حالا هدر بیاد، دوباره نهایی‌اش می‌کنیم // حالا هدر بیاد، دوباره نهایی‌اش می‌کنیم
fetchAndRenderHeader(entry); fetchAndRenderHeader(entry);
} }
@@ -1175,7 +1178,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());
@@ -1186,7 +1189,7 @@ public void showChat(ChatEntry entry) {
private void addBubble( private void addBubble(
boolean outgoing, boolean outgoing,
String displayName, String displayName,
String type, String type,
@@ -1198,7 +1201,7 @@ private void addBubble(
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)";
@@ -1289,7 +1292,7 @@ private void addBubble(
menu.show(row, ev.getScreenX(), ev.getScreenY()); 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();
@@ -1748,18 +1751,10 @@ 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) {
@@ -1768,17 +1763,18 @@ private void addBubble(
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) {
@@ -1814,14 +1810,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 CANT SEND MESSAGES IN THIS CHANNEL");
applyMode(ChatViewMode.READ_ONLY);
}
} }
@@ -1876,11 +1866,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", "")
: ""; : "";
@@ -1889,27 +1914,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 مقصد
// ⛔ روی ترد FX کال شبکه نزن!
new Thread(() -> {
JSONObject res = ActionHandler.sendWithResponse(req); JSONObject res = ActionHandler.sendWithResponse(req);
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) { boolean ok = (res != null && "success".equalsIgnoreCase(res.optString("status")));
MainController.getInstance().onJoinedOrAdded(currentChat);
applyMode(ChatViewMode.NORMAL); Platform.runLater(() -> {
Platform.runLater(() -> messageInput.requestFocus()); if (!ok) {
} else {
addSystemMessage("Join failed: " + (res != null ? res.optString("message","") : "no response")); 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 CANT 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();
} }
// //
@@ -2041,6 +2110,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;
@@ -2050,36 +2168,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());
@@ -2090,32 +2199,53 @@ 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
if (viewerUuid == null || viewerUuid.isBlank()) {
addSystemMessage("Missing viewer UUID");
return;
}
// شبکه روی بک‌گراند
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() org.json.JSONObject req = new org.json.JSONObject()
.put("action", "toggle_block") .put("action", "toggle_block")
.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id")) .put("user_id", viewerUuid) // internal_uuid خودت
.put("target_id", (other != null) ? other.toString() : currentChat.getDisplayId()); .put("target_id", other.toString()); // internal_uuid طرف مقابل
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req); org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
boolean ok = (res != null) && ("ok".equalsIgnoreCase(res.optString("status")) boolean ok = res != null && "success".equalsIgnoreCase(res.optString("status"));
|| "success".equalsIgnoreCase(res.optString("status")));
Platform.runLater(() -> {
if (ok) { if (ok) {
applyMode(ChatViewMode.NORMAL); applyMode(ChatViewMode.NORMAL);
Platform.runLater(() -> messageInput.requestFocus()); messageInput.requestFocus();
} else { } else {
addSystemMessage("Unblock failed."); addSystemMessage("Unblock failed: " + (res != null ? res.optString("message","") : ""));
} }
});
}).start();
} }
private boolean canPostToChannel(ChatEntry entry, JSONObject headerData) { private boolean canPostToChannel(ChatEntry entry, JSONObject headerData) {
// 1) اگر سرور صراحتاً can_post داد، همان را بگیر // 1) اگر سرور صراحتاً can_post داد، همان را بگیر
if (headerData != null && headerData.has("can_post")) { if (headerData != null && headerData.has("can_post")) {
@@ -2587,6 +2717,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;
}
}
@@ -156,7 +156,7 @@ public class MainController {
} }
void refreshChatListUI() { public void refreshChatListUI() {
Platform.runLater(() -> { Platform.runLater(() -> {
chatListContainer.getChildren().clear(); chatListContainer.getChildren().clear();
itemControllers.clear(); itemControllers.clear();
@@ -433,33 +433,83 @@ 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) {
ChatPageController controller = loader.getController(); // اسم فیلدها را با سرور خودت یکی کن
controller.showChat(chat); blockedByMe = data.optBoolean("blocked_by_me", false) || data.optBoolean("is_blocked", false);
blockedMe = data.optBoolean("blocked_me", false);
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();
} }
} }
final ChatViewMode mode = blockedByMe
? ChatViewMode.BLOCKED
: (blockedMe ? ChatViewMode.READ_ONLY : ChatViewMode.NORMAL);
Platform.runLater(() -> openChatWithMode(chat, mode));
}).start();
return; // نذار پایین دوباره باز شود
}
}
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);
}
private void openChatWithMode(ChatEntry chat, ChatViewMode mode) { private void openChatWithMode(ChatEntry chat, ChatViewMode mode) {
try { try {
@@ -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