444 lines
17 KiB
Java
444 lines
17 KiB
Java
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 toggle’s 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();
|
||
}
|
||
} |