From f79eeb20861ae99cf75de086e9145f077ce4fc32 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Sat, 6 Sep 2025 00:45:24 +0330 Subject: [PATCH] Archived chats --- .../telegramfinalproject/Client/Session.java | 36 ++++++ .../UI/ChatPageController.java | 77 ++++++++--- .../UI/MainController.java | 121 ++++++++++++++++-- 3 files changed, 207 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index a555949..7ef40ed 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -172,4 +172,40 @@ public class Session { return entry; } + + + public static boolean inArchivedView = false; + + public static boolean isArchived(UUID chatId) { + if (archivedChats == null) return false; + for (ChatEntry e : archivedChats) if (e.getId().equals(chatId)) return true; + return false; + } + + public static void moveToArchived(ChatEntry e) { + if (e == null) return; + if (chatList != null) chatList.removeIf(x -> x.getId().equals(e.getId())); + if (activeChats != null) activeChats.removeIf(x -> x.getId().equals(e.getId())); + if (archivedChats != null) archivedChats.removeIf(x -> x.getId().equals(e.getId())); + if (archivedChats != null) archivedChats.add(e); + } + + public static void moveToActive(ChatEntry e) { + if (e == null) return; + if (archivedChats != null) archivedChats.removeIf(x -> x.getId().equals(e.getId())); + if (activeChats != null) activeChats.removeIf(x -> x.getId().equals(e.getId())); + if (chatList != null) chatList.removeIf(x -> x.getId().equals(e.getId())); + if (activeChats != null) activeChats.add(e); + } + + // مرتب‌سازی بر اساس آخرین پیام (در صورت داشتن فیلد) + public static void sortListsByLastMessage() { + java.util.Comparator cmp = + java.util.Comparator.comparing(ChatEntry::getLastMessageTime, java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder())) + .reversed(); + if (activeChats != null) activeChats.sort(cmp); + if (archivedChats != null) archivedChats.sort(cmp); + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index f845ddd..801a559 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -294,14 +294,9 @@ public class ChatPageController { if (archiveItem != null) { archiveItem.setOnAction(e -> { - System.out.println("Toggling archive for chat " + chatName); - // TODO: backend call → toggleArchive(chatId, type) - // Also update text: "Archive chat" ↔ "Unarchive chat" - if ("Archive chat".equals(archiveItem.getText())) { - archiveItem.setText("Unarchive chat"); - } else { - archiveItem.setText("Archive chat"); - } + ChatEntry entry = Session.currentChatEntry; + if (entry == null) return; + toggleArchive(entry); }); } @@ -1161,18 +1156,63 @@ public class ChatPageController { } private void toggleArchive(ChatEntry entry) { - if (entry == null) return; + boolean currentlyArchived = Session.isArchived(entry.getId()); - // Just toggle the text for now - if ("Archive chat".equals(archiveItem.getText())) { - archiveItem.setText("Unarchive chat"); - System.out.println("Pretend: chat " + entry.getName() + " archived."); - } else { - archiveItem.setText("Archive chat"); - System.out.println("Pretend: chat " + entry.getName() + " unarchived."); - } + // Disable UI source while processing + if (archiveItem != null) archiveItem.setDisable(true); + + new Thread(() -> { + JSONObject req = new JSONObject() + .put("chat_id", entry.getId().toString()); + + if (!currentlyArchived) { + // ARCHIVE + req.put("action", "archive_chat") + .put("chat_type", entry.getType()); // سرور می‌خواهد + } else { + // UNARCHIVE + req.put("action", "unarchive_chat"); + } + + JSONObject resp = ActionHandler.sendWithResponse(req); + + Platform.runLater(() -> { + if (archiveItem != null) archiveItem.setDisable(false); + + if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) { + // 1) جابه‌جایی در Session + if (!currentlyArchived) { + Session.moveToArchived(entry); + if (archiveItem != null) archiveItem.setText("Unarchive chat"); + } else { + Session.moveToActive(entry); + if (archiveItem != null) archiveItem.setText("Archive chat"); + } + Session.sortListsByLastMessage(); + + // 2) تازه‌سازی UI لیست‌ها + try { + MainController.getInstance().refreshChatListUI(); // پیاده‌سازی در بخش 3 + } catch (Exception ignore) {} + + // 3) اگر وارد نمای آرشیو هستیم و آن‌آرشیو شد، بلافاصله از لیست آرشیو حذف شود + if (Session.inArchivedView && !Session.isArchived(entry.getId())) { + try { MainController.getInstance().refreshArchivedListUI(); } catch (Exception ignore) {} + } + + // 4) اگر تازه برای اولین بار آرشیو داریم، ردیف "Archived Chats" در بالای لیست ظاهر شود + try { MainController.getInstance().ensureArchivedHeaderRow(); } catch (Exception ignore) {} + + } else { + String msg = (resp != null ? resp.optString("message", "Unknown error") + : "No response from server."); + new Alert(Alert.AlertType.ERROR, "Failed to toggle archive: " + msg).showAndWait(); + } + }); + }).start(); } + private void loadMessages(ChatEntry entry) { JSONObject req = new JSONObject(); req.put("action", "get_messages_UI"); @@ -2184,7 +2224,6 @@ public class ChatPageController { .put("user_id", myInternalUuid) // UUID من .put("id", targetId); // UUID مقصد - // ⛔ روی ترد FX کال شبکه نزن! new Thread(() -> { JSONObject res = ActionHandler.sendWithResponse(req); boolean ok = (res != null && "success".equalsIgnoreCase(res.optString("status"))); @@ -2699,7 +2738,7 @@ public class ChatPageController { currentChat.getType().equalsIgnoreCase(target.type)) { loadMessages(currentChat); } else { - addSystemMessage("Forwarded to " + (target.name.isBlank() ? target.id : target.name)); +// addSystemMessage("Forwarded to " + (target.name.isBlank() ? target.id : target.name)); } } }); diff --git a/src/main/java/org/to/telegramfinalproject/UI/MainController.java b/src/main/java/org/to/telegramfinalproject/UI/MainController.java index 4f6ac87..5189b2c 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/MainController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/MainController.java @@ -10,11 +10,7 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.scene.shape.Circle; import javafx.util.Duration; import org.to.telegramfinalproject.Client.Session; @@ -38,6 +34,10 @@ public class MainController { @FXML private ScrollPane globalSearchScroll; @FXML private VBox globalSearchResultsContainer; + //Archives + @FXML private VBox chatListVBox; + @FXML private Label chatListHeader; + private boolean blockedByMeFlag = false; private boolean blockedMeFlag = false; @@ -153,11 +153,26 @@ public class MainController { } +// public void refreshChatListUI() { +// Platform.runLater(() -> { +// chatListContainer.getChildren().clear(); +// itemControllers.clear(); +// populateChatListFromSession(); // همون متدی که نودها رو می‌سازه و می‌چسبونه +// }); +// } + + public void refreshChatListUI() { Platform.runLater(() -> { chatListContainer.getChildren().clear(); itemControllers.clear(); - populateChatListFromSession(); // همون متدی که نودها رو می‌سازه و می‌چسبونه + + if (Session.inArchivedView) { + refreshArchivedListUI(); // ⬅️ همین ظرف chatListContainer را پر می‌کند + } else { + ensureArchivedHeaderRow(); // ⬅️ اول هدر را در chatListContainer بگذار + populateChatListFromSession(); + } }); } @@ -170,7 +185,8 @@ public class MainController { @FXML public void initialize() { // Populate chat list - populateChatListFromSession(); +// populateChatListFromSession(); + refreshChatListUI(); // Register the scene for automatic CSS updates Platform.runLater(() -> { @@ -316,7 +332,7 @@ public class MainController { } private void populateChatListFromSession() { - chatListContainer.getChildren().clear(); +// chatListContainer.getChildren().clear(); var list = (org.to.telegramfinalproject.Client.Session.activeChats != null && !org.to.telegramfinalproject.Client.Session.activeChats.isEmpty()) @@ -1234,6 +1250,95 @@ public class MainController { + private static final String ARCHIVED_ROW_KEY = "ARCHIVED_HEADER_ROW"; + + public void ensureArchivedHeaderRow() { + boolean hasArchived = Session.archivedChats != null && !Session.archivedChats.isEmpty(); + + // اگر آرشیو نداریم، هدر قبلی را حذف کن و برگرد + if (!hasArchived) { + chatListContainer.getChildren().removeIf(n -> ARCHIVED_ROW_KEY.equals(n.getUserData())); + return; + } + + // اگر قبلاً هست، دوباره نساز + boolean exists = chatListContainer.getChildren().stream() + .anyMatch(n -> ARCHIVED_ROW_KEY.equals(n.getUserData())); + if (exists) return; + + HBox headerRow = new HBox(8); + headerRow.setUserData(ARCHIVED_ROW_KEY); + headerRow.getStyleClass().add("archived-header-row"); + + Label title = new Label("Archived Chats"); + Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); + Button openBtn = new Button("Open"); + openBtn.setOnAction(e -> showArchivedListUI()); + + headerRow.getChildren().addAll(title, spacer, openBtn); + headerRow.setMinHeight(36); headerRow.setPrefHeight(36); + + // در ابتدای همان لیست اصلیِ چت‌ها + chatListContainer.getChildren().add(0, headerRow); + } + + public void showArchivedListUI() { + Session.inArchivedView = true; + refreshChatListUI(); + } + + public void refreshArchivedListUI() { + chatListContainer.getChildren().clear(); + + // Back row (بماند) + HBox backRow = new HBox(); + Label back = new Label("← Back to Active"); + back.getStyleClass().add("archived-back"); + back.setOnMouseClicked(e -> { + Session.inArchivedView = false; + refreshChatListUI(); + }); + backRow.getChildren().add(back); + backRow.setStyle("-fx-padding: 6 10 8 10;"); + chatListContainer.getChildren().add(backRow); + + // ✅ از همین سل‌ساز FXML استفاده کن تا دقیقا هم‌شکل اکتیو باشد + if (Session.archivedChats != null && !Session.archivedChats.isEmpty()) { + for (ChatEntry e : Session.archivedChats) { + addChatNode(e); // ⬅️ همونی که در اکتیو استفاده می‌کنی + } + } else { + Label empty = new Label("No archived chats"); + empty.getStyleClass().add("muted"); + chatListContainer.getChildren().add(empty); + } + } + + + + // ساخت یک سل آیتم (برای اکتیو/آرشیو) + private HBox buildChatCell(ChatEntry e, boolean archivedView) { + HBox row = new HBox(8); + ImageView avatar = new ImageView(); avatar.setFitWidth(36); avatar.setFitHeight(36); + // ... لود تصویر از e.getImageUrl() اگر داری + Label name = new Label(e.getName()); + Region spacer = new Region(); HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); + Label last = new Label(safeLastLine(e)); // آخرین پیام/زمان (اختیاری) + + row.getChildren().addAll(avatar, name, spacer, last); + row.getStyleClass().add("chat-row"); + + // کلیک روی آیتم → openChat + row.setOnMouseClicked(ev -> openChat(e)); + + return row; + } + + private String safeLastLine(ChatEntry e) { + String s = e.getLastMessagePreview(); // اگر داری + return s != null ? s : ""; + } + } \ No newline at end of file