diff --git a/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java new file mode 100644 index 0000000..a6a038f --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java @@ -0,0 +1,274 @@ +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.io.File; +import java.util.*; +import java.util.stream.Collectors; + +public class AddMembersController { + + @FXML private VBox contactsList; + @FXML private VBox addMembersCard; + @FXML private TextField searchField; + @FXML private Pane overlayBackground; + @FXML private ScrollPane contactsScroll; + @FXML private Button searchIcon; + @FXML private Label memberCountLabel; + @FXML private FlowPane selectedMembersPane; + @FXML private Button cancelButton; + @FXML private Button createButton; + + // Keep selected contacts + private final Set selectedContacts = new HashSet<>(); + + private String groupName; + private File groupImageFile; + + // Sample data for testing + private final List allContacts = Arrays.asList( + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"), + new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png") + ); + + @FXML + public void initialize() { + updateMemberCount(); + + // Sort + render initially + 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 + Platform.runLater(() -> searchField.requestFocus()); + + // Cancel closes overlay + cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent())); + + // Close when clicking outside + overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent())); + + // Create action + createButton.setOnAction(e -> { + if (selectedContacts.isEmpty()) { + return; + } + createGroup(); + }); + + // Smooth scroll + 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; + contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY); + }); + + // Theme icon handling + Platform.runLater(() -> { + if (addMembersCard.getScene() != null) { + ThemeManager.getInstance().registerScene(addMembersCard.getScene()); + } + }); + ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal)); + updateSearchIcon(ThemeManager.getInstance().isDarkMode()); + } + + private void createGroup() { +// String groupName = this.groupName; // set earlier from setGroupInfo() +// File groupImage = this.groupImageFile; // also passed earlier +// +// // Collect selected members +// List memberIds = selectedContacts.stream() +// .map(Contact::getId) // you need some unique identifier for contacts +// .collect(Collectors.toList()); +// +// // Build JSON payload for server +// JSONObject req = new JSONObject(); +// req.put("action", "create_group"); +// req.put("name", groupName); +// req.put("members", memberIds); +// +// if (groupImage != null) { +// req.put("image_path", groupImage.getAbsolutePath()); +// // or upload the file separately depending on your backend design +// } +// +// try { +// JSONObject res = NetworkClient.sendWithResponse(req); // your socket wrapper +// if ("success".equals(res.getString("status"))) { +// // Get new group chat ID from server +// String chatId = res.getString("chat_id"); +// +// // ✅ Close overlay +// MainController.getInstance().closeOverlay(overlayRoot); +// +// // ✅ Open chat immediately +// FXMLLoader loader = new FXMLLoader(getClass().getResource( +// "/org/to/telegramfinalproject/Fxml/chat_page.fxml")); +// Node chatPage = loader.load(); +// +// ChatPageController chatController = loader.getController(); +// chatController.setChat(groupName, +// groupImage != null ? groupImage.toURI().toString() +// : "/org/to/telegramfinalproject/Avatars/default_group.png"); +// +// MainController.getInstance().getChatDisplayArea().getChildren().setAll(chatPage); +// +// } else { +// showAlert("Failed to create group: " + res.getString("message")); +// } +// } catch (Exception ex) { +// ex.printStackTrace(); +// showAlert("Error creating group."); +// } + } + + private void renderContacts(List contacts) { + contactsList.getChildren().clear(); + + if (contacts.isEmpty()) { + StackPane emptyPane = new StackPane(); + emptyPane.setPrefHeight(300); + emptyPane.setAlignment(Pos.CENTER); + + Label emptyLabel = new Label("No contacts found"); + emptyLabel.getStyleClass().add("no-contacts-label"); + + emptyPane.getChildren().add(emptyLabel); + 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(48); + avatar.setFitHeight(48); + 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); + + // Click to toggle selection + item.setOnMouseClicked(e -> toggleSelection(c)); + + // Highlight if already selected + if (selectedContacts.contains(c)) { + item.getStyleClass().add("contact-selected"); + } + + contactsList.getChildren().add(item); + } + } + + private void toggleSelection(Contact contact) { + if (selectedContacts.contains(contact)) { + selectedContacts.remove(contact); + } else { + selectedContacts.add(contact); + } + updateMemberCount(); + updateSelectedMembersPane(); + renderContacts(allContacts); + } + + private void updateSelectedMembersPane() { + selectedMembersPane.getChildren().clear(); + for (Contact c : selectedContacts) { + Label chip = new Label(c.getName()); + chip.getStyleClass().add("member-chip"); + selectedMembersPane.getChildren().add(chip); + } + } + + private void updateMemberCount() { + memberCountLabel.setText(selectedContacts.size() + " / 200000"); + } + + 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); + } + + private void showAlert(String msg) { + Alert alert = new Alert(Alert.AlertType.WARNING, msg, ButtonType.OK); + alert.showAndWait(); + } + + public void setGroupInfo(String groupName, File groupImageFile) { + this.groupName = groupName; + this.groupImageFile = groupImageFile; + + // You can use these later when creating the group + System.out.println("Group name passed: " + groupName); + if (groupImageFile != null) { + System.out.println("Group image: " + groupImageFile.getName()); + } + } + + // 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; } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java b/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java new file mode 100644 index 0000000..ba0a533 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java @@ -0,0 +1,105 @@ +package org.to.telegramfinalproject.UI; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.scene.layout.Pane; +import java.io.File; +import java.io.IOException; + +public class NewGroupController { + + @FXML private VBox newGroupCard; + @FXML private Pane overlayBackground; + @FXML private TextField groupNameField; + @FXML private Label groupNameLabel; + @FXML private Button cameraButton; + @FXML private ImageView cameraIcon; + @FXML private Button cancelButton; + @FXML private Button nextButton; + @FXML private StackPane overlayRoot; // the root + + private File groupImageFile; + + @FXML + public void initialize() { + // Load default camera icon + cameraIcon.setImage(new Image( + getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png") + )); + + // Camera button action → choose image + cameraButton.setOnAction(e -> { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Choose Group Picture"); + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg") + ); + File file = chooser.showOpenDialog(cameraButton.getScene().getWindow()); + if (file != null) { + groupImageFile = file; + cameraIcon.setImage(new Image(file.toURI().toString())); + } + }); + + cancelButton.setOnAction(e -> { + MainController.getInstance().closeOverlay(overlayRoot); + }); + + overlayBackground.setOnMouseClicked(e -> { + MainController.getInstance().closeOverlay(overlayRoot); + }); + + nextButton.setOnAction(e -> { + String groupName = groupNameField.getText().trim(); + + if (groupName.isEmpty()) { + // Apply error style + groupNameField.getStyleClass().add("error"); + groupNameLabel.getStyleClass().add("error"); + + return; + } + + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/add_member.fxml")); + StackPane addMembersOverlay = loader.load(); + + // Optional: pass groupName and image to AddMembersController + AddMembersController controller = loader.getController(); + controller.setGroupInfo(groupName, groupImageFile); + + // Close the current "New Group" overlay + MainController.getInstance().closeOverlay(overlayRoot); + + // Then open the Add Members overlay + MainController.getInstance().showOverlay(addMembersOverlay); + + } catch (IOException ex) { + ex.printStackTrace(); + } + }); + + groupNameField.textProperty().addListener((obs, oldVal, newVal) -> { + if (!newVal.trim().isEmpty()) { + groupNameField.getStyleClass().remove("error"); + groupNameLabel.getStyleClass().remove("error"); + } + }); + + // Auto_focus search bar when overlay opens + Platform.runLater(() -> groupNameField.requestFocus()); + } + + private void showAlert(String msg) { + Alert alert = new Alert(Alert.AlertType.WARNING, msg, ButtonType.OK); + alert.showAndWait(); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index c5c67f0..ab1a43f 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -196,8 +196,20 @@ public class SidebarMenuController { } } - // Example button actions - private void createNewGroup() { System.out.println("Creating New Group..."); } + private void createNewGroup() { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/new_group.fxml")); + Node groupOverlay = loader.load(); + + // Show overlay (like MyProfile, Contacts, etc.) + MainController.getInstance().showOverlay(groupOverlay); + + } catch (IOException e) { + e.printStackTrace(); + } + } + private void createNewChannel() { System.out.println("Creating New Channel..."); } private void openContacts() { 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 22ad344..53e5618 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -624,5 +624,94 @@ } .contacts-list { - -fx-background-color: #1f2a36; /* make sure the VBox background also matches */ + -fx-background-color: #1f2a36; +} + +/* ===== Group Card ===== */ +.group-card { + -fx-background-color: #1f2a36; + -fx-background-radius: 8; + -fx-padding: 16; +} + +.group-name-label { + -fx-text-fill: #1e90ff; /* same blue as light theme */ + -fx-font-size: 13px; + -fx-font-weight: bold; +} + +.group-input { + -fx-background-color: transparent; + -fx-border-color: #444; + -fx-border-width: 0 0 1 0; + -fx-padding: 4 2; + -fx-text-fill: #eee; + -fx-prompt-text-fill: #777; +} + +.group-camera-button { + -fx-background-color: #3390ec; + -fx-background-radius: 50%; + -fx-cursor: hand; +} + +.footer-button { + -fx-background-color: transparent; + -fx-text-fill: #3390ec; + -fx-font-size: 13px; + -fx-font-weight: bold; +} + +/* Group name field normal */ +.group-name-field { + -fx-background-color: transparent; + -fx-border-color: #1e90ff; + -fx-border-width: 0 0 2 0; /* underline only */ + -fx-text-fill: #fff; + -fx-prompt-text-fill: #777; + -fx-focus-color: transparent; /* prevent default blue glow */ + -fx-faint-focus-color: transparent; +} + +/* Error state for group name */ +.group-name-label.error { + -fx-text-fill: #e74c3c; /* red */ +} + +.group-name-field.error { + -fx-border-color: #e74c3c; + -fx-border-width: 0 0 2 0; /* only underline */ + -fx-focus-color: transparent; /* remove default blue glow */ +} + +/* Member count label */ +.member-count { + -fx-font-size: 12px; + -fx-text-fill: #aaa; /* lighter gray for dark background */ +} + +/* Pane holding selected members */ +.selected-members-pane { + -fx-background-color: transparent; + -fx-padding: 6; + -fx-hgap: 6; + -fx-vgap: 6; +} + +/* Footer */ +.contacts-footer { + -fx-background-color: transparent; + -fx-padding: 8 12; +} + +/* Chip for selected members */ +.member-chip { + -fx-background-color: #253544; /* dark bluish bubble */ + -fx-background-radius: 16; + -fx-padding: 4 8; + -fx-spacing: 6; + -fx-alignment: center; +} +.member-chip:hover { + -fx-background-color: #31475b; /* lighter on hover */ } 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 b5e9b16..25d533c 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -637,7 +637,85 @@ } .no-contacts-label { - -fx-text-fill: #777; /* light theme */ + -fx-text-fill: #777; -fx-font-size: 13px; } +/* ===== Group Card ===== */ +.group-card { + -fx-background-color: #fff; + -fx-background-radius: 8; + -fx-padding: 16; +} + +/* Group name label */ +.group-name-label { + -fx-text-fill: #1e90ff; /* blue color */ + -fx-font-size: 13px; + -fx-font-weight: bold; +} + +/* Group name input */ +.group-name-field { + -fx-background-color: transparent; + -fx-border-width: 0 0 2 0; /* only bottom line */ + -fx-border-color: #1e90ff; /* blue underline */ + -fx-padding: 4 0 4 0; + -fx-font-size: 13px; +} + +.group-camera-button { + -fx-background-color: #3390ec; + -fx-background-radius: 50%; + -fx-cursor: hand; +} + +.footer-button { + -fx-background-color: transparent; + -fx-text-fill: #3390ec; + -fx-font-size: 13px; + -fx-font-weight: bold; +} + +/* Error state for group name */ +.group-name-label.error { + -fx-text-fill: #e74c3c; /* red */ +} + +.group-name-field.error { + -fx-border-color: #e74c3c; + -fx-border-width: 0 0 2 0; /* only underline */ + -fx-focus-color: transparent; /* remove default blue glow */ +} + +/* Member count label (top right in header) */ +.member-count { + -fx-font-size: 12px; + -fx-text-fill: #777; /* subtle gray */ +} + +/* Pane holding selected members (chips flow horizontally) */ +.selected-members-pane { + -fx-background-color: transparent; + -fx-padding: 6; + -fx-hgap: 6; + -fx-vgap: 6; +} + +/* Footer with Cancel & Create buttons */ +.contacts-footer { + -fx-background-color: transparent; + -fx-padding: 8 12; +} + +/* Small chip for selected members */ +.member-chip { + -fx-background-color: #d9d9d9; /* light gray bubble */ + -fx-background-radius: 16; + -fx-padding: 4 8; + -fx-spacing: 6; + -fx-alignment: center; +} +.member-chip:hover { + -fx-background-color: #c6c6c6; /* slightly darker on hover */ +} diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/add_member.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/add_member.fxml new file mode 100644 index 0000000..98ca2d8 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/add_member.fxml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +