diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 274c4f7..9741c93 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -15,8 +15,13 @@ module org.to.telegramfinalproject {
requires spark.core;
requires javax.servlet.api;
requires mp3agic;
- opens org.to.telegramfinalproject to javafx.fxml;
- exports org.to.telegramfinalproject;
- exports org.to.telegramfinalproject.Client;
+
+ // FXML کنترلرها در این پکیجاند:
+ opens org.to.telegramfinalproject.UI to javafx.fxml;
+ // اگر FXML از کلاسهای Client هم استفاده میکند:
opens org.to.telegramfinalproject.Client to javafx.fxml;
-}
\ No newline at end of file
+
+ // فقط اگر لازم است از بیرون به این APIها کامپایل شود:
+ exports org.to.telegramfinalproject.Client;
+ // ⚠️ عمداً NOT exporting/opens ریشهی پکیج، چون کلاس ندارد.
+}
diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java
index 803fc78..6c7be2a 100644
--- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java
+++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java
@@ -138,22 +138,22 @@ public class IncomingMessageListener implements Runnable {
JSONObject finalMsg = msg;
JSONObject finalMsg1 = msg;
switch (action) {
- case "added_to_group", "added_to_channel",
- "removed_from_group", "removed_from_channel",
- "chat_deleted", "created_private_chat" -> {
- // این قسمت مستقل از UI/کنسول است
- System.out.println("🔄 Chat list changed. Updating...");
- Session.forceRefreshChatList = true;
-
- String chatId = msg.getString("chat_id");
- String chatType = msg.getString("chat_type");
- ActionHandler.requestChatInfo(chatId, chatType);
-
- 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...");
- ActionHandler.forceExitChat = true;
- }
- }
+// case "added_to_group", "added_to_channel",
+// "removed_from_group", "removed_from_channel",
+// "chat_deleted", "created_private_chat" -> {
+// // این قسمت مستقل از UI/کنسول است
+// System.out.println("🔄 Chat list changed. Updating...");
+// Session.forceRefreshChatList = true;
+//
+// String chatId = msg.getString("chat_id");
+// String chatType = msg.getString("chat_type");
+// ActionHandler.requestChatInfo(chatId, chatType);
+//
+// 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...");
+// ActionHandler.forceExitChat = true;
+// }
+// }
// case "chat_updated" -> {
// 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...");
new Thread(() -> {
try {
@@ -174,7 +256,7 @@ public class IncomingMessageListener implements Runnable {
}).start();
}
- case "new_message" -> {
+ case "new_message" :{
JSONObject data = response.optJSONObject("data");
if (data == null) break;
@@ -198,7 +280,7 @@ public class IncomingMessageListener implements Runnable {
});
}
- case "message_edited" -> {
+ case "message_edited": {
JSONObject ui = normalizeMessageId(msg);
// (اختیاری) اگر ایونت زمان و چت را هم میدهد، میتوانی چتلیست را آپدیت کنی
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);
Platform.runLater(() -> {
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);
Platform.runLater(() -> {
var mc = MainController.getInstance();
@@ -228,12 +310,12 @@ public class IncomingMessageListener implements Runnable {
- case "chat_updated" -> {
+ case "chat_updated": {
var data = response.getJSONObject("data");
bumpChatListFromUpdate(data);
}
- case "user_status_changed" -> {
+ case "user_status_changed" : {
displayRealTimeMessage(action, msg);
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);
}
- default -> {
+ default :{
System.out.println("\n❓ Unknown real-time action: " + action);
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())));
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java
index b6fcb77..a555949 100644
--- a/src/main/java/org/to/telegramfinalproject/Client/Session.java
+++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java
@@ -133,7 +133,6 @@ public class Session {
resortAndRefresh();
}
- /** سورت بر اساس lastMessageTime (نزولی) و آپدیت active/archived */
public static void resortAndRefresh() {
chatList.sort((c1, c2) -> {
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java
index 469b70d..26e2f23 100644
--- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java
+++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java
@@ -68,6 +68,8 @@ public class ChatPageController {
@FXML
private ImageView searchIcon;
+ @FXML private VBox blockedPane;
+
@FXML
private Button moreButton; // 3-dots button
@FXML
@@ -118,6 +120,8 @@ public class ChatPageController {
private boolean blockedByMeFlag = false;
private boolean blockedMeFlag = false;
+ private volatile boolean justJoinedThisChat = false;
+
// ===== state =====
@@ -385,7 +389,6 @@ public class ChatPageController {
// if (!text.isEmpty()) {
// addMessage("You", text);
// messageInput.clear();
-// // TODO: send to server
// }
// }
@@ -704,7 +707,7 @@ public class ChatPageController {
// messageContainer.getChildren().add(msg);
// }
- private void addSystemMessage(String content) {
+ public void addSystemMessage(String content) {
Label sys = new Label(content);
sys.setStyle("-fx-text-fill: gray; -fx-font-size: 11;");
messageContainer.getChildren().add(sys);
@@ -712,38 +715,38 @@ public class ChatPageController {
messageScrollPane.setVvalue(1.0);
}
- /**
- * Update all header/footer icons according to current theme.
- */
- private void syncIconsWithTheme() {
- boolean dark = themeManager.isDarkMode();
- // We use “_light” icons on dark backgrounds, and “_dark” on light backgrounds.
- String suffix = dark ? "_light.png" : "_dark.png";
-
- // attachment
- if (attachmentIcon != null) {
- attachmentIcon.setImage(loadIcon("attachment" + suffix));
- }
- // send
- if (sendIcon != null) {
- sendIcon.setImage(loadIcon("send_cyan2.png"));
- }
- // header icons
- if (searchIcon != null) {
- searchIcon.setImage(loadIcon("search" + suffix));
- }
- if (moreIcon != null) {
- moreIcon.setImage(loadIcon("more" + suffix));
- }
-
- // 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 (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
-
- // View profile icon in more button
- ((ImageView) viewProfileItem.getGraphic())
- .setImage(loadIcon("view_profile" + suffix));
- }
+// /**
+// * Update all header/footer icons according to current theme.
+// */
+// private void syncIconsWithTheme() {
+// boolean dark = themeManager.isDarkMode();
+// // We use “_light” icons on dark backgrounds, and “_dark” on light backgrounds.
+// String suffix = dark ? "_light.png" : "_dark.png";
+//
+// // attachment
+// if (attachmentIcon != null) {
+// attachmentIcon.setImage(loadIcon("attachment" + suffix));
+// }
+// // send
+// if (sendIcon != null) {
+// sendIcon.setImage(loadIcon("send_cyan2.png"));
+// }
+// // header icons
+// if (searchIcon != null) {
+// searchIcon.setImage(loadIcon("search" + suffix));
+// }
+// if (moreIcon != null) {
+// moreIcon.setImage(loadIcon("more" + suffix));
+// }
+//
+// // 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 (chatStatus != null) chatStatus.setStyle(dark ? "-fx-text-fill:#8ea1b2;" : "-fx-text-fill:#7e8a97;");
+//
+// // View profile icon in more button
+// ((ImageView) viewProfileItem.getGraphic())
+// .setImage(loadIcon("view_profile" + suffix));
+// }
private Image loadIcon(String filename) {
var url = getClass().getResource(ICON_BASE + filename);
@@ -772,7 +775,7 @@ public class ChatPageController {
// } else {
// setDefaultHeaderAvatarByType(entry.getType());
// }
-//// userAvatar.setClip(new Circle(20, 20, 20));
+ //// userAvatar.setClip(new Circle(20, 20, 20));
// AvatarFX.circleClip(userAvatar, 36);
//
//
@@ -813,34 +816,35 @@ public class ChatPageController {
// }
-public void showChat(ChatEntry entry) {
- this.currentChat = entry;
- this.chatName = entry.getName();
+ public void showChat(ChatEntry entry) {
+ this.currentChat = entry;
+ this.chatName = entry.getName();
- chatTitle.setText(entry.getName());
- if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
- Image img = AvatarLocalResolver.load(entry.getImageUrl());
- if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType());
- } else {
- setDefaultHeaderAvatarByType(entry.getType());
- }
- AvatarFX.circleClip(userAvatar, 36);
+ chatTitle.setText(entry.getName());
+ if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
+ Image img = AvatarLocalResolver.load(entry.getImageUrl());
+ if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType());
+ } else {
+ setDefaultHeaderAvatarByType(entry.getType());
+ }
+ 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);
- markAsRead(entry);
+ // حالت اولیه (بدون انتظار هدر)
+ 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);
+ }
- // حالا هدر بیاد، دوباره نهاییاش میکنیم
- fetchAndRenderHeader(entry);
+ messageContainer.getChildren().clear();
+ loadMessages(entry);
+ markAsRead(entry);
+
+ // حالا هدر بیاد، دوباره نهاییاش میکنیم
+ fetchAndRenderHeader(entry);
// === (3-dot menu + header click) ===
configureHeaderActions(entry);
@@ -897,7 +901,7 @@ public void showChat(ChatEntry entry) {
public void showChat(ChatEntry entry, ChatViewMode mode) {
this.currentChat = entry;
- // --- Header (fallback until server responds) ---
+ // --- Header ---
chatTitle.setText(entry.getName());
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
Image img = AvatarLocalResolver.load(entry.getImageUrl());
@@ -1400,7 +1404,7 @@ public void showChat(ChatEntry entry) {
// 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());
@@ -1411,110 +1415,110 @@ public void showChat(ChatEntry entry) {
-private void addBubble(
- boolean outgoing,
- String displayName,
- String type,
- String content,
- java.time.LocalDateTime sentAt,
- String messageId,
- String forwardedFrom,
- String forwardedBy,
- String replyToId,
- boolean edited,
- org.json.JSONArray reactions
-) {
- // === Meta (نام + زمان) ===
- String metaText = (displayName == null ? "" : displayName) + " • " + formatWhen(sentAt);
- if (edited) metaText += " (edited)";
- Label meta = new Label(metaText);
- meta.setStyle("-fx-font-size: 11; -fx-text-fill: #7e8a97;");
- meta.setWrapText(true);
- // برچسب برای آپدیتهای بعدی (edit)
- meta.getProperties().put("role", "metaLabel");
+ private void addBubble(
+ boolean outgoing,
+ String displayName,
+ String type,
+ String content,
+ java.time.LocalDateTime sentAt,
+ String messageId,
+ String forwardedFrom,
+ String forwardedBy,
+ String replyToId,
+ boolean edited,
+ org.json.JSONArray reactions
+ ) {
+ // === Meta (نام + زمان) ===
+ String metaText = (displayName == null ? "" : displayName) + " • " + formatWhen(sentAt);
+ if (edited) metaText += " (edited)";
+ Label meta = new Label(metaText);
+ meta.setStyle("-fx-font-size: 11; -fx-text-fill: #7e8a97;");
+ meta.setWrapText(true);
+ // برچسب برای آپدیتهای بعدی (edit)
+ meta.getProperties().put("role", "metaLabel");
- // === متن/نوع پیام ===
- String t = type == null ? "" : type.trim().toUpperCase();
- boolean isText = t.isEmpty() ? (content != null && !content.isBlank()) : "TEXT".equals(t);
- String bodyText = isText ? (content == null ? "" : content) : bracketLabel(t);
+ // === متن/نوع پیام ===
+ String t = type == null ? "" : type.trim().toUpperCase();
+ boolean isText = t.isEmpty() ? (content != null && !content.isBlank()) : "TEXT".equals(t);
+ String bodyText = isText ? (content == null ? "" : content) : bracketLabel(t);
- Label msg = new Label(bodyText);
- msg.setWrapText(true);
- msg.setMinHeight(Region.USE_PREF_SIZE);
- // برچسب برای آپدیتهای بعدی (edit)
- msg.getProperties().put("role", "msgLabel");
+ Label msg = new Label(bodyText);
+ msg.setWrapText(true);
+ msg.setMinHeight(Region.USE_PREF_SIZE);
+ // برچسب برای آپدیتهای بعدی (edit)
+ msg.getProperties().put("role", "msgLabel");
- // === رنگ بابلها
- boolean dark = themeManager.isDarkMode();
- String mine = dark ? "#2b7cff" : "#d8ecff"; // outgoing (من)
- String theirs = dark ? "#2c333a" : "#f2f4f7"; // incoming (خیلی روشن بهجای سفید)
- String bg = outgoing ? mine : theirs;
+ // === رنگ بابلها
+ boolean dark = themeManager.isDarkMode();
+ String mine = dark ? "#2b7cff" : "#d8ecff"; // outgoing (من)
+ String theirs = dark ? "#2c333a" : "#f2f4f7"; // incoming (خیلی روشن بهجای سفید)
+ String bg = outgoing ? mine : theirs;
- msg.setStyle(
- "-fx-background-color:" + bg + ";" +
- "-fx-padding:8 12;" +
- "-fx-background-radius:12;" +
- "-fx-max-width: 520;"
- );
+ msg.setStyle(
+ "-fx-background-color:" + bg + ";" +
+ "-fx-padding:8 12;" +
+ "-fx-background-radius:12;" +
+ "-fx-max-width: 520;"
+ );
- // === بدنهی بابل ===
- VBox bubble = new VBox(4);
- bubble.getChildren().add(meta);
+ // === بدنهی بابل ===
+ VBox bubble = new VBox(4);
+ bubble.getChildren().add(meta);
- // برچسبگذاری بابل برای پیدا کردنش در آپدیتهای realtime
- if (messageId != null && !messageId.isBlank()) {
- 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());
+ // برچسبگذاری بابل برای پیدا کردنش در آپدیتهای realtime
+ if (messageId != null && !messageId.isBlank()) {
+ 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());
+ }
+ });
+ }
private ContextMenu buildMessageMenu(boolean isMine, String messageId, String type, String content) {
ContextMenu menu = new ContextMenu();
@@ -1973,37 +1977,30 @@ private void addBubble(
data.optString("last_seen", null)
));
- // ⭐️ بلاک؟
- boolean blocked = data.optBoolean("blocked", false)
- || 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);
- }
+ // ❌ هیچ applyMode اینجا نزن!
+ // حتی اگر blocked آمد، تصمیم مود از بیرون میآید.
}
+
private void updateGroupHeader(ChatEntry entry, JSONObject data) {
chatTitle.setText(nz(data.optString("group_name", entry.getName())));
String img = data.optString("image_url", "");
if (hasVal(img)) {
try {
- Image im = AvatarLocalResolver.load(img); // ⬅️
+ Image im = AvatarLocalResolver.load(img);
if (im != null) userAvatar.setImage(im);
userAvatar.setClip(new Circle(20, 20, 20));
} catch (Exception ignore) {}
}
-
int members = data.optInt("member_count", 0);
int online = data.optInt("online_count", -1);
chatStatus.setText(online >= 0 ? (members + " members, " + online + " online")
: (members + " members"));
+
+ // ❌ هیچ applyMode اینجا نزن!
}
// private void updateChannelHeader(ChatEntry entry, JSONObject data) {
@@ -2039,14 +2036,8 @@ private void addBubble(
int subs = data.optInt("member_count", 0);
chatStatus.setText(subs + " subscribers");
- boolean canPost = canPostToChannel(entry, data);
- if (canPost) {
- 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);
- }
+ // ❌ هیچ applyMode اینجا نزن!
+ // حتی اگر can_post را بده، به مود دست نزن.
}
@@ -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
private void onJoinClicked() {
if (currentChat == null) return;
- // 1) internal_uuid کاربر فعلی (UUID)
String myInternalUuid = Session.currentUser != null
? Session.currentUser.optString("internal_uuid", "")
: "";
@@ -2114,27 +2140,71 @@ private void addBubble(
return;
}
- // 2) internal_uuid مقصد (گروه/کانال)
- String targetId = currentChat.getId().toString();
+ final 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()
.put("action", action)
- .put("user_id", myInternalUuid) // ← UUID
- .put("id", targetId); // ← UUID گروه/کانال
+ .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"));
- }
+ // ⛔ روی ترد FX کال شبکه نزن!
+ new Thread(() -> {
+ JSONObject res = ActionHandler.sendWithResponse(req);
+ boolean ok = (res != null && "success".equalsIgnoreCase(res.optString("status")));
+
+ Platform.runLater(() -> {
+ 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) {
currentMode = mode;
@@ -2275,36 +2394,27 @@ private void addBubble(
boolean readOnly = (mode == ChatViewMode.READ_ONLY);
boolean blocked = (mode == ChatViewMode.BLOCKED);
- // Composer فقط در حالت نرمال
+ // فقط در حالت نرمال: کامپوزر
composerPane.setVisible(normal);
composerPane.setManaged(normal);
- // Join / Add
+ // پنلهای 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);
+ readOnlyPane.setVisible(readOnly);
+ 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) {
String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP";
joinButton.setText(("Join " + what).toUpperCase());
@@ -2318,25 +2428,43 @@ private void addBubble(
@FXML
private void onUnblockClicked() {
if (currentChat == null) return;
- UUID other = currentChat.getOtherUserId();
- if (other == null && currentChat.getDisplayId() == null) return;
-
- org.json.JSONObject req = new org.json.JSONObject()
- .put("action", "toggle_block")
- .put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"))
- .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.");
+ 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()
+ .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,7 +2940,63 @@ 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;
+ }
+ }
+
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/to/telegramfinalproject/UI/MainController.java b/src/main/java/org/to/telegramfinalproject/UI/MainController.java
index 723cbef..4f6ac87 100644
--- a/src/main/java/org/to/telegramfinalproject/UI/MainController.java
+++ b/src/main/java/org/to/telegramfinalproject/UI/MainController.java
@@ -153,7 +153,7 @@ public class MainController {
}
- void refreshChatListUI() {
+ public void refreshChatListUI() {
Platform.runLater(() -> {
chatListContainer.getChildren().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) {
- try {
- FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
- Node chatPage = loader.load();
+ // فقط برای پرایوت: اول بلاک/بلاکشدن را چک کن، بعد مود را تعیین کن
+ if ("private".equalsIgnoreCase(chat.getType())) {
+ 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);
-
-
-
- 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();
+ 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);
}
diff --git a/src/main/java/org/to/telegramfinalproject/UI/SettingsController.java b/src/main/java/org/to/telegramfinalproject/UI/SettingsController.java
index 03336c0..005c699 100644
--- a/src/main/java/org/to/telegramfinalproject/UI/SettingsController.java
+++ b/src/main/java/org/to/telegramfinalproject/UI/SettingsController.java
@@ -10,6 +10,9 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
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.net.URL;
@@ -48,7 +51,7 @@ public class SettingsController {
instance = this;
editProfileItem.setOnAction(e -> openEditProfile());
- logoutItem.setOnAction(e -> System.out.println("Log Out clicked"));
+ logoutItem.setOnAction(e -> onLogoutClicked());
populateFromSession();
@@ -230,4 +233,33 @@ public class SettingsController {
Circle clip = new Circle(40, 30, 38); // centerX, centerY, radius
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();
+ }
+
}
diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css
index 97d0747..bc3a46b 100644
--- a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css
+++ b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css
@@ -56,20 +56,25 @@
-fx-background-color: transparent;
}
-/* لینکاستایل دکمهها در بنر */
-.chat-footer-banner .footer-link-btn {
+
+
+.button.footer-link-btn {
-fx-background-color: transparent;
- -fx-text-fill: #1E88E5; /* آبی پیشفرض */
- -fx-font-weight: 700;
- -fx-padding: 6 12;
-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-effect: null;
+ -fx-text-fill: #1E88E5; /* پیشفرض آبی */
}
-.chat-footer-banner .footer-link-btn:hover {
+.button.footer-link-btn:hover {
-fx-underline: true;
}
-/* نسخه قرمز برای UNBLOCK */
-.chat-footer-banner .footer-link-btn.danger {
- -fx-text-fill: #D32F2F; /* قرمز */
-}
+/* نسخهی قرمز برای UNBLOCK */
+.button.footer-link-btn.danger {
+ -fx-text-fill: #D32F2F;
+}
\ No newline at end of file
diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml
index 2660490..d2f88fc 100644
--- a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml
+++ b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml
@@ -147,10 +147,14 @@
visible="false" managed="false" styleClass="chat-footer-banner">
-
+ 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;"/>
+
diff --git a/uploads/avatars/2025-09-05/0ae6648c-9b72-43db-8229-206d8134c0f3.jpg b/uploads/avatars/2025-09-05/0ae6648c-9b72-43db-8229-206d8134c0f3.jpg
new file mode 100644
index 0000000..ee48cbb
Binary files /dev/null and b/uploads/avatars/2025-09-05/0ae6648c-9b72-43db-8229-206d8134c0f3.jpg differ
diff --git a/uploads/avatars/2025-09-05/3336cf86-cb4e-47d6-85c2-44431a1ed07f.jpg b/uploads/avatars/2025-09-05/3336cf86-cb4e-47d6-85c2-44431a1ed07f.jpg
new file mode 100644
index 0000000..3a96141
Binary files /dev/null and b/uploads/avatars/2025-09-05/3336cf86-cb4e-47d6-85c2-44431a1ed07f.jpg differ
diff --git a/uploads/avatars/2025-09-05/5b195266-4c7b-4b66-8303-c469dd49b5ed.jpg b/uploads/avatars/2025-09-05/5b195266-4c7b-4b66-8303-c469dd49b5ed.jpg
new file mode 100644
index 0000000..8233662
Binary files /dev/null and b/uploads/avatars/2025-09-05/5b195266-4c7b-4b66-8303-c469dd49b5ed.jpg differ
diff --git a/uploads/avatars/2025-09-05/91103024-fa95-4ee9-aa82-7b6e50ac960f.jpg b/uploads/avatars/2025-09-05/91103024-fa95-4ee9-aa82-7b6e50ac960f.jpg
new file mode 100644
index 0000000..3a96141
Binary files /dev/null and b/uploads/avatars/2025-09-05/91103024-fa95-4ee9-aa82-7b6e50ac960f.jpg differ
diff --git a/uploads/avatars/2025-09-05/a60e9051-ca4c-4eaa-95b8-417044af8569.jpg b/uploads/avatars/2025-09-05/a60e9051-ca4c-4eaa-95b8-417044af8569.jpg
new file mode 100644
index 0000000..05084e6
Binary files /dev/null and b/uploads/avatars/2025-09-05/a60e9051-ca4c-4eaa-95b8-417044af8569.jpg differ
diff --git a/uploads/avatars/2025-09-05/ca9f35f2-8ecb-450f-8b56-b6b67c87f256.jpg b/uploads/avatars/2025-09-05/ca9f35f2-8ecb-450f-8b56-b6b67c87f256.jpg
new file mode 100644
index 0000000..103ad9a
Binary files /dev/null and b/uploads/avatars/2025-09-05/ca9f35f2-8ecb-450f-8b56-b6b67c87f256.jpg differ