Files
Telegram-Final-Project/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java
T

444 lines
17 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
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;
import org.to.telegramfinalproject.Models.ChatEntry;
import java.io.IOException;
import java.net.URL;
import java.util.UUID;
public class SidebarMenuController {
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
@FXML private VBox sidebarRoot;
@FXML private ImageView profileImage;
@FXML private Label usernameLabel;
@FXML private Button myProfileButton;
@FXML private Button newGroupButton;
@FXML private Button newChannelButton;
@FXML private Button contactsButton;
@FXML private Button savedMessagesButton;
@FXML private Button settingsButton;
@FXML private Button telegramFeaturesButton;
@FXML private Button telegramQnAButton;
// Custom Telegram-style toggle
@FXML private HBox nightModeToggle;
@FXML private Region toggleThumb;
@FXML private ImageView nightModeIcon;
// convenience handle
private final ThemeManager themeManager = ThemeManager.getInstance();
@FXML
public void initialize() {
// Load default profile image
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
if (profile != null) profileImage.setImage(profile);
AvatarFX.circleClip(profileImage, 56);
setupButtonActions();
setupToggleAction();
// When the Scene is ready:
sidebarRoot.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene != null) {
// 1) Register with ThemeManager so this scene auto-updates on theme changes
themeManager.registerScene(newScene);
// 2) Make sure we start in LIGHT mode
themeManager.setDarkMode(false);
// 3) Sync icons & toggle with current mode
boolean dark = themeManager.isDarkMode();
updateIcons(dark);
syncToggleVisual(dark, /*animate=*/true);
// Ensure the thumb is correctly positioned after layout pass
Platform.runLater(() -> syncToggleVisual(themeManager.isDarkMode(), false));
}
});
// If theme is changed from somewhere else (another screen), keep sidebar in sync
themeManager.darkModeProperty().addListener((o, wasDark, isDark) -> {
updateIcons(isDark);
syncToggleVisual(isDark, /*animate=*/true);
});
}
private void setupButtonActions() {
myProfileButton.setOnAction(e -> openMyProfile());
newGroupButton.setOnAction(e -> createNewGroup());
newChannelButton.setOnAction(e -> createNewChannel());
contactsButton.setOnAction(e -> openContacts());
savedMessagesButton.setOnAction(e -> openSavedMessages());
settingsButton.setOnAction(e -> openSettings());
telegramFeaturesButton.setOnAction(e -> openTelegramFeatures());
telegramQnAButton.setOnAction(e -> openTelegramQnA());
}
private void setupToggleAction() {
nightModeToggle.setOnMouseClicked(e -> {
boolean newDark = !themeManager.isDarkMode();
// Update the global theme via ThemeManager
themeManager.setDarkMode(newDark);
themeManager.applyThemeToAll();
// Animate the thumb to its new position (listener will handle icons & final position sync)
syncToggleVisual(newDark, /*animate=*/true);
});
}
/** Update all button icons to match theme.
* darkMode == true => white icons => use *_light.png
* darkMode == false => dark icons => use *_dark.png
*/
private void updateIcons(boolean darkMode) {
String suffix = darkMode ? "_light.png" : "_dark.png";
myProfileButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix));
newGroupButton.setGraphic(makeIcon(ICON_PATH + "new_group" + suffix));
newChannelButton.setGraphic(makeIcon(ICON_PATH + "new_channel" + suffix));
contactsButton.setGraphic(makeIcon(ICON_PATH + "contacts" + suffix));
savedMessagesButton.setGraphic(makeIcon(ICON_PATH + "saved_messages" + suffix));
settingsButton.setGraphic(makeIcon(ICON_PATH + "settings" + suffix));
telegramFeaturesButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix));
telegramQnAButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix));
// Night-mode moon icon
Image moon = loadImage(ICON_PATH + "night_mode" + suffix);
if (moon != null) nightModeIcon.setImage(moon);
}
/** Keep the toggles CSS class and thumb position in sync with the current mode. */
private void syncToggleVisual(boolean darkMode, boolean animate) {
// CSS class "on" on the track
if (darkMode) {
if (!nightModeToggle.getStyleClass().contains("on")) {
nightModeToggle.getStyleClass().add("on");
}
} else {
nightModeToggle.getStyleClass().remove("on");
}
// Compute target X for the thumb
double offX = 2;
double onX = Math.max(2, nightModeToggle.getWidth() - toggleThumb.getWidth() - 4);
double targetX = darkMode ? onX : offX;
if (animate) {
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
tt.setToX(targetX);
tt.play();
} else {
toggleThumb.setTranslateX(targetX);
}
}
// --- helpers -------------------------------------------------------------
private Image loadImage(String path) {
URL res = getClass().getResource(path);
if (res == null) {
System.err.println("Resource not found: " + path);
return null;
}
return new Image(res.toExternalForm());
}
private ImageView makeIcon(String path) {
ImageView iv = new ImageView();
Image img = loadImage(path);
if (img != null) {
iv.setImage(img);
iv.setFitWidth(22);
iv.setFitHeight(22);
iv.setPreserveRatio(true);
}
return iv;
}
// Sidebar actions
private void openMyProfile() {
try {
// 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(profileName, "online", bio, userId, imageUrl);
// 4. Show overlay
MainController.getInstance().showOverlay(profileOverlay);
} catch (IOException e) {
e.printStackTrace();
showAlert("Error", "Error opening profile", Alert.AlertType.ERROR);
}
}
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() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/to/telegramfinalproject/Fxml/new_channel.fxml"));
Node channelOverlay = loader.load();
// Show overlay (like MyProfile, Contacts, etc.)
MainController.getInstance().showOverlay(channelOverlay);
} catch (IOException e) {
e.printStackTrace();
}
}
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() {
try {
// 1) درخواست به سرور
JSONObject req = new JSONObject().put("action", "get_or_create_saved_messages");
JSONObject res = ActionHandler.sendWithResponse(req);
if (res == null || !"success".equals(res.optString("status"))) {
String msg = (res != null) ? res.optString("message", "Unknown error") : "No response";
showAlert("Saved Messages", "Failed: " + msg, Alert.AlertType.ERROR);
return;
}
JSONObject data = res.optJSONObject("data");
if (data == null) {
showAlert("Saved Messages", "Malformed response.", Alert.AlertType.ERROR);
return;
}
// 2) داده‌ها
String chatIdStr = data.optString("chat_id", null);
if (chatIdStr == null || chatIdStr.isBlank()) {
showAlert("Saved Messages", "Missing chat_id.", Alert.AlertType.ERROR);
return;
}
UUID chatId = UUID.fromString(chatIdStr);
String name = data.optString("name", "Saved Messages");
String chatType = data.optString("chat_type", "private");
boolean isSaved = data.optBoolean("is_saved_messages", true);
// اگر آیکن اختصاصی برای Saved داری، این مسیر رو بده؛ وگرنه null بذار:
String savedIcon = ICON_PATH + (themeManager.isDarkMode() ? "saved_messages_light.png" : "saved_messages_dark.png");
// اگر چنین آیکنی نداری، می‌تونی null بدی تا همان image_url خالی بماند:
// String savedIcon = null;
// 3) درج/به‌روزرسانی در Session و بیاور اول لیست
ChatEntry entry = org.to.telegramfinalproject.Client.Session
.upsertSavedMessages(chatId, name, chatType, savedIcon);
// 4) وضعیت‌های فعلی سشن برای ناوبری
org.to.telegramfinalproject.Client.Session.currentChatId = chatId.toString();
org.to.telegramfinalproject.Client.Session.currentChatType = chatType;
org.to.telegramfinalproject.Client.Session.currentChatEntry = entry;
org.to.telegramfinalproject.Client.Session.backToChatList = false;
// 5) ریفِرش ظاهری لیست چت‌ها (اگر متدی برای این داری، صدا بزن)
try {
MainController.getInstance().refreshChatListUI(); // اگر متد دیگری داری عوضش کن
} catch (Throwable ignore) { }
// 6) باز کردن چت
// --- مسیر اول: اگر متدی داری که با ChatEntry باز می‌کند:
boolean opened = false;
try {
MainController.getInstance().openChat(entry);
MainController.getInstance().closeSidebar();
opened = true;
} catch (Throwable t) {
// مسیر دوم: اگر با id/type باز می‌کنی، یا اول info می‌گیری:
try {
ActionHandler.requestChatInfo(String.valueOf(chatId), chatType);
// اگر متد آشکار برای باز کردن با id داری، اینجا صدا بزن:
// MainController.getInstance().openChatById(chatId, chatType);
opened = true;
} catch (Throwable t2) {
t2.printStackTrace();
}
}
if (!opened) {
// اگر هیچ‌کدام نداشت، حداقل پیغام بده که چت ساخته و آماده است:
System.out.println("Saved Messages ready. Open manually with currentChatId/currentChatType.");
}
} catch (Exception ex) {
ex.printStackTrace();
showAlert("Saved Messages", "Error: " + ex.getMessage(), Alert.AlertType.ERROR);
}
}
private void openSettings() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/to/telegramfinalproject/Fxml/settings.fxml"));
Node settingsOverlay = loader.load();
// Show overlay on top of everything
MainController.getInstance().showOverlay(settingsOverlay);
} catch (IOException e) {
e.printStackTrace();
}
}
private void openTelegramFeatures() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/to/telegramfinalproject/Fxml/telegram_features.fxml"));
Node featuresOverlay = loader.load();
MainController.getInstance().showOverlay(featuresOverlay);
} catch (IOException e) {
e.printStackTrace();
}
}
private void openTelegramQnA() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/to/telegramfinalproject/Fxml/telegram_qna.fxml"));
Node qnaOverlay = loader.load();
MainController.getInstance().showOverlay(qnaOverlay);
} catch (IOException e) {
e.printStackTrace();
}
}
public void setUserFromSession(JSONObject user) {
if (user == null) return;
// نام نمایشی (اول profile_name بعد username)
String displayName = user.optString("profile_name",
user.optString("username", ""));
usernameLabel.setText(displayName);
// عکس پروفایل: هم URL اینترنتی هم مسیر ریسورس را پشتیبانی کن
String img = user.optString("image_url", "");
Image pic = tryLoadImage(img);
if (pic == null) {
pic = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
}
if (pic != null) profileImage.setImage(pic);
}
private Image tryLoadImage(String src) {
if (src == null || src.isBlank()) return null;
try {
// اگر مسیر داخل resources است
var res = getClass().getResource(src);
if (res != null) return new Image(res.toExternalForm(), true);
// اگر مسیر نسبی سرور (مثل /avatars/...) است
String fileUri = AvatarLocalResolver.resolve(src);
if (fileUri != null) return new Image(fileUri, true);
// در غیر این صورت، فرض URL کامل
return new Image(src, true);
} catch (Exception ignored) {
return null;
}
}
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();
}
}