diff --git a/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java index 262dd2f..f077271 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java @@ -109,8 +109,10 @@ public class AddMembersController { String name = c.optString("profile_name", ""); String id = c.optString("contact_id", ""); // ← internal_uuid مخاطب if (id.isBlank()) continue; - String imageUrl = c.optString("image_url", - "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + String imageUrl = c.optString("image_url", "").trim(); + if (imageUrl.isEmpty() || imageUrl.equals("null")) { + imageUrl = "/org/to/telegramfinalproject/Avatars/default_user_profile.png"; + } String status = Optional.ofNullable(c.optString("last_seen", "")) .filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse(""); allContacts.add(new Contact(id, name, status, imageUrl)); @@ -146,12 +148,13 @@ public class AddMembersController { HBox item = new HBox(10); item.getStyleClass().add("contact-item"); - // آواتار + // Avatar ImageView avatar = new ImageView(loadAvatar(c.getImageUrl())); avatar.setFitWidth(48); avatar.setFitHeight(48); avatar.setPreserveRatio(true); + // Details (name + status) VBox details = new VBox(2); Label nameLabel = new Label(c.getName()); nameLabel.getStyleClass().add("contact-name"); @@ -162,28 +165,27 @@ public class AddMembersController { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - CheckBox cb = new CheckBox(); - cb.setSelected(selectedContacts.contains(c)); - cb.selectedProperty().addListener((obs, ov, nv) -> { - if (nv) selectedContacts.add(c); else selectedContacts.remove(c); + // Add elements (no checkbox here) + item.getChildren().addAll(avatar, details, spacer); + + // Highlight if already selected + if (selectedContacts.contains(c)) { + item.getStyleClass().add("contact-selected"); + } + + // Toggle selection by clicking the whole row + item.setOnMouseClicked(e -> { + if (selectedContacts.contains(c)) { + selectedContacts.remove(c); + item.getStyleClass().remove("contact-selected"); + } else { + selectedContacts.add(c); + item.getStyleClass().add("contact-selected"); + } updateMemberCount(); updateSelectedMembersPane(); - // برای هایلایت - if (nv) item.getStyleClass().add("contact-selected"); - else item.getStyleClass().remove("contact-selected"); }); - item.getChildren().addAll(avatar, details, spacer, cb); - - // کلیک روی ردیف = toggle - item.setOnMouseClicked(e -> { - boolean newVal = !cb.isSelected(); - cb.setSelected(newVal); - }); - - // هایلایت انتخاب‌شده - if (selectedContacts.contains(c)) item.getStyleClass().add("contact-selected"); - contactsList.getChildren().add(item); } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/AddSubscriberController.java b/src/main/java/org/to/telegramfinalproject/UI/AddSubscriberController.java index a2a287c..8ac0c26 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/AddSubscriberController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/AddSubscriberController.java @@ -106,8 +106,10 @@ public class AddSubscriberController { String name = c.optString("profile_name", ""); String id = c.optString("contact_id", ""); // internal_uuid if (id.isBlank()) continue; - String imageUrl = c.optString("image_url", - "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + String imageUrl = c.optString("image_url", "").trim(); + if (imageUrl.isEmpty() || imageUrl.equals("null")) { + imageUrl = "/org/to/telegramfinalproject/Avatars/default_user_profile.png"; + } String status = Optional.ofNullable(c.optString("last_seen", "")) .filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse(""); allContacts.add(new Contact(id, name, status, imageUrl)); @@ -142,11 +144,13 @@ public class AddSubscriberController { HBox item = new HBox(10); item.getStyleClass().add("contact-item"); + // Avatar ImageView avatar = new ImageView(loadAvatar(c.getImageUrl())); avatar.setFitWidth(48); avatar.setFitHeight(48); avatar.setPreserveRatio(true); + // Details VBox details = new VBox(2); Label nameLabel = new Label(c.getName()); nameLabel.getStyleClass().add("contact-name"); @@ -157,22 +161,27 @@ public class AddSubscriberController { Region spacer = new Region(); HBox.setHgrow(spacer, Priority.ALWAYS); - CheckBox cb = new CheckBox(); - cb.setSelected(selected.contains(c)); - cb.selectedProperty().addListener((obs, ov, nv) -> { - if (nv) selected.add(c); else selected.remove(c); + // Add elements (no checkbox) + item.getChildren().addAll(avatar, details, spacer); + + // Highlight if already selected + if (selected.contains(c)) { + item.getStyleClass().add("contact-selected"); + } + + // Toggle selection by clicking the row + item.setOnMouseClicked(e -> { + if (selected.contains(c)) { + selected.remove(c); + item.getStyleClass().remove("contact-selected"); + } else { + selected.add(c); + item.getStyleClass().add("contact-selected"); + } updateCount(); updateSelectedPane(); - if (nv) item.getStyleClass().add("contact-selected"); - else item.getStyleClass().remove("contact-selected"); }); - item.getChildren().addAll(avatar, details, spacer, cb); - - item.setOnMouseClicked(e -> cb.setSelected(!cb.isSelected())); - - if (selected.contains(c)) item.getStyleClass().add("contact-selected"); - contactsList.getChildren().add(item); } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java new file mode 100644 index 0000000..c7c5486 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java @@ -0,0 +1,142 @@ +package org.to.telegramfinalproject.UI; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +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; + +public class BlockedUsersController { + + @FXML private Pane overlayBackground; + @FXML private Button backButton; + @FXML private Button closeButton; + @FXML private Label countLabel; + @FXML private ListView blockedList; + @FXML private VBox blockedCard; + + @FXML + public void initialize() { + // Back button icon + backButton.setGraphic(makeIcon("/org/to/telegramfinalproject/Icons/back_button_dark.png")); + + // Example blocked users (replace with server data) + addBlockedUser("Asal", "@Whales_suicide", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Replies", "@replies", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Asal", "@Whales_suicide", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Replies", "@replies", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Asal", "@Whales_suicide", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Replies", "@replies", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Asal", "@Whales_suicide", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Replies", "@replies", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Asal", "@Whales_suicide", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + addBlockedUser("Replies", "@replies", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"); + + updateCount(); + + closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayBackground.getParent())); + backButton.setOnAction(e -> MainController.getInstance().goBack((StackPane) overlayBackground.getParent())); + + overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(blockedCard.getParent())); + + // Register scene for ThemeManager → stylesheet swap will handle colors/icons + Platform.runLater(() -> { + if (blockedCard.getScene() != null) { + ThemeManager.getInstance().registerScene(blockedCard.getScene()); + } + }); + + // Listener for theme change + ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> { + updateBackIcon(newVal); + }); + + // Set initial state + updateBackIcon(ThemeManager.getInstance().isDarkMode()); + + // Add CSS + blockedList.getStylesheets().add( + getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm() + ); + + // Wait until skin is ready + blockedList.skinProperty().addListener((obs, oldSkin, newSkin) -> { + if (newSkin != null) { + // Look up vertical scrollbar inside the ListView + ScrollBar vBar = (ScrollBar) blockedList.lookup(".scroll-bar:vertical"); + if (vBar != null) { + blockedList.setOnScroll(event -> { + double deltaY = event.getDeltaY() * 0.003; // smaller = smoother + double newValue = vBar.getValue() - deltaY; + vBar.setValue(Math.max(0, Math.min(newValue, 1))); // clamp between 0–1 + }); + } + } + }); + } + + private void addBlockedUser(String name, String id, String avatarPath) { + // Avatar + ImageView avatar = new ImageView(new Image(getClass().getResourceAsStream(avatarPath))); + avatar.setFitWidth(60); + avatar.setFitHeight(60); + avatar.setPreserveRatio(true); + avatar.setSmooth(true); + avatar.setCache(true); + + // round mask + Circle clip = new Circle(20, 20, 40); // x, y, radius (same as half of fit size) + avatar.setClip(clip); + + // Name + ID + VBox info = new VBox(2); + Label nameLabel = new Label(name); + nameLabel.getStyleClass().add("blocked-name"); + Label idLabel = new Label(id); + idLabel.getStyleClass().add("blocked-id"); + info.getChildren().addAll(nameLabel, idLabel); + + // Unblock button + Button unblockBtn = new Button("Unblock"); + unblockBtn.getStyleClass().add("unblock-button"); + unblockBtn.setOnAction(e -> { + blockedList.getItems().removeIf(h -> h.getChildren().contains(avatar)); + updateCount(); + }); + + // Row + HBox row = new HBox(10, avatar, info, unblockBtn); + row.setSpacing(12); + row.getStyleClass().add("blocked-row"); + HBox.setHgrow(info, javafx.scene.layout.Priority.ALWAYS); + + blockedList.getItems().add(row); + } + + private void updateCount() { + countLabel.setText(blockedList.getItems().size() + " blocked users"); + } + + private void updateBackIcon(boolean darkMode) { + String iconPath = darkMode + ? "/org/to/telegramfinalproject/Icons/back_button_light.png" + : "/org/to/telegramfinalproject/Icons/back_button_dark.png"; + + ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath))); + icon.setFitWidth(16); + icon.setFitHeight(16); + backButton.setGraphic(icon); + } + + private ImageView makeIcon(String path) { + ImageView iv = new ImageView(new Image(getClass().getResourceAsStream(path))); + iv.setFitWidth(20); + iv.setFitHeight(20); + return iv; + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChangeCredentialsController.java b/src/main/java/org/to/telegramfinalproject/UI/ChangeCredentialsController.java new file mode 100644 index 0000000..fd06ad4 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/ChangeCredentialsController.java @@ -0,0 +1,160 @@ +package org.to.telegramfinalproject.UI; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +public class ChangeCredentialsController { + + @FXML private VBox credentialsCard; + @FXML private Pane overlayBackground; + @FXML private TextField usernameField; + @FXML private Label usernameLabel; + + @FXML private Label passwordLabel; + @FXML private PasswordField passwordField; + @FXML private TextField visiblePasswordField; + @FXML private Button togglePasswordBtn; + + @FXML private Label confirmPasswordLabel; + @FXML private PasswordField confirmPasswordField; + @FXML private TextField visibleConfirmPasswordField; + @FXML private Button toggleConfirmBtn; + + @FXML private Button cancelButton; + @FXML private Button saveButton; + @FXML private Button closeButton; + + private boolean passwordVisible = false; + private boolean confirmVisible = false; + + @FXML + private void initialize() { + // Toggle password visibility + togglePasswordBtn.setOnAction(e -> togglePasswordVisibility()); + toggleConfirmBtn.setOnAction(e -> toggleConfirmVisibility()); + + saveButton.setOnAction(e -> validateAndSave()); + + // Close the overlay + closeButton.setOnAction(e -> + MainController.getInstance().closeOverlay(credentialsCard.getParent())); + cancelButton.setOnAction(e -> + MainController.getInstance().closeOverlay(credentialsCard.getParent())); + overlayBackground.setOnMouseClicked(e -> + MainController.getInstance().closeOverlay(credentialsCard.getParent()) + ); + + // Reset error when typing + usernameField.textProperty().addListener((obs, o, n) -> removeError(usernameField, usernameLabel)); + + passwordField.textProperty().addListener((obs, o, n) -> { + visiblePasswordField.setText(n); // keep synced + removeError(passwordField, passwordLabel); + removeError(visiblePasswordField, passwordLabel); + }); + visiblePasswordField.textProperty().addListener((obs, o, n) -> { + passwordField.setText(n); + removeError(passwordField, passwordLabel); + removeError(visiblePasswordField, passwordLabel); + }); + + confirmPasswordField.textProperty().addListener((obs, o, n) -> { + visibleConfirmPasswordField.setText(n); + removeError(confirmPasswordField, confirmPasswordLabel); + removeError(visibleConfirmPasswordField, confirmPasswordLabel); + }); + visibleConfirmPasswordField.textProperty().addListener((obs, o, n) -> { + confirmPasswordField.setText(n); + removeError(confirmPasswordField, confirmPasswordLabel); + removeError(visibleConfirmPasswordField, confirmPasswordLabel); + }); + + // Auto_focus password field when overlay opens + Platform.runLater(() -> usernameField.requestFocus()); + } + + private void togglePasswordVisibility() { + passwordVisible = !passwordVisible; + visiblePasswordField.setText(passwordField.getText()); + visiblePasswordField.setVisible(passwordVisible); + visiblePasswordField.setManaged(passwordVisible); + + passwordField.setVisible(!passwordVisible); + passwordField.setManaged(!passwordVisible); + + togglePasswordBtn.setText(passwordVisible ? "👁" : "👁"); + } + + private void toggleConfirmVisibility() { + confirmVisible = !confirmVisible; + visibleConfirmPasswordField.setText(confirmPasswordField.getText()); + visibleConfirmPasswordField.setVisible(confirmVisible); + visibleConfirmPasswordField.setManaged(confirmVisible); + + confirmPasswordField.setVisible(!confirmVisible); + confirmPasswordField.setManaged(!confirmVisible); + + toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁"); + } + + private void validateAndSave() { + boolean hasError = false; + + // --- Username check --- + if (usernameField.getText().trim().isEmpty()) { + addError(usernameField, usernameLabel); + hasError = true; + } else { + removeError(usernameField, usernameLabel); + } + + // --- Password check --- + String pwd = passwordVisible ? visiblePasswordField.getText().trim() : passwordField.getText().trim(); + if (pwd.isEmpty()) { + addError(passwordField, passwordLabel); + addError(visiblePasswordField, passwordLabel); + hasError = true; + } else { + removeError(passwordField, passwordLabel); + removeError(visiblePasswordField, passwordLabel); + } + + // --- Confirm password check --- + String confirm = confirmVisible ? visibleConfirmPasswordField.getText().trim() : confirmPasswordField.getText().trim(); + if (confirm.isEmpty()) { + addError(confirmPasswordField, confirmPasswordLabel); + addError(visibleConfirmPasswordField, confirmPasswordLabel); + hasError = true; + } else if (!confirm.equals(pwd)) { + addError(confirmPasswordField, confirmPasswordLabel); + addError(visibleConfirmPasswordField, confirmPasswordLabel); + hasError = true; + } else { + removeError(confirmPasswordField, confirmPasswordLabel); + removeError(visibleConfirmPasswordField, confirmPasswordLabel); + } + + if (hasError) return; + + // TODO: send to server + System.out.println("Saving new credentials..."); + } + + private void addError(TextField field, Label label) { + if (!field.getStyleClass().contains("error-input")) { + field.getStyleClass().add("error-input"); + } + if (!label.getStyleClass().contains("error-label")) { + label.getStyleClass().add("error-label"); + } + } + + private void removeError(TextField field, Label label) { + field.getStyleClass().remove("error-input"); + label.getStyleClass().remove("error-label"); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java b/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java index 5b73e4d..b023d71 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java @@ -2,12 +2,16 @@ package org.to.telegramfinalproject.UI; import javafx.application.Platform; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import java.io.IOException; + public class CheckPasswordController { @FXML private VBox checkPasswordCard; @@ -103,9 +107,8 @@ public class CheckPasswordController { visiblePasswordField.getStyleClass().remove("error"); passwordLabel.getStyleClass().remove("error"); - // TODO: go to change credentials scene - System.out.println("Password OK → go to next scene"); - // MainController.getInstance().showOverlay("/org/to/telegramfinalproject/Fxml/change_credentials.fxml"); + // Go to the next scene + openChangeCredentials(); } else { // Error → add error styles @@ -142,4 +145,18 @@ public class CheckPasswordController { icon.setFitHeight(16); backButton.setGraphic(icon); } + + private void openChangeCredentials() { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/change_credentials.fxml")); + Node overlay = loader.load(); + + // Show new overlay + MainController.getInstance().showOverlay(overlay); + + } catch (IOException ex) { + ex.printStackTrace(); + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java b/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java index de8eba8..8df3ad3 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java @@ -1,9 +1,11 @@ package org.to.telegramfinalproject.UI; +import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; +import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; @@ -13,6 +15,10 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; +import javafx.util.Duration; +import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ActionHandler; +import org.to.telegramfinalproject.Client.Session; import java.io.File; import java.io.IOException; @@ -43,11 +49,18 @@ public class EditProfileController { )); // close on background click - overlayBackground.setOnMouseClicked(e -> closeEdit()); + overlayBackground.setOnMouseClicked(e -> { + saveChangesAndClose(); + closeEdit(); + }); - closeButton.setOnAction(e -> closeEdit()); + closeButton.setOnAction(e -> { + saveChangesAndClose(); + closeEdit(); + }); backButton.setOnAction(e -> { + saveChangesAndClose(); MainController.getInstance().goBack(overlayBackground); }); @@ -112,4 +125,78 @@ public class EditProfileController { usernameField.setText(userId); profileImageView.setImage(profileImage); } + + private void saveChangesAndClose() { + JSONObject currentUser = Session.currentUser; + + String newBio = bioField.getText().trim(); + String newName = nameField.getText().trim(); + String newUserId = usernameField.getText().trim(); + + boolean anyError = false; + + // --- Update bio --- + String oldBio = currentUser.optString("bio", ""); + if (!newBio.equals(oldBio)) { + JSONObject req = new JSONObject().put("action", "edit_bio").put("new_bio", newBio); + JSONObject resp = ActionHandler.sendWithResponse(req); + if (resp == null || !"success".equals(resp.optString("status"))) { + anyError = true; + showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR); + } else { + currentUser.put("bio", newBio); + } + } + + // --- Update profile name --- + String oldName = currentUser.optString("profile_name", ""); + if (!newName.equals(oldName)) { + JSONObject req = new JSONObject().put("action", "edit_profile_name").put("new_profile_name", newName); + JSONObject resp = ActionHandler.sendWithResponse(req); + if (resp == null || !"success".equals(resp.optString("status"))) { + anyError = true; + showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR); + } else { + currentUser.put("profile_name", newName); + } + } + + // --- Update user ID (username) --- + String oldUserId = currentUser.optString("user_id", ""); + if (!newUserId.equals(oldUserId)) { + JSONObject req = new JSONObject().put("action", "edit_user_id").put("new_user_id", newUserId); + JSONObject resp = ActionHandler.sendWithResponse(req); + if (resp == null || !"success".equals(resp.optString("status"))) { + anyError = true; + showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR); + } else { + currentUser.put("user_id", newUserId); + } + } + + if (!anyError) { + MyProfileController.getInstance().setProfileData( + currentUser.optString("profile_name"), + "online", + currentUser.optString("bio"), + currentUser.optString("user_id"), + currentUser.optString("image_url", "/org/to/telegramfinalproject/Avatars/default_user_profile.png") + ); + MainController.getInstance().closeOverlay(bioField.getParent().getParent()); + } + } + + private void showAlert(String title, String message, Alert.AlertType type) { + Alert alert = new Alert(type); + alert.setTitle(title); + alert.setHeaderText(null); // no big header, just the message + alert.setContentText(message); + + // optional: style it to fit your dark/light theme + if (alert.getDialogPane().getScene() != null) { + ThemeManager.getInstance().registerScene(alert.getDialogPane().getScene()); + } + + alert.showAndWait(); + } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/MainController.java b/src/main/java/org/to/telegramfinalproject/UI/MainController.java index 3ad086f..38f2236 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/MainController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/MainController.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.UI; +import javafx.animation.PauseTransition; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.fxml.FXML; @@ -605,6 +606,9 @@ public class MainController { overlayLayer.getChildren().remove(current); } overlayLayer.getChildren().add(overlayNode); + + // Automatically close sidebar + closeSidebar(); } public void closeOverlay(Node overlayNode) { @@ -640,6 +644,7 @@ public class MainController { private enum SRType { USER, GROUP, CHANNEL, MESSAGE } + // Inner class private static class SearchResult { final SRType type; final String title; // name / sender_name / ... @@ -1133,8 +1138,4 @@ public class MainController { getChatPageController().showChat(entry); } } - - - - } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/MyProfileController.java b/src/main/java/org/to/telegramfinalproject/UI/MyProfileController.java index 1ff67b2..52a82b8 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/MyProfileController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/MyProfileController.java @@ -28,7 +28,14 @@ public class MyProfileController { @FXML private VBox profileCard; @FXML private VBox bioBlock; - private final ThemeManager themeManager = ThemeManager.getInstance(); + private String userID; + private static MyProfileController instance; + + public MyProfileController() { + instance = this; + } + + public static MyProfileController getInstance() { return instance;} @FXML private void initialize() { @@ -77,11 +84,12 @@ public class MyProfileController { bioBlock.setManaged(false); } else { userBio.setText(bio); - userBio.setVisible(true); - userBio.setManaged(true); + bioBlock.setVisible(true); + bioBlock.setManaged(true); } - this.userId.setText(userId); + this.userId.setText("@" + userId); + userID = userId; if (imageUrl != null && !imageUrl.isEmpty()) { profileImage.setImage(new Image(imageUrl)); @@ -111,8 +119,8 @@ public class MyProfileController { controller.setProfileData( profileName.getText(), profileStatus.getText(), - userBio.isVisible() ? userBio.getText() : null, - userId.getText(), + bioBlock.isVisible() ? userBio.getText() : null, + userID, profileImage.getImage() ); diff --git a/src/main/java/org/to/telegramfinalproject/UI/PrivacySecurityController.java b/src/main/java/org/to/telegramfinalproject/UI/PrivacySecurityController.java index e76ac5c..13b3229 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/PrivacySecurityController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/PrivacySecurityController.java @@ -41,8 +41,17 @@ public class PrivacySecurityController { // Blocked users management blockedUsersButton.setOnAction(e -> { - // TODO: open Blocked Users scene - System.out.println("Open blocked users management"); + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/blocked_users.fxml")); + Node overlay = loader.load(); + + // Show new overlay + MainController.getInstance().showOverlay(overlay); + + } catch (IOException ex) { + ex.printStackTrace(); + } }); // Close diff --git a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index 696529d..267a0ff 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -1,11 +1,13 @@ package org.to.telegramfinalproject.UI; +import javafx.animation.PauseTransition; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.image.Image; @@ -15,6 +17,7 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.util.Duration; import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ActionHandler; import org.to.telegramfinalproject.Client.AvatarLocalResolver; @@ -182,17 +185,42 @@ public class SidebarMenuController { // Sidebar actions private void openMyProfile() { try { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/my_profile.fxml")); + // 1. Ask server for profile info + JSONObject request = new JSONObject().put("action", "get_user_profile"); + JSONObject response = ActionHandler.sendWithResponse(request); + + if (response == null || !"success".equals(response.optString("status"))) { + showAlert("Error", "Failed to load profile", Alert.AlertType.ERROR); + return; + } + + JSONObject profile = response.optJSONObject("data"); + if (profile == null) { + showAlert("Error", "Malformed profile data", Alert.AlertType.ERROR); + return; + } + + // Extract fields + String profileName = profile.optString("profile_name", "Unknown"); + String bio = profile.optString("bio", ""); + String userId = profile.optString("user_id", ""); + String imageUrl = profile.optString("profile_picture_url", null); + + // 2. Load overlay FXML + FXMLLoader loader = new FXMLLoader(getClass().getResource( + "/org/to/telegramfinalproject/Fxml/my_profile.fxml")); Node profileOverlay = loader.load(); + // 3. Pass real data to controller MyProfileController controller = loader.getController(); - controller.setProfileData("Asal", "online", "Pass me that lovely little gun♡.", "@Whales_suicide", null); + controller.setProfileData(profileName, "online", bio, userId, imageUrl); - // Show the overlay on top of mainRoot + // 4. Show overlay MainController.getInstance().showOverlay(profileOverlay); } catch (IOException e) { e.printStackTrace(); + showAlert("Error", "Error opening profile", Alert.AlertType.ERROR); } } @@ -309,5 +337,17 @@ public class SidebarMenuController { } } + private void showAlert(String title, String message, Alert.AlertType type) { + Alert alert = new Alert(type); + alert.setTitle(title); + alert.setHeaderText(null); // no big header, just the message + alert.setContentText(message); + // optional: style it to fit your dark/light theme + if (alert.getDialogPane().getScene() != null) { + ThemeManager.getInstance().registerScene(alert.getDialogPane().getScene()); + } + + alert.showAndWait(); + } } diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css index 5def59d..97d0747 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css @@ -1,4 +1,3 @@ -/* دقیقاً فقط روی این دو دکمه اعمال می‌شود */ #joinAction, #addContactAction { -fx-background-color: transparent, transparent, transparent, transparent; -fx-background-insets: 0,0,0,0; 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 c9d899e..967258c 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -989,3 +989,93 @@ -fx-padding: 0 6 0 6; -fx-font-size: 14px; } + +.blocked-card { + -fx-background-color: #1f2a36; + -fx-background-radius: 10; + -fx-padding: 12; +} + +.blocked-header { + -fx-padding: 8; +} +.blocked-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.blocked-count { + -fx-font-size: 13px; + -fx-text-fill: #999; + -fx-padding: 4 0; +} + +.blocked-list { + -fx-background-color: #253544; +} + +.blocked-row { + -fx-padding: 8; + -fx-background-color: transparent; +} +.blocked-row:hover { + -fx-background-color: #253544; +} + +.blocked-name { + -fx-font-size: 13px; + -fx-font-weight: bold; + -fx-text-fill: white; +} +.blocked-id { + -fx-font-size: 12px; + -fx-text-fill: #aaa; +} + +.unblock-button { + -fx-background-color: transparent; + -fx-text-fill: #3a86ff; + -fx-cursor: hand; +} + +.unblock-button:hover { + -fx-text-fill: #66aaff; +} + +.blocked-info-label { + -fx-background-color: #2c3e50; /* darker banner background */ + -fx-text-fill: #bbb; /* lighter muted text */ + -fx-font-size: 12px; + -fx-wrap-text: true; + -fx-padding: 5; + -fx-background-radius: 4; +} + +.blocked-list .thumb { + -fx-background-color: rgba(200, 200, 200, 0.5); /* lighter for dark bg */ +} + +/* Each list cell */ +.blocked-list .list-cell { + -fx-background-color: #1f2a36; + -fx-text-fill: #eeeeee; + -fx-padding: 8 12; + -fx-border-color: #2f2f2f; + -fx-border-width: 0 0 1 0; +} + +.blocked-list .list-cell:hover { + -fx-background-color: #2a3a4a; +} + +/* Error label */ +.label.error-label { + -fx-text-fill: red; +} + +/* Error input */ +.error-input { + -fx-border-color: red; + -fx-border-radius: 6; +} 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 48d8e32..9f00b66 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -989,3 +989,84 @@ -fx-padding: 0 6 0 6; -fx-font-size: 14px; } + +/* Blocked Users card */ +.blocked-card { + -fx-background-color: white; + -fx-background-radius: 10; + -fx-padding: 12; +} + +/* Header */ +.blocked-header { + -fx-padding: 8; +} +.blocked-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +/* Count */ +.blocked-count { + -fx-font-size: 13px; + -fx-text-fill: #888; + -fx-padding: 4 0; +} + +/* List */ +.blocked-list { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +/* Row */ +.blocked-row { + -fx-padding: 8; + -fx-background-color: transparent; +} +.blocked-row:hover { + -fx-background-color: #f5f5f5; +} + +/* Labels */ +.blocked-name { + -fx-font-size: 13px; + -fx-font-weight: bold; + -fx-text-fill: black; +} +.blocked-id { + -fx-font-size: 12px; + -fx-text-fill: #666; +} + +/* Button */ +.unblock-button { + -fx-background-color: transparent; + -fx-text-fill: #2196f3; + -fx-cursor: hand; +} + +.unblock-button:hover { + -fx-text-fill: #66aaff; +} + +.blocked-info-label { + -fx-background-color: #f5f5f5; /* light gray background */ + -fx-text-fill: #555; /* muted text */ + -fx-font-size: 12px; + -fx-wrap-text: true; + -fx-padding: 5; /* space inside like a banner */ + -fx-background-radius: 4; /* soft rounded edges */ +} + +/* Error label */ +.label.error-label { + -fx-text-fill: red; +} + +/* Error input */ +.error-input { + -fx-border-color: red; + -fx-border-radius: 6; +} diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/scrollpane.css b/src/main/resources/org/to/telegramfinalproject/CSS/scrollpane.css index b6f1ffe..7c00d54 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/scrollpane.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/scrollpane.css @@ -20,3 +20,20 @@ -fx-background-color: rgba(100, 100, 100, 0.5); /* Semi-transparent thumb */ -fx-background-radius: 3px; } + + +.blocked-list .scroll-bar { + -fx-background-color: transparent; + -fx-opacity: 0; + -fx-pref-width: 6px; + -fx-background-radius: 3px; +} + +.blocked-list:hover .scroll-bar { + -fx-opacity: 0.6; +} + +.blocked-list .thumb { + -fx-background-color: rgba(100, 100, 100, 0.5); + -fx-background-radius: 3px; +} diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/telegram-action.css b/src/main/resources/org/to/telegramfinalproject/CSS/telegram-action.css index 68a71e6..e9fe509 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/telegram-action.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/telegram-action.css @@ -1,4 +1,4 @@ -/* رنگ‌ها */ +/* Colors */ :root { -tg-bg: #ffffff; -tg-sheet: #ffffff; diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/blocked_users.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/blocked_users.fxml new file mode 100644 index 0000000..11f4644 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/blocked_users.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + +