Global search UI implemented.

This commit is contained in:
Asal Lotfi
2025-08-30 17:01:58 +03:30
parent 60338fec52
commit fe70aa6bd7
12 changed files with 446 additions and 65 deletions
@@ -34,7 +34,7 @@ public class ChatItemController {
* @param unread Unread message count * @param unread Unread message count
* @param imageUrl Path/URL of profile image (can be null/empty) * @param imageUrl Path/URL of profile image (can be null/empty)
*/ */
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl) { public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl, String chatType) {
chatName.setText(name); chatName.setText(name);
lastMessage.setText(lastMsg); lastMessage.setText(lastMsg);
chatTime.setText(time); chatTime.setText(time);
@@ -77,9 +77,17 @@ public class ChatItemController {
profileImageUser.setManaged(true); profileImageUser.setManaged(true);
} else { } else {
String path;
if ("group".equalsIgnoreCase(chatType)) {
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
} else if ("channel".equalsIgnoreCase(chatType)) {
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
} else {
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
}
profileImageUser.setImage(new Image( profileImageUser.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream( Objects.requireNonNull(getClass().getResourceAsStream(path))
"/org/to/telegramfinalproject/Icons/default_profile.png"))
)); ));
profileImageUser.setVisible(true); profileImageUser.setVisible(true);
profileImageUser.setManaged(true); profileImageUser.setManaged(true);
@@ -588,12 +588,10 @@ public class ChatPageController {
return (id==null||id.isEmpty()) ? "Unknown" : id.substring(0, Math.min(8,id.length())); return (id==null||id.isEmpty()) ? "Unknown" : id.substring(0, Math.min(8,id.length()));
} }
private void markAsRead(ChatEntry entry) { private void markAsRead(ChatEntry entry) {
JSONObject readReq = new JSONObject(); JSONObject readReq = new JSONObject();
readReq.put("action", "mark_as_read"); readReq.put("action", "mark_as_read");
readReq.put("receiver_id", entry.getId().toString()); // ⛳️ internal_id readReq.put("receiver_id", entry.getId().toString()); // internal_id
readReq.put("receiver_type", entry.getType()); readReq.put("receiver_type", entry.getType());
ActionHandler.sendWithResponse(readReq); ActionHandler.sendWithResponse(readReq);
} }
@@ -632,10 +630,6 @@ public class ChatPageController {
// messageContainer.getChildren().add(row); // messageContainer.getChildren().add(row);
// } // }
private void addBubble( private void addBubble(
boolean outgoing, boolean outgoing,
String displayName, String displayName,
@@ -801,7 +795,4 @@ public class ChatPageController {
private static boolean notBlank(String s) { return s != null && !s.isBlank(); } private static boolean notBlank(String s) { return s != null && !s.isBlank(); }
private static String ellipsize(String s, int max) { return s.length() > max ? s.substring(0, max) + "" : s; } private static String ellipsize(String s, int max) { return s.length() > max ? s.substring(0, max) + "" : s; }
} }
@@ -9,6 +9,7 @@ import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@@ -16,25 +17,30 @@ import javafx.util.Duration;
import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ChatEntry;
import org.to.telegramfinalproject.Client.ActionHandler; import org.to.telegramfinalproject.Client.ActionHandler;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate; import java.util.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MainController { public class MainController {
// === LEFT PANE === // === LEFT PANE ===
@FXML private VBox chatListContainer; // inside the scrollPane @FXML private VBox chatListContainer; // inside the scrollPane
@FXML private ScrollPane scrollPane; // chat list scroll @FXML private ScrollPane scrollPane; // chat list scroll
// === Global Search ===
@FXML private TextField searchBar;
@FXML private VBox globalSearchPane;
@FXML private ListView<SearchResult> globalSearchResults;
@FXML private VBox noResultsBox;
@FXML private ImageView noResultIcon;
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 VBox chatSearchPane; // search results panel
@FXML private ListView<String> chatSearchResults; @FXML private ListView<String> chatSearchResults;
@FXML private MenuButton scopeDropdown;
// === TOP BAR ===
@FXML private TextField searchBar; // global search bar
@FXML private Button menuButton;
// === RIGHT PANE === // === RIGHT PANE ===
@FXML private VBox leftPane; @FXML private VBox leftPane;
@@ -137,15 +143,104 @@ public class MainController {
// } // }
// }); // });
// وقتی کاربر Enter زد روی سرچ‌بار، درخواست سرچ بفرست searchBar.setOnAction(e -> {
searchBar.setOnAction(e -> performGlobalSearch(searchBar.getText().trim())); String keyword = searchBar.getText().trim();
if (keyword.isEmpty()) return;
// باز/بسته کردن پنل نتایج با تایپ (دلخواه) if (currentSearchMode == SearchMode.GLOBAL) {
searchBar.textProperty().addListener((obs,o,n)->{ performGlobalSearch(keyword);
if (n!=null && !n.isBlank()) showSearchPanel(); } else if (currentSearchMode == SearchMode.CHAT && currentChatId != null) {
performChatSearch(keyword, currentChatId);
}
}); });
// کلیک روی نتیجه searchBar.setOnAction(e -> performGlobalSearch(searchBar.getText().trim()));
searchBar.textProperty().addListener((obs,o,n)->{
if (n!=null && !n.isBlank()) showGlobalSearchPanel();
});
globalSearchResults.setOnMouseClicked(e -> {
int idx = globalSearchResults.getSelectionModel().getSelectedIndex();
if (idx >= 0 && idx < searchBacking.size()) {
openSearchResult(searchBacking.get(idx));
}
});
globalSearchResults.setCellFactory(list -> new ListCell<>() {
private final HBox container = new HBox(10);
private final ImageView avatar = new ImageView();
private final VBox texts = new VBox(2);
private final Label title = new Label();
private final Label subtitle = new Label();
{
avatar.setFitWidth(36);
avatar.setFitHeight(36);
avatar.getStyleClass().add("global-search-avatar");
title.getStyleClass().add("global-search-title");
subtitle.getStyleClass().add("global-search-subtitle");
texts.getChildren().addAll(title, subtitle);
container.getChildren().addAll(avatar, texts);
}
@Override
protected void updateItem(SearchResult r, boolean empty) {
super.updateItem(r, empty);
if (empty || r == null) {
setGraphic(null);
} else {
title.setText(r.title);
if (r.type == SRType.MESSAGE) {
subtitle.setText(r.subtitle != null ? r.subtitle : "");
} else {
subtitle.setText("Press to see messages");
}
// Default profile images depending on type
switch (r.type) {
case USER:
avatar.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
))
));
break;
case GROUP:
avatar.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Avatars/default_group_profile.png"
))
));
break;
case CHANNEL:
avatar.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Avatars/default_channel_profile.png"
))
));
break;
case MESSAGE:
// For messages, use sender default (user style)
avatar.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
))
));
break;
}
avatar.setFitWidth(50);
avatar.setFitHeight(50);
avatar.setPreserveRatio(true);
avatar.setSmooth(true);
setGraphic(container);
}
}
});
// CLick on the search result
chatSearchResults.setOnMouseClicked(e -> { chatSearchResults.setOnMouseClicked(e -> {
int idx = chatSearchResults.getSelectionModel().getSelectedIndex(); int idx = chatSearchResults.getSelectionModel().getSelectedIndex();
if (idx >= 0 && idx < searchBacking.size()) { if (idx >= 0 && idx < searchBacking.size()) {
@@ -166,11 +261,25 @@ public class MainController {
searchBar.requestFocus(); searchBar.requestFocus();
} }
public void showGlobalSearchPanel() {
scrollPane.setVisible(false);
scrollPane.setManaged(false);
chatSearchPane.setVisible(false);
chatSearchPane.setManaged(false);
globalSearchPane.setVisible(true);
globalSearchPane.setManaged(true);
}
@FXML @FXML
public void closeSearchPanel() { public void closeSearchPanel() {
chatSearchPane.setVisible(false); chatSearchPane.setVisible(false);
chatSearchPane.setManaged(false); chatSearchPane.setManaged(false);
currentSearchMode = SearchMode.GLOBAL;
currentChatId = null;
scrollPane.setVisible(true); scrollPane.setVisible(true);
scrollPane.setManaged(true); scrollPane.setManaged(true);
} }
@@ -282,7 +391,7 @@ public class MainController {
Node chatItem = loader.load(); Node chatItem = loader.load();
ChatItemController controller = loader.getController(); ChatItemController controller = loader.getController();
cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), "/org/to/telegramfinalproject/Avatars/default_profile.png"); cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), "/org/to/telegramfinalproject/Avatars/default_user_profile.png", chat.getType());
item.setOnMouseClicked(e -> openChat(chat)); item.setOnMouseClicked(e -> openChat(chat));
chatListContainer.getChildren().add(item); chatListContainer.getChildren().add(item);
@@ -325,6 +434,9 @@ public class MainController {
ChatPageController controller = loader.getController(); ChatPageController controller = loader.getController();
controller.showChat(chat); controller.showChat(chat);
currentSearchMode = SearchMode.CHAT;
currentChatId = UUID.fromString(chat.getId().toString());
chatDisplayArea.getChildren().setAll(chatPage); chatDisplayArea.getChildren().setAll(chatPage);
ChatItemController item = itemControllers.get(chat.getId()); ChatItemController item = itemControllers.get(chat.getId());
@@ -441,11 +553,13 @@ public class MainController {
private void updateIconsForDarkMode() { private void updateIconsForDarkMode() {
// Example: switch images to white versions // Example: switch images to white versions
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_light.png"))); 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() { private void updateIconsForLightMode() {
// Example: switch images to black versions // Example: switch images to black versions
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_dark.png"))); 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() { private void updateLabelsForDarkMode() {
@@ -497,6 +611,49 @@ public class MainController {
} }
} }
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<String> 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) { public void performGlobalSearch(String keyword) {
if (keyword == null || keyword.isBlank()) { if (keyword == null || keyword.isBlank()) {
@@ -510,7 +667,7 @@ public class MainController {
return; return;
} }
showSearchPanel(); showGlobalSearchPanel();
// درخواست به سرور (مثل کنسول) // درخواست به سرور (مثل کنسول)
org.json.JSONObject req = new org.json.JSONObject(); org.json.JSONObject req = new org.json.JSONObject();
@@ -597,17 +754,46 @@ public class MainController {
private String subtitleSep(String s){ return s==null || s.isBlank()? "" : ""; } private String subtitleSep(String s){ return s==null || s.isBlank()? "" : ""; }
private void renderSearchResults(java.util.List<SearchResult> results) { // private void renderSearchResults(java.util.List<SearchResult> results) {
searchBacking.clear(); // searchBacking.clear();
searchBacking.addAll(results); // searchBacking.addAll(results);
//
// javafx.collections.ObservableList<SearchResult> view =
// javafx.collections.FXCollections.observableArrayList(results);
// globalSearchResults.setItems(view);
// globalSearchResults.setVisible(true);
// globalSearchResults.setManaged(true);
// }
javafx.collections.ObservableList<String> view = javafx.collections.FXCollections.observableArrayList(); private void renderSearchResults(List<SearchResult> results) {
for (SearchResult r : results) view.add(r.toDisplay()); globalSearchResults.getItems().clear();
chatSearchResults.setItems(view);
chatSearchResults.setVisible(true); if (results.isEmpty()) {
chatSearchResults.setManaged(true); noResultsBox.setVisible(true);
noResultsBox.setManaged(true);
globalSearchResults.setVisible(false);
globalSearchResults.setManaged(false);
return;
} }
noResultsBox.setVisible(false);
noResultsBox.setManaged(false);
globalSearchResults.setVisible(true);
globalSearchResults.setManaged(true);
globalSearchResults.getItems().addAll(results);
}
@FXML
public void closeGlobalSearch() {
globalSearchPane.setVisible(false);
globalSearchPane.setManaged(false);
scrollPane.setVisible(true);
scrollPane.setManaged(true);
searchBar.clear();
}
private void openSearchResult(SearchResult r) { private void openSearchResult(SearchResult r) {
switch (r.type) { switch (r.type) {
@@ -754,8 +940,4 @@ public class MainController {
return false; return false;
} }
} }
@@ -3,7 +3,6 @@ package org.to.telegramfinalproject.UI;
import javafx.animation.TranslateTransition; import javafx.animation.TranslateTransition;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.Image; import javafx.scene.image.Image;
@@ -48,7 +47,7 @@ public class SidebarMenuController {
@FXML @FXML
public void initialize() { public void initialize() {
// Load default profile image // Load default profile image
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_profile.png"); Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
if (profile != null) profileImage.setImage(profile); if (profile != null) profileImage.setImage(profile);
setupButtonActions(); setupButtonActions();
@@ -197,7 +196,7 @@ public class SidebarMenuController {
String img = user.optString("image_url", ""); String img = user.optString("image_url", "");
Image pic = tryLoadImage(img); Image pic = tryLoadImage(img);
if (pic == null) { if (pic == null) {
pic = loadImage("/org/to/telegramfinalproject/Avatars/default_profile.png"); pic = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
} }
if (pic != null) profileImage.setImage(pic); if (pic != null) profileImage.setImage(pic);
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,3 +1,9 @@
/* ===== Dark Theme Root ===== */
.root {
-fx-background-color: #0f1c26; /* overall dark base */
-fx-text-fill: #e8f1f8;
}
/* Sidebar buttons */ /* Sidebar buttons */
.sidebar-btn { .sidebar-btn {
-fx-background-color: transparent; -fx-background-color: transparent;
@@ -170,7 +176,7 @@
.search-placeholder { -fx-text-fill: #8ea1b2; -fx-font-size: 13px; } .search-placeholder { -fx-text-fill: #8ea1b2; -fx-font-size: 13px; }
/* ===== Global search bar ===== */ /* ===== Global search bar ===== */
.global-search { .global-search-pane {
-fx-background-color: #1b2b39; -fx-background-color: #1b2b39;
-fx-background-radius: 15; -fx-background-radius: 15;
-fx-text-fill: #e8f1f8; -fx-text-fill: #e8f1f8;
@@ -294,3 +300,82 @@
-fx-background-radius: 10; -fx-background-radius: 10;
-fx-font-weight: bold; -fx-font-weight: bold;
} }
/* === Global Search List (Dark) === */
.global-search-list {
-fx-background-color: #1b2735; /* uniform dark background */
-fx-control-inner-background: #1b2735;
-fx-control-inner-background-alt: #1b2735; /* disable stripes */
}
.global-search-list .virtual-flow,
.global-search-list .clipped-container,
.global-search-list .sheet {
-fx-background-color: #1b2735; /* internal containers same */
}
.global-search-list .list-cell {
-fx-padding: 8 10;
-fx-background-color: #1b2735; /* solid dark rows */
-fx-border-color: #2e3948; /* subtle divider */
-fx-border-width: 0 0 0.5 0;
-fx-alignment: CENTER_LEFT;
-fx-text-fill: #e8f1f8; /* light text */
}
.global-search-list .list-cell:hover {
-fx-background-color: rgba(255, 255, 255, 0.08); /* lighter on hover */
}
.global-search-list .list-cell:selected {
-fx-background-color: #2a3a4a; /* darker blue-gray on select */
}
/* Titles & subtitles */
.global-search-title {
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-text-fill: #e8f1f8;
}
.global-search-subtitle {
-fx-font-size: 12px;
-fx-text-fill: #8ea1b2; /* muted gray-blue */
}
/* Search header */
.search-header {
-fx-padding: 8 12;
-fx-background-color: #1b2735;
-fx-border-color: #2e3948;
-fx-border-width: 0 0 1 0;
}
.search-header-label {
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-text-fill: #e8f1f8;
}
/* No results */
.no-results-label {
-fx-font-size: 12px;
-fx-text-fill: #8ea1b2;
}
/* Avatar placeholders */
.global-search-avatar {
-fx-background-radius: 50%;
-fx-background-color: #444;
-fx-min-width: 36;
-fx-min-height: 36;
}
/* Close button */
.search-close-btn {
-fx-background-color: transparent;
-fx-text-fill: #e8f1f8;
-fx-font-size: 14px;
-fx-cursor: hand;
}
.search-close-btn:hover {
-fx-text-fill: #2ca4ff;
}
@@ -1,3 +1,8 @@
.root {
-fx-base: #ffffff;
-fx-text-base-color: #000000;
}
/* Sidebar buttons */ /* Sidebar buttons */
.sidebar-btn { .sidebar-btn {
-fx-background-color: transparent; -fx-background-color: transparent;
@@ -195,7 +200,7 @@
-fx-cursor: hand; -fx-cursor: hand;
} }
.global-search { .global-search-pane {
-fx-background-color: #f5f5f5; -fx-background-color: #f5f5f5;
-fx-background-radius: 15; -fx-background-radius: 15;
-fx-text-fill: #0f141a; -fx-text-fill: #0f141a;
@@ -293,3 +298,104 @@
-fx-background-radius: 10; -fx-background-radius: 10;
-fx-font-weight: bold; -fx-font-weight: bold;
} }
/* === Global Search List === */
.global-search-list .list-cell {
-fx-padding: 8 10;
-fx-background-color: transparent;
-fx-border-color: #ddd; /* light divider */
-fx-border-width: 0 0 0.5 0;
-fx-alignment: CENTER_LEFT;
}
.global-search-list .list-cell:hover {
-fx-background-color: rgba(0, 0, 0, 0.05); /* light hover */
}
.global-search-title {
-fx-font-size: 14px;
-fx-font-weight: bold;
-fx-text-fill: black;
}
.global-search-subtitle {
-fx-font-size: 12px;
-fx-text-fill: #666; /* gray for light mode */
}
/* Avatar placeholder (if no image) */
.global-search-avatar {
-fx-background-radius: 50%;
-fx-background-color: #bbb;
-fx-min-width: 36;
-fx-min-height: 36;
}
/* Global search panel */
.global-search-pane {
-fx-background-color: -fx-base;
}
.search-header {
-fx-padding: 8 12;
-fx-background-color: transparent;
-fx-border-color: #ddd;
-fx-border-width: 0 0 1 0;
}
.search-header-label {
-fx-font-size: 13px;
-fx-font-weight: bold;
-fx-text-fill: -fx-text-base-color;
}
.no-results-label {
-fx-font-size: 12px;
-fx-text-fill: gray;
}
.close-btn {
-fx-background-color: transparent;
-fx-cursor: hand;
-fx-font-size: 14px;
}
/* ===== Global Search Result Panel ===== */
.search-results-container {
-fx-background-color: -fx-base;
-fx-padding: 8;
-fx-spacing: 6;
-fx-border-color: transparent transparent #ddd transparent;
-fx-border-width: 0 0 1 0;
}
.search-results-title {
-fx-font-weight: bold;
-fx-font-size: 13px;
-fx-text-fill: -fx-text-base-color;
}
.search-result-item {
-fx-background-color: transparent;
-fx-padding: 6 10;
-fx-spacing: 8;
}
.search-result-item:hover {
-fx-background-color: rgba(0,0,0,0.05);
}
/* Avatar always square, not squeezed */
.search-result-avatar {
-fx-fit-width: 40;
-fx-fit-height: 40;
-fx-preserve-ratio: true;
}
/* Close button */
.search-close-btn {
-fx-background-color: transparent;
-fx-text-fill: -fx-text-base-color;
-fx-font-size: 14px;
-fx-cursor: hand;
}
@@ -69,44 +69,54 @@
</content> </content>
</ScrollPane> </ScrollPane>
<!-- Search panel (hidden initially) --> <!-- Search in chat -->
<VBox fx:id="chatSearchPane" <VBox fx:id="chatSearchPane"
visible="false" managed="false" visible="false" managed="false"
styleClass="chat-search-pane"> styleClass="chat-search-pane">
<!-- Section label -->
<Label text="Search messages in" styleClass="search-label"/> <Label text="Search messages in" styleClass="search-label"/>
<!-- Tiny separator line -->
<Separator styleClass="search-separator" prefHeight="0.5"/> <Separator styleClass="search-separator" prefHeight="0.5"/>
<!-- User profile + "This chat" + close -->
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-scope-bar"> <HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-scope-bar">
<!-- Avatar --> <ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true"/>
<ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true">
<image>
<Image url="@/org/to/telegramfinalproject/Avatars/profile_test.png"/>
</image>
</ImageView>
<!-- scope -->
<VBox alignment="CENTER_LEFT"> <VBox alignment="CENTER_LEFT">
<Label text="This chat" styleClass="search-scope"/> <Label text="This chat" styleClass="search-scope"/>
</VBox> </VBox>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<!-- Close button -->
<Button text="✕" onAction="#closeSearchPanel" styleClass="close-btn"/> <Button text="✕" onAction="#closeSearchPanel" styleClass="close-btn"/>
</HBox> </HBox>
<!-- Placeholder -->
<VBox alignment="CENTER" spacing="10" prefHeight="300"> <VBox alignment="CENTER" spacing="10" prefHeight="300">
<ImageView fx:id="searchIconLarge" fitWidth="64" fitHeight="64" preserveRatio="true"/> <ImageView fx:id="searchIconLarge" fitWidth="64" fitHeight="64" preserveRatio="true"/>
<Label text="Search for messages" styleClass="search-placeholder"/> <Label text="Search for messages" styleClass="search-placeholder"/>
</VBox> </VBox>
<!-- Results -->
<ListView fx:id="chatSearchResults" visible="false" managed="false"/> <ListView fx:id="chatSearchResults" visible="false" managed="false"/>
</VBox> </VBox>
<!-- Global Search Panel -->
<VBox fx:id="globalSearchPane" visible="false" managed="false" styleClass="global-search-pane">
<!-- Header -->
<HBox alignment="CENTER_LEFT">
<Label text=" Search results" styleClass="search-results-title"/>
<Region HBox.hgrow="ALWAYS"/>
<Button text="✕" onAction="#closeGlobalSearch" styleClass="search-close-btn"/>
</HBox>
<!-- Results -->
<ListView fx:id="globalSearchResults" styleClass="global-search-list"/>
<!-- No Results Placeholder -->
<VBox fx:id="noResultsBox" alignment="CENTER" spacing="10" visible="false" managed="false">
<ImageView fx:id="noResultIcon" fitWidth="64" fitHeight="64" preserveRatio="true">
<image>
<Image url="@/org/to/telegramfinalproject/Icons/no_result_dark.png"/>
</image>
</ImageView>
<Label text="No results found" styleClass="no-results-label"/>
</VBox>
</VBox>
</StackPane> </StackPane>
</VBox> </VBox>
Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B