Global search UI implemented.
This commit is contained in:
@@ -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 |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 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 |
Reference in New Issue
Block a user