Search system fixed.

This commit is contained in:
Asal Lotfi
2025-08-31 01:01:05 +03:30
parent dff7b82e34
commit 51daae6bd0
4 changed files with 133 additions and 151 deletions
@@ -21,10 +21,6 @@ public class ChatItemController {
@FXML private ImageView profileImageSystem;
@FXML private Circle systemCircle;
// Default avatar resource path
private static final String DEFAULT_AVATAR =
"/org/to/telegramfinalproject/Avatars/default_avatar.png";
/**
* Set chat item data, including a profile image (or default).
*
@@ -431,7 +431,7 @@ public class ChatPageController {
chatStatus.setText("");
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
try {
userAvatar.setImage(new Image(entry.getImageUrl()));
userAvatar.setImage(new Image(entry.getImageUrl() != null ? entry.getImageUrl() : "/org/to/telegramfinalproject/Avatars/default_user_profile.png"));
userAvatar.setClip(new Circle(18, 18, 18));
} catch (Exception ignored) {
}
@@ -13,6 +13,7 @@ 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.Models.ChatEntry;
import org.to.telegramfinalproject.Client.ActionHandler;
@@ -28,9 +29,10 @@ public class MainController {
// === Global Search ===
@FXML private TextField searchBar;
@FXML private VBox globalSearchPane;
@FXML private ListView<SearchResult> globalSearchResults;
@FXML private VBox noResultsBox;
@FXML private ImageView noResultIcon;
@FXML private ScrollPane globalSearchScroll;
@FXML private VBox globalSearchResultsContainer;
private enum SearchMode {
GLOBAL,
CHAT
@@ -83,10 +85,9 @@ public class MainController {
@FXML
public void initialize() {
// addSampleChats();
// Populate chat list
populateChatListFromSession();
// Register the scene for automatic CSS updates
Platform.runLater(() -> {
ThemeManager.getInstance().registerScene(mainRoot.getScene());
@@ -95,17 +96,15 @@ public class MainController {
// Listen for theme changes to update icons & labels manually
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
if (newVal) {
// Dark mode ON
updateIconsForDarkMode();
updateLabelsForDarkMode();
} else {
// Light mode ON
updateIconsForLightMode();
updateLabelsForLightMode();
}
});
// Smooth scroll feel
// Smooth scroll feel for chat list
scrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
scrollPane.setPannable(true);
scrollPane.setFitToWidth(true);
@@ -124,25 +123,7 @@ public class MainController {
mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider());
}
// Search in chat field listener
// searchBar.textProperty().addListener((obs, oldV, newV) -> {
// if (!chatSearchPane.isVisible()) return; // only react if in search mode
//
// if (newV.trim().isEmpty()) {
// chatSearchResults.setVisible(false);
// chatSearchResults.setManaged(false);
// } else {
// chatSearchResults.setVisible(true);
// chatSearchResults.setManaged(true);
// chatSearchResults.getItems().setAll(
// "Result 1: " + newV,
// "Result 2: " + newV,
// "Result 3: " + newV
// );
// }
// });
// Search bar enter action
searchBar.setOnAction(e -> {
String keyword = searchBar.getText().trim();
if (keyword.isEmpty()) return;
@@ -154,100 +135,28 @@ public class MainController {
}
});
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);
// 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();
}
}
});
// CLick on the search result
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
@@ -255,12 +164,28 @@ public class MainController {
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);
@@ -273,15 +198,14 @@ public class MainController {
}
@FXML
public void closeSearchPanel() {
chatSearchPane.setVisible(false);
chatSearchPane.setManaged(false);
currentSearchMode = SearchMode.GLOBAL;
currentChatId = null;
public void closeGlobalSearch() {
globalSearchPane.setVisible(false);
globalSearchPane.setManaged(false);
scrollPane.setVisible(true);
scrollPane.setManaged(true);
searchBar.clear();
}
// private void addSampleChats() {
@@ -387,11 +311,14 @@ public class MainController {
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_user_profile.png", chat.getType());
// 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);
@@ -434,7 +361,6 @@ public class MainController {
ChatPageController controller = loader.getController();
controller.showChat(chat);
currentSearchMode = SearchMode.CHAT;
currentChatId = UUID.fromString(chat.getId().toString());
chatDisplayArea.getChildren().setAll(chatPage);
@@ -669,7 +595,7 @@ public class MainController {
showGlobalSearchPanel();
// درخواست به سرور (مثل کنسول)
// Request to server
org.json.JSONObject req = new org.json.JSONObject();
req.put("action", "search");
req.put("keyword", keyword);
@@ -766,33 +692,61 @@ public class MainController {
// }
private void renderSearchResults(List<SearchResult> results) {
globalSearchResults.getItems().clear();
globalSearchResultsContainer.getChildren().clear();
if (results.isEmpty()) {
noResultsBox.setVisible(true);
noResultsBox.setManaged(true);
globalSearchResults.setVisible(false);
globalSearchResults.setManaged(false);
globalSearchScroll.setVisible(false);
globalSearchScroll.setManaged(false);
return;
}
noResultsBox.setVisible(false);
noResultsBox.setManaged(false);
globalSearchResults.setVisible(true);
globalSearchResults.setManaged(true);
globalSearchScroll.setVisible(true);
globalSearchScroll.setManaged(true);
globalSearchResults.getItems().addAll(results);
}
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);
@FXML
public void closeGlobalSearch() {
globalSearchPane.setVisible(false);
globalSearchPane.setManaged(false);
// 🔑 Clip it into a circle
Circle clip = new Circle(20, 20, 20); // x,y = center, radius = 20
avatar.setClip(clip);
scrollPane.setVisible(true);
scrollPane.setManaged(true);
Label title = new Label(r.title);
title.getStyleClass().add("global-search-title");
searchBar.clear();
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) {
@@ -44,10 +44,14 @@
</graphic>
</Button>
<TextField fx:id="searchBar"
prefHeight="36" prefWidth="430.0"
promptText="Search"
styleClass="global-search"/>
<HBox spacing="5" alignment="CENTER" HBox.hgrow="ALWAYS">
<TextField fx:id="searchBar"
promptText="Search"
HBox.hgrow="ALWAYS"
maxWidth="Infinity"
styleClass="global-search"/>
</HBox>
</children>
</HBox>
@@ -104,8 +108,36 @@
<Button text="✕" onAction="#closeGlobalSearch" styleClass="search-close-btn"/>
</HBox>
<!-- Results -->
<ListView fx:id="globalSearchResults" styleClass="global-search-list"/>
<!-- Search results -->
<ScrollPane fx:id="globalSearchScroll"
fitToWidth="true"
hbarPolicy="NEVER"
vbarPolicy="AS_NEEDED"
styleClass="chat-scroll">
<content>
<VBox fx:id="globalSearchResultsContainer" spacing="5"
styleClass="global-search-list">
<!-- One search result item -->
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-result-item">
<!-- Avatar -->
<StackPane>
<ImageView fx:id="searchResultAvatar"
fitWidth="40" fitHeight="40"
preserveRatio="true"
smooth="true"
cache="true"
styleClass="avatar"/>
</StackPane>
<!-- Texts -->
<VBox spacing="2">
<Label fx:id="searchResultTitle" styleClass="global-search-title"/>
<Label fx:id="searchResultSubtitle" styleClass="global-search-subtitle"/>
</VBox>
</HBox>
</VBox>
</content>
</ScrollPane>
<!-- No Results Placeholder -->
<VBox fx:id="noResultsBox" alignment="CENTER" spacing="10" visible="false" managed="false">