From 694f923c10e8464ee339c998214971b961e203b6 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Mon, 1 Sep 2025 13:04:57 +0330 Subject: [PATCH] Contacts UI implemented. --- .../UI/ContactsController.java | 167 ++++++++++++++++++ .../UI/SidebarMenuController.java | 16 +- .../telegramfinalproject/CSS/dark_theme.css | 96 ++++++++++ .../telegramfinalproject/CSS/light_theme.css | 88 +++++++++ .../telegramfinalproject/Fxml/contacts.fxml | 57 ++++++ 5 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/to/telegramfinalproject/UI/ContactsController.java create mode 100644 src/main/resources/org/to/telegramfinalproject/Fxml/contacts.fxml diff --git a/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java b/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java new file mode 100644 index 0000000..b2b8d0b --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java @@ -0,0 +1,167 @@ +package org.to.telegramfinalproject.UI; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.*; + +import java.util.*; +import java.util.stream.Collectors; + +public class ContactsController { + + @FXML private VBox contactsList; + @FXML private VBox contactsCard; + @FXML private TextField searchField; + @FXML private Button closeFooterButton; + @FXML private Pane overlayBackground; + @FXML private ScrollPane contactsScroll; + @FXML private Button searchIcon; + + // Sample data for testing (later fetch from DB/server) + private final List allContacts = Arrays.asList( + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_profile.png") + ); + + @FXML + public void initialize() { + // Sort contacts alphabetically on load + List sorted = allContacts.stream() + .sorted(Comparator.comparing(Contact::getName)) + .collect(Collectors.toList()); + renderContacts(sorted); + + // Search filter + searchField.textProperty().addListener((obs, oldVal, newVal) -> { + String filter = newVal.toLowerCase(); + List filtered = allContacts.stream() + .filter(c -> c.getName().toLowerCase().contains(filter)) + .sorted(Comparator.comparing(Contact::getName)) + .collect(Collectors.toList()); + renderContacts(filtered); + }); + + // Auto_focus search bar when overlay opens + Platform.runLater(() -> searchField.requestFocus()); + + // Close with footer button + closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(contactsCard.getParent())); + + // Close when clicking outside card + overlayBackground.setOnMouseClicked(e -> { + MainController.getInstance().closeOverlay(contactsCard.getParent()); + }); + + // Smooth scroll feel for contacts list + contactsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()); + contactsScroll.setPannable(true); + contactsScroll.setFitToWidth(true); + contactsScroll.setFitToHeight(false); + contactsScroll.getContent().setOnScroll(event -> { + double deltaY = event.getDeltaY() * 0.003; // smaller = smoother + contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY); + }); + + // Register scene for ThemeManager → stylesheet swap will handle colors/icons + Platform.runLater(() -> { + if (contactsCard.getScene() != null) { + ThemeManager.getInstance().registerScene(contactsCard.getScene()); + } + }); + + // Listener for theme change + ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> { + updateSearchIcon(newVal); + }); + + // Set initial state + updateSearchIcon(ThemeManager.getInstance().isDarkMode()); + } + + private void renderContacts(List contacts) { + contactsList.getChildren().clear(); + + if (contacts.isEmpty()) { + contactsList.getChildren().clear(); + + StackPane emptyPane = new StackPane(); + emptyPane.setPrefHeight(300); // << pushes it lower + emptyPane.setAlignment(Pos.CENTER); + + Label emptyLabel = new Label("No contacts found"); + emptyLabel.getStyleClass().add("no-contacts-label"); + + emptyPane.getChildren().add(emptyLabel); + + // Do NOT give it prefHeight or Vgrow → prevents scroll bar + contactsList.getChildren().add(emptyPane); + return; + } + + for (Contact c : contacts) { + HBox item = new HBox(10); + item.getStyleClass().add("contact-item"); + + // Avatar + ImageView avatar = new ImageView(new Image( + Objects.requireNonNull(getClass().getResourceAsStream(c.getImageUrl())) + )); + avatar.setFitWidth(58); + avatar.setFitHeight(58); + avatar.setPreserveRatio(true); + + VBox details = new VBox(2); + Label nameLabel = new Label(c.getName()); + nameLabel.getStyleClass().add("contact-name"); + + Label statusLabel = new Label(c.getStatus()); + statusLabel.getStyleClass().add("contact-status"); + + details.getChildren().addAll(nameLabel, statusLabel); + + item.getChildren().addAll(avatar, details); + contactsList.getChildren().add(item); + } + } + + private void updateSearchIcon(boolean darkMode) { + String iconPath = darkMode + ? "/org/to/telegramfinalproject/Icons/search_light.png" + : "/org/to/telegramfinalproject/Icons/search_dark.png"; + + ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath))); + icon.setFitWidth(16); + icon.setFitHeight(16); + searchIcon.setGraphic(icon); + } + + // Inner class for contact data + public static class Contact { + private final String name; + private final String status; + private final String imageUrl; + + public Contact(String name, String status, String imageUrl) { + this.name = name; + this.status = status; + this.imageUrl = imageUrl; + } + public String getName() { return name; } + public String getStatus() { return status; } + public String getImageUrl() { return imageUrl; } + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index 289cf12..1641fb6 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -191,7 +191,21 @@ public class SidebarMenuController { // Example button actions private void createNewGroup() { System.out.println("Creating New Group..."); } private void createNewChannel() { System.out.println("Creating New Channel..."); } - private void openContacts() { System.out.println("Opening Contacts..."); } + + private void openContacts() { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/contacts.fxml")); + Node contactsOverlay = loader.load(); + + // Show overlay on top of everything + MainController.getInstance().showOverlay(contactsOverlay); + + } catch (IOException e) { + e.printStackTrace(); + } + } + private void openSavedMessages() { System.out.println("Opening Saved Messages..."); } private void openSettings() { System.out.println("Opening Settings..."); } private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); } diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css index f4157e0..1226f89 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -444,3 +444,99 @@ .camera-button:hover { -fx-background-color: #0077b6; } + +.contacts-card { + -fx-background-color: #1f2a36; + -fx-background-radius: 8; + -fx-padding: 12; +} + +.contacts-header { + -fx-padding: 8 0 8 0; +} +.contacts-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #fff; +} +.icon-button { + -fx-background-color: transparent; + -fx-text-fill: #fff; +} + +.contact-item { + -fx-padding: 8 12; + -fx-background-color: #1e2a35; + -fx-background-radius: 6; + + /* Add subtle shadow like light theme */ + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.4), 4, 0.2, 0, 1); +} + +.contact-item:hover { + -fx-background-color: #253544; /* subtle hover */ + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.6), 6, 0.3, 0, 1); /* stronger on hover */ +} + +.contact-name { + -fx-font-size: 14px; + -fx-text-fill: #eee; +} +.contact-status { + -fx-font-size: 12px; + -fx-text-fill: #999; +} + +.footer-button { + -fx-background-color: transparent; + -fx-text-fill: #1e90ff; + -fx-font-weight: bold; +} + +.overlay-background { + -fx-background-color: rgba(0, 0, 0, 0.4); /* semi-transparent black */ + -fx-pref-width: 100%; + -fx-pref-height: 100%; +} + +.contacts-search-box { + -fx-background-color: #253544; + -fx-background-radius: 10; + -fx-padding: 6 10; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 8, 0.3, 0, 1); + -fx-alignment: center-left; +} + +.contacts-search { + -fx-background-color: #253544; + -fx-border-color: #253544; + -fx-prompt-text-fill: #777; /* Placeholder text */ + -fx-text-fill: #eee; /* Actual user text (light so it’s visible) */ + -fx-font-size: 13px; +} + +.search-icon { + -fx-background-color: transparent; + -fx-cursor: hand; + -fx-opacity: 0.7; +} +.search-icon:hover { + -fx-opacity: 1; +} + +.no-contacts-label { + -fx-text-fill: #aaa; + -fx-font-size: 13px; +} + +.contacts-scroll { + -fx-background-color: transparent; /* outer scroll */ +} + +.contacts-scroll > .viewport { + -fx-background-color: #1f2a36; /* matches contacts-card */ +} + +.contacts-list { + -fx-background-color: #1f2a36; /* make sure the VBox background also matches */ +} diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css index 0f44182..e172216 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -443,3 +443,91 @@ .camera-button:hover { -fx-background-color: #0077b6; } + +.contacts-card { + -fx-background-color: white; + -fx-background-radius: 8; + -fx-padding: 12; +} + +.contacts-header { + -fx-padding: 8 0 8 0; +} +.contacts-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #000; +} +.icon-button { + -fx-background-color: transparent; + -fx-text-fill: #000; +} + +.contacts-search { + -fx-background-color: #f5f5f5; + -fx-background-radius: 6; + -fx-padding: 6; + -fx-font-size: 13px; +} + +.contact-item { + -fx-padding: 8 12; + -fx-background-color: white; + -fx-background-radius: 6; +} + +.contact-item:hover { + -fx-background-color: #f0f0f0; /* highlight on hover */ +} + +.contact-name { + -fx-font-size: 14px; + -fx-text-fill: #111; +} +.contact-status { + -fx-font-size: 12px; + -fx-text-fill: #666; +} + +.footer-button { + -fx-background-color: transparent; + -fx-text-fill: #0078d7; + -fx-font-weight: bold; +} + +.overlay-background { + -fx-background-color: rgba(0, 0, 0, 0.4); /* semi-transparent black */ + -fx-pref-width: 100%; + -fx-pref-height: 100%; +} + +.contacts-search-box { + -fx-background-color: #ffffff; + -fx-background-radius: 6; + -fx-padding: 6 12; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.1), 4, 0.2, 0, 1); + -fx-alignment: center-left; +} + +.contacts-search { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-prompt-text-fill: #aaa; + -fx-text-fill: #222; + -fx-font-size: 13px; +} + +.search-icon { + -fx-background-color: transparent; + -fx-cursor: hand; + -fx-opacity: 0.8; +} +.search-icon:hover { + -fx-opacity: 1; +} + +.no-contacts-label { + -fx-text-fill: #777; /* light theme */ + -fx-font-size: 13px; +} + diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/contacts.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/contacts.fxml new file mode 100644 index 0000000..ddc9d91 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/contacts.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + +