package org.to.telegramfinalproject.UI; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Pos; 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.shape.Circle; import javafx.util.Duration; import org.to.telegramfinalproject.Client.Session; import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Client.ActionHandler; import java.io.IOException; import java.util.*; public class MainController { // === LEFT PANE === @FXML private VBox chatListContainer; // inside the scrollPane @FXML private ScrollPane scrollPane; // chat list scroll // === Global Search === @FXML private TextField searchBar; @FXML private VBox globalSearchPane; @FXML private VBox noResultsBox; @FXML private ImageView noResultIcon; @FXML private ScrollPane globalSearchScroll; @FXML private VBox globalSearchResultsContainer; private enum SearchMode { GLOBAL, CHAT } private SearchMode currentSearchMode = SearchMode.GLOBAL; private UUID currentChatId; // if in CHAT mode, which chat to search in // === Search In Chat === @FXML private VBox chatSearchPane; // search results panel @FXML private ListView chatSearchResults; // === RIGHT PANE === @FXML private VBox leftPane; @FXML private javafx.scene.layout.StackPane chatDisplayArea; @FXML private Label placeholderLabel; // === Main root === @FXML private StackPane mainRoot; @FXML private SplitPane mainSplitPane; // === Sidebar === @FXML private Pane overlay; @FXML private ImageView menuIcon; private SidebarMenuController sidebarController; //save sidebar controller // === Time formatter for messages === private static final java.time.format.DateTimeFormatter FMT_HHMM = java.time.format.DateTimeFormatter.ofPattern("HH:mm"); private static final java.time.format.DateTimeFormatter FMT_DATE_TIME = java.time.format.DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); private static final String YESTERDAY_LABEL = "Yesterday"; // === STATE === private static MainController instance; private Node sidebarRoot; private boolean isSidebarOpen = false; // Add ThemeManager handle private final ThemeManager themeManager = ThemeManager.getInstance(); public MainController() { instance = this; } public static MainController getInstance() { return instance; } private final Map itemControllers = new HashMap<>(); //For realtime handling public void onChatUpdated(UUID chatId, String chatType, LocalDateTime lastTs, boolean isIncoming, String lastPreview) { // اگر هیچ‌کجا نیست، بی‌خیال boolean exists = (Session.chatList != null && Session.chatList.stream().anyMatch(c -> chatId.equals(c.getId()))) || (Session.activeChats != null && Session.activeChats.stream().anyMatch(c -> chatId.equals(c.getId()))) || (Session.archivedChats != null && Session.archivedChats.stream().anyMatch(c -> chatId.equals(c.getId()))); if (!exists) return; boolean isOpen = Session.currentChatId != null && Session.currentChatId.equals(chatId.toString()); forEachChat(chatId, chat -> { chat.setLastMessageTime(lastTs); if (lastPreview != null) chat.setLastMessagePreview(lastPreview); if (isIncoming && !isOpen) chat.setUnreadCount(chat.getUnreadCount() + 1); // ✅ یکدست با unreadCount }); java.util.Comparator byTimeDesc = (c1, c2) -> { var t1 = c1.getLastMessageTime(); var t2 = c2.getLastMessageTime(); if (t1 == null && t2 == null) return 0; if (t1 == null) return 1; if (t2 == null) return -1; return t2.compareTo(t1); }; if (Session.chatList != null) Session.chatList.sort(byTimeDesc); if (Session.activeChats != null) Session.activeChats.sort(byTimeDesc); if (Session.archivedChats != null) Session.archivedChats.sort(byTimeDesc); refreshChatListUI(); refreshBadgesIfAny(); } // MainController private void forEachChat(UUID chatId, java.util.function.Consumer fn) { java.util.List> lists = java.util.List.of( Session.chatList != null ? Session.chatList : java.util.List.of(), Session.activeChats != null ? Session.activeChats : java.util.List.of(), Session.archivedChats != null ? Session.archivedChats : java.util.List.of() ); for (var lst : lists) { for (var c : lst) { if (chatId.equals(c.getId())) { fn.accept(c); } } } } private void refreshChatListUI() { Platform.runLater(() -> { chatListContainer.getChildren().clear(); itemControllers.clear(); populateChatListFromSession(); // همون متدی که نودها رو می‌سازه و می‌چسبونه }); } private void refreshBadgesIfAny() { // آپدیت نشان‌ها اگر لازم است } @FXML public void initialize() { // Populate chat list populateChatListFromSession(); // Register the scene for automatic CSS updates Platform.runLater(() -> { ThemeManager.getInstance().registerScene(mainRoot.getScene()); }); // Listen for theme changes to update icons & labels manually ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> { if (newVal) { updateIconsForDarkMode(); updateLabelsForDarkMode(); } else { updateIconsForLightMode(); updateLabelsForLightMode(); } }); // Smooth scroll feel for chat list scrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()); scrollPane.setPannable(true); scrollPane.setFitToWidth(true); scrollPane.setFitToHeight(false); scrollPane.getContent().setOnScroll(event -> { double deltaY = event.getDeltaY() * 0.003; // smaller = smoother scrollPane.setVvalue(scrollPane.getVvalue() - deltaY); }); // ---------- SplitPane limits so panes can't fully collapse ---------- leftPane.setMinWidth(72); // avatars-only collapse chatDisplayArea.setMinWidth(360); // chat area never fully collapses mainSplitPane.widthProperty().addListener((o, ov, nv) -> clampDivider()); if (!mainSplitPane.getDividers().isEmpty()) { mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider()); } // Search bar enter action searchBar.setOnAction(e -> { String keyword = searchBar.getText().trim(); if (keyword.isEmpty()) return; if (currentSearchMode == SearchMode.GLOBAL) { performGlobalSearch(keyword); } else if (currentSearchMode == SearchMode.CHAT && currentChatId != null) { performChatSearch(keyword, currentChatId); } }); // Typing in search bar shows global search panel searchBar.textProperty().addListener((obs, o, n) -> { if (n != null && !n.isBlank()) { if (currentSearchMode == SearchMode.GLOBAL) { showGlobalSearchPanel(); } else if (currentSearchMode == SearchMode.CHAT) { showSearchPanel(); } } }); chatSearchResults.setOnMouseClicked(e -> { int idx = chatSearchResults.getSelectionModel().getSelectedIndex(); if (idx >= 0 && idx < searchBacking.size()) { openSearchResult(searchBacking.get(idx)); } }); // Smooth scroll feel for global search results globalSearchScroll.getStylesheets().add( getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm() ); globalSearchScroll.setPannable(true); globalSearchScroll.setFitToWidth(true); globalSearchScroll.setFitToHeight(false); globalSearchScroll.getContent().setOnScroll(event -> { double deltaY = event.getDeltaY() * 0.003; // match chat list smoothness globalSearchScroll.setVvalue(globalSearchScroll.getVvalue() - deltaY); }); } // Called from ChatPageController when user clicks search button public void showSearchPanel() { scrollPane.setVisible(false); scrollPane.setManaged(false); currentSearchMode = SearchMode.CHAT; chatSearchPane.setVisible(true); chatSearchPane.setManaged(true); searchBar.requestFocus(); } @FXML public void closeSearchPanel() { chatSearchPane.setVisible(false); chatSearchPane.setManaged(false); currentSearchMode = SearchMode.GLOBAL; currentChatId = null; scrollPane.setVisible(true); scrollPane.setManaged(true); searchBar.clear(); } public void showGlobalSearchPanel() { scrollPane.setVisible(false); scrollPane.setManaged(false); chatSearchPane.setVisible(false); chatSearchPane.setManaged(false); globalSearchPane.setVisible(true); globalSearchPane.setManaged(true); } @FXML public void closeGlobalSearch() { globalSearchPane.setVisible(false); globalSearchPane.setManaged(false); scrollPane.setVisible(true); scrollPane.setManaged(true); searchBar.clear(); } // private void addSampleChats() { // addChat("Archived Chats", "Your archived chats", "10:45", 0); // addChat("Saved Messages", "Keep messages for later", "Yesterday", 0); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // addChat("Alice", "Hey, how are you?", "14:12", 3); // addChat("Bob", "Let's meet tomorrow", "12:08", 1); // // // } // ... private String formatChatTime(java.time.LocalDateTime ts) { if (ts == null) return ""; var today = java.time.LocalDate.now(); var d = ts.toLocalDate(); if (d.isEqual(today)) { //today return FMT_HHMM.format(ts); //yesterday } else if (d.isEqual(today.minusDays(1))) { return YESTERDAY_LABEL; } else { return FMT_DATE_TIME.format(ts); } } private void populateChatListFromSession() { chatListContainer.getChildren().clear(); var list = (org.to.telegramfinalproject.Client.Session.activeChats != null && !org.to.telegramfinalproject.Client.Session.activeChats.isEmpty()) ? org.to.telegramfinalproject.Client.Session.activeChats : org.to.telegramfinalproject.Client.Session.chatList; if (list == null || list.isEmpty()) return; ChatEntry saved = null; java.util.List others = new java.util.ArrayList<>(); for (ChatEntry c : list) { if (saved == null && isSavedMessages(c)) { saved = c; } else { others.add(c); } } // اگر “Archived Chats” یا هدر دیگری داری، قبلش اضافه کن (اختیاری) // addArchivedHeaderIfYouHaveOne(); // 1) همیشه Saved اول بیاد (اگر وجود داشت) if (saved != null) { addChatNode(saved); } for (ChatEntry c : others) { addChatNode(c); } } // private void addChatNode(ChatEntry chat ) { // try { // FXMLLoader fx = new FXMLLoader(getClass().getResource( // "/org/to/telegramfinalproject/Fxml/chat_item.fxml")); // Node item = fx.load(); // ChatItemController cc = fx.getController(); // // String lastPreview = ""; // String time = (chat.getLastMessageTime() != null) // ? timeFmt.format(chat.getLastMessageTime()) : ""; // // cc.setChatData(chat.getName(), lastPreview, time, 0); // // item.setOnMouseClicked(e -> openChat(String.valueOf(chat))); // chatListContainer.getChildren().add(item); // } catch (Exception e) { // e.printStackTrace(); // } // } // private void addChatNode(ChatEntry chat) { // try { // FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml")); // Node item = fx.load(); // ChatItemController cc = fx.getController(); // // String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview(); // String timeText = formatChatTime(chat.getLastMessageTime()); // FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml")); // Node chatItem = loader.load(); // ChatItemController controller = loader.getController(); // // cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), "/org/to/telegramfinalproject/Avatars/default_profile.png"); // item.setOnMouseClicked(e -> openChat(chat)); // chatListContainer.getChildren().add(item); // // itemControllers.put(chat.getId(), cc); // // } catch (Exception ex) { // ex.printStackTrace(); // } // } private void addChatNode(ChatEntry chat) { try { FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml")); Node item = fx.load(); ChatItemController cc = fx.getController(); String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview(); String timeText = chat.getLastMessageTime() == null ? "" : formatChatTime(chat.getLastMessageTime()); // If chat has a profile picture, pass it; otherwise null String imageUrl = (chat.getImageUrl() != null && !chat.getImageUrl().isEmpty()) ? chat.getImageUrl() : null; cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), imageUrl, chat.getType()); item.setOnMouseClicked(e -> openChat(chat)); chatListContainer.getChildren().add(item); itemControllers.put(chat.getId(), cc); } catch (Exception ex) { ex.printStackTrace(); } } private String mapTypeToLabel(String t) { switch (t.toUpperCase()) { case "IMAGE": return "[Image]"; case "AUDIO": return "[Audio]"; case "VIDEO": return "[Video]"; case "FILE": return "[File]"; default: return "[Message]"; } } // private void addChat(String name, String lastMsg, String time, int unread) { // try { // FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml")); // Node chatItem = loader.load(); // ChatItemController controller = loader.getController(); // controller.setChatData(name, lastMsg, time, unread); // // chatItem.setOnMouseClicked(e -> openChat(name)); // chatListContainer.getChildren().add(chatItem); // } catch (IOException e) { // e.printStackTrace(); // } // } private 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(); } } @FXML private void toggleSidebar() { if (isSidebarOpen) { closeSidebar(); } else { openSidebar(); } } // @FXML // private void openSidebar() { // try { // if (sidebarRoot == null) { // sidebarRoot = FXMLLoader.load(getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml")); // StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT); // sidebarRoot.setTranslateX(-getSidebarWidth()); // mainRoot.getChildren().add(sidebarRoot); // } // // TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot); // slideIn.setToX(0); // slideIn.play(); // // overlay.setVisible(true); // overlay.setMouseTransparent(false); // // isSidebarOpen = true; // // } catch (IOException e) { // e.printStackTrace(); // } // } @FXML private void openSidebar() { try { if (sidebarRoot == null) { FXMLLoader loader = new FXMLLoader( getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml")); sidebarRoot = loader.load(); sidebarController = loader.getController(); StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT); sidebarRoot.setTranslateX(-getSidebarWidth()); mainRoot.getChildren().add(sidebarRoot); } if (sidebarController != null && org.to.telegramfinalproject.Client.Session.currentUser != null) { sidebarController.setUserFromSession( org.to.telegramfinalproject.Client.Session.currentUser); } TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot); slideIn.setToX(0); slideIn.play(); overlay.setVisible(true); overlay.setMouseTransparent(false); isSidebarOpen = true; } catch (IOException e) { e.printStackTrace(); } } @FXML private void closeSidebar() { if (sidebarRoot != null && isSidebarOpen) { TranslateTransition slideOut = new TranslateTransition(Duration.millis(250), sidebarRoot); slideOut.setToX(-getSidebarWidth()); slideOut.setOnFinished(e -> { overlay.setVisible(false); overlay.setMouseTransparent(true); isSidebarOpen = false; }); slideOut.play(); } } private double getSidebarWidth() { return 250; // Sidebar width in px } private void clampDivider() { if (mainSplitPane.getDividers().isEmpty()) return; double totalW = mainSplitPane.getWidth(); if (totalW <= 0) return; double leftMinRatio = leftPane.getMinWidth() / totalW; double rightMinRatio = chatDisplayArea.getMinWidth() / totalW; double maxLeftRatio = 1.0 - rightMinRatio; SplitPane.Divider d = mainSplitPane.getDividers().get(0); double p = d.getPosition(); if (p < leftMinRatio) { d.setPosition(leftMinRatio); } else if (p > maxLeftRatio) { d.setPosition(maxLeftRatio); } } private void updateIconsForDarkMode() { // Example: switch images to white versions menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_light.png"))); noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_light.png"))); } private void updateIconsForLightMode() { // Example: switch images to black versions menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_dark.png"))); noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_dark.png"))); } private void updateLabelsForDarkMode() { //myLabel.setStyle("-fx-text-fill: white;"); } private void updateLabelsForLightMode() { //myLabel.setStyle("-fx-text-fill: black;"); } // ===== Search UI state ===== private final java.util.List searchBacking = new java.util.ArrayList<>(); @FXML private AnchorPane chatHost; // کانتینر مناسب در مین برای قرار دادن ChatPage private ChatPageController chatPageController; public ChatPageController getChatPageController() { return chatPageController; } private enum SRType { USER, GROUP, CHANNEL, MESSAGE } private static class SearchResult { final SRType type; final String title; // name / sender_name / ... final String subtitle; // @"id" / context string / time final String receiverType; // برای MESSAGE/GROUP/CHANNEL: private|group|channel, برای USER: null final java.util.UUID uuid; // uuid آن موجودیت (user/group/channel/chat holder) یا receiver_id پیام final String displayId; // id قابل نمایش (مثل username یا group_id/channel_id) final String messageId; // فقط برای MESSAGE final String time; // نمایش SearchResult(SRType t, String title, String subtitle, String receiverType, java.util.UUID uuid, String displayId, String messageId, String time) { this.type = t; this.title = title; this.subtitle = subtitle; this.receiverType = receiverType; this.uuid = uuid; this.displayId = displayId; this.messageId = messageId; this.time = time; } String toDisplay() { switch (type) { case MESSAGE: String left = (title == null || title.isBlank()) ? "Message" : title; String right = (time == null ? "" : (" • " + time)); return "🗨 " + left + right + (subtitle==null?"":(" — " + subtitle)); case USER: return "👤 " + title + (subtitle==null?"":(" — " + subtitle)); case GROUP: return "👥 " + title + (subtitle==null?"":(" — " + subtitle)); case CHANNEL: return "📣 " + title + (subtitle==null?"":(" — " + subtitle)); } return title; } } private void performChatSearch(String keyword, UUID chatId) { if (keyword == null || keyword.isBlank()) { chatSearchResults.getItems().clear(); return; } showSearchPanel(); org.json.JSONObject req = new org.json.JSONObject(); req.put("action", "search_in_chat"); req.put("keyword", keyword); req.put("chat_id", chatId.toString()); req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id")); new Thread(() -> { org.json.JSONObject resp; try { resp = ActionHandler.sendWithResponse(req); } catch (Exception ex) { ex.printStackTrace(); return; } if (resp == null || !"success".equals(resp.optString("status"))) return; org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results"); if (arr == null) arr = new org.json.JSONArray(); java.util.List tmp = new java.util.ArrayList<>(); for (int i = 0; i < arr.length(); i++) { org.json.JSONObject it = arr.getJSONObject(i); String time = it.optString("time", ""); String content = it.optString("content", "[No content]"); tmp.add("🗨 " + content + (time.isEmpty() ? "" : " • " + time)); } Platform.runLater(() -> { chatSearchResults.getItems().setAll(tmp); chatSearchResults.setVisible(true); chatSearchResults.setManaged(true); }); }).start(); } public void performGlobalSearch(String keyword) { if (keyword == null || keyword.isBlank()) { chatSearchResults.getItems().clear(); searchBacking.clear(); return; } if (org.to.telegramfinalproject.Client.Session.currentUser == null || !org.to.telegramfinalproject.Client.Session.currentUser.has("user_id")) { System.out.println("You must be logged in to search."); return; } showGlobalSearchPanel(); // Request to server org.json.JSONObject req = new org.json.JSONObject(); req.put("action", "search"); req.put("keyword", keyword); req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id")); new Thread(() -> { org.json.JSONObject resp; try { resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req); } catch (Exception ex) { ex.printStackTrace(); return; } if (resp == null || !"success".equals(resp.optString("status"))) return; org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results"); if (arr == null) arr = new org.json.JSONArray(); final java.util.List tmp = new java.util.ArrayList<>(); for (int i = 0; i < arr.length(); i++) { org.json.JSONObject it = arr.getJSONObject(i); String type = it.optString("type",""); switch (type) { case "user": { java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid")); String name = it.optString("name","Unknown"); String id = it.optString("id", ""); tmp.add(new SearchResult( SRType.USER, name, id.isBlank()? null : "@"+id, null, uuid, id, null, null )); break; } case "group": { java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid")); String name = it.optString("name","Unknown group"); String id = it.optString("id", ""); tmp.add(new SearchResult( SRType.GROUP, name, id.isBlank()? null : id, "group", uuid, id, null, null )); break; } case "channel": { java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid")); String name = it.optString("name","Unknown channel"); String id = it.optString("id", ""); tmp.add(new SearchResult( SRType.CHANNEL, name, id.isBlank()? null : id, "channel", uuid, id, null, null )); break; } case "message": { String senderName = it.optString("sender_name", it.optString("sender","Unknown")); String time = it.optString("time", ""); String content = it.optString("content","[No content]"); String rType = it.optString("receiver_type",""); // private|group|channel java.util.UUID rUuid = java.util.UUID.fromString(it.getString("receiver_id")); String ctx = ""; if ("group".equals(rType)) ctx = it.optString("group_name",""); if ("channel".equals(rType)) ctx = it.optString("channel_name",""); String subtitle = (ctx.isBlank()? "" : ctx) + (content.isBlank()? "" : (subtitleSep(ctx)+content)); tmp.add(new SearchResult( SRType.MESSAGE, senderName, subtitle, rType, rUuid, it.optString("receiver_display_id", ""), it.optString("message_id", null), time )); break; } } } Platform.runLater(() -> renderSearchResults(tmp)); }).start(); } private String subtitleSep(String s){ return s==null || s.isBlank()? "" : " — "; } // private void renderSearchResults(java.util.List results) { // searchBacking.clear(); // searchBacking.addAll(results); // // javafx.collections.ObservableList view = // javafx.collections.FXCollections.observableArrayList(results); // globalSearchResults.setItems(view); // globalSearchResults.setVisible(true); // globalSearchResults.setManaged(true); // } private void renderSearchResults(List results) { globalSearchResultsContainer.getChildren().clear(); if (results.isEmpty()) { noResultsBox.setVisible(true); noResultsBox.setManaged(true); globalSearchScroll.setVisible(false); globalSearchScroll.setManaged(false); return; } noResultsBox.setVisible(false); noResultsBox.setManaged(false); globalSearchScroll.setVisible(true); globalSearchScroll.setManaged(true); for (SearchResult r : results) { HBox container = new HBox(10); ImageView avatar = new ImageView(); avatar.setFitWidth(40); avatar.setFitHeight(40); avatar.setSmooth(true); avatar.setPreserveRatio(true); // 🔑 Clip it into a circle Circle clip = new Circle(20, 20, 20); // x,y = center, radius = 20 avatar.setClip(clip); Label title = new Label(r.title); title.getStyleClass().add("global-search-title"); Label subtitle = new Label( r.type == SRType.MESSAGE ? (r.subtitle != null ? r.subtitle : "") : "Press to see messages" ); subtitle.getStyleClass().add("global-search-subtitle"); VBox texts = new VBox(2, title, subtitle); container.getChildren().addAll(avatar, texts); // Default profile per type switch (r.type) { case USER -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream( "/org/to/telegramfinalproject/Avatars/default_user_profile.png")))); case GROUP -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream( "/org/to/telegramfinalproject/Avatars/default_group_profile.png")))); case CHANNEL -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream( "/org/to/telegramfinalproject/Avatars/default_channel_profile.png")))); case MESSAGE -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream( "/org/to/telegramfinalproject/Avatars/default_user_profile.png")))); } container.setOnMouseClicked(e -> openSearchResult(r)); globalSearchResultsContainer.getChildren().add(container); } } private void openSearchResult(SearchResult r) { switch (r.type) { case USER: { java.util.UUID chatId = findExistingPrivateChatId(r.uuid); if (chatId == null) { chatId = fetchOrCreatePrivateChat(r.uuid); if (chatId == null) { System.out.println("❌ Failed to create/find private chat."); return; } } org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry(); ce.setId(chatId.toString()); // ⬅️ internal chat_id ce.setDisplayId(r.displayId); // username ce.setName(r.title); // profile_name ce.setType("private"); openChat(ce); break; } case GROUP: case CHANNEL: { org.to.telegramfinalproject.Models.ChatEntry existing = findExistingChat(r.uuid, r.receiverType); if (existing != null) { openChat(existing); } else { org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry(); ce.setId(r.uuid.toString()); // internal_uuid group/channel ce.setDisplayId(r.displayId); // group_id/channel_id ce.setName(r.title); ce.setType(r.receiverType); openChat(ce); } break; } case MESSAGE: { org.to.telegramfinalproject.Models.ChatEntry existing = findExistingChat(r.uuid, r.receiverType); if (existing != null) { openChat(existing); } else { org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry(); ce.setId(r.uuid.toString()); // receiver internal_uuid ce.setType(r.receiverType); ce.setName(guessNameForReceiver(r)); ce.setDisplayId(r.displayId); openChat(ce); } break; } } } private org.to.telegramfinalproject.Models.ChatEntry findExistingChat(java.util.UUID internalId, String type) { var list = (org.to.telegramfinalproject.Client.Session.chatList==null) ? java.util.Collections.emptyList() : org.to.telegramfinalproject.Client.Session.chatList; for (var c : list) { if (internalId.toString().equals(c.getId().toString()) && type.equalsIgnoreCase(c.getType())) { return c; } } return null; } private java.util.UUID findExistingPrivateChatId(java.util.UUID otherUserUuid) { var list = (org.to.telegramfinalproject.Client.Session.chatList==null) ? java.util.Collections.emptyList() : org.to.telegramfinalproject.Client.Session.chatList; for (var c : list) { if ("private".equalsIgnoreCase(c.getType())) { if (otherUserUuid.equals(c.getOtherUserId())) { try { return java.util.UUID.fromString(c.getId().toString()); } catch (Exception ignored) {} } } } return null; } private java.util.UUID fetchOrCreatePrivateChat(java.util.UUID otherUserUuid) { try { org.json.JSONObject req = new org.json.JSONObject(); req.put("action", "get_or_create_private_chat"); req.put("user1", org.to.telegramfinalproject.Client.Session.currentUser.getString("internal_uuid")); req.put("user2", otherUserUuid.toString()); org.json.JSONObject resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req); if (resp != null && "success".equals(resp.optString("status"))) { String chatId = resp.getJSONObject("data").getString("chat_id"); return java.util.UUID.fromString(chatId); } } catch (Exception e) { e.printStackTrace(); } return null; } private String guessNameForReceiver(SearchResult r) { if (r.title != null && !r.title.isBlank()) return r.title; if (r.receiverType != null) { switch (r.receiverType) { case "group": return "Group"; case "channel": return "Channel"; case "private": return "Private Chat"; } } return "Chat"; } private boolean isSavedMessages(ChatEntry c) { if (c == null) return false; // اگر تایپ اختصاصی داری if ("saved".equalsIgnoreCase(c.getType())) return true; // اگر با نام مشخص ذخیره می‌کنی String n = c.getName(); if (n != null && n.equalsIgnoreCase("Saved Messages")) return true; // حالت پرایوت با خودِ کاربر String me = (org.to.telegramfinalproject.Client.Session.currentUser != null) ? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "") : ""; try { UUID other = c.getOtherUserId(); // اگر این فیلد را داری if ("private".equalsIgnoreCase(c.getType()) && other != null && other.toString().equalsIgnoreCase(me)) { return true; } } catch (Exception ignore) {} return false; } }