Private chat info UI + backend implemented.
@@ -7,7 +7,7 @@ import java.sql.SQLException;
|
|||||||
public class ConnectionDb {
|
public class ConnectionDb {
|
||||||
private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram";
|
private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram";
|
||||||
private static final String USERNAME = "postgres";
|
private static final String USERNAME = "postgres";
|
||||||
private static final String PASSWORD = "Partow@1384";
|
private static final String PASSWORD = "124postpass";
|
||||||
|
|
||||||
public ConnectionDb() {
|
public ConnectionDb() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.to.telegramfinalproject.UI;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
|
public class ChannelInfoController {
|
||||||
|
public void setChannelDataFromJson(ChatEntry entry, JSONObject data) {
|
||||||
|
// read name, status, bio, image url from JSON
|
||||||
|
// update labels/images accordingly
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.to.telegramfinalproject.UI;
|
|||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.geometry.Side;
|
import javafx.geometry.Side;
|
||||||
@@ -23,6 +24,7 @@ import org.to.telegramfinalproject.Client.Session;
|
|||||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
@@ -58,6 +60,8 @@ public class ChatPageController {
|
|||||||
private Label chatTitle; // contact/group title
|
private Label chatTitle; // contact/group title
|
||||||
@FXML
|
@FXML
|
||||||
private Label chatStatus; // last seen / online
|
private Label chatStatus; // last seen / online
|
||||||
|
@FXML
|
||||||
|
private HBox chatHeader; // Header
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button searchInChatButton; // magnifier button
|
private Button searchInChatButton; // magnifier button
|
||||||
@@ -84,11 +88,11 @@ public class ChatPageController {
|
|||||||
@FXML private VBox joinPane;
|
@FXML private VBox joinPane;
|
||||||
@FXML private VBox addContactPane;
|
@FXML private VBox addContactPane;
|
||||||
|
|
||||||
//For search handeling
|
// For search handling
|
||||||
@FXML private Button joinButton;
|
@FXML private Button joinButton;
|
||||||
@FXML private Button addContactButton;
|
@FXML private Button addContactButton;
|
||||||
|
|
||||||
//Handle View chat
|
// Handle View chat
|
||||||
@FXML private Button unblockBtn;
|
@FXML private Button unblockBtn;
|
||||||
@FXML private VBox readOnlyPane;
|
@FXML private VBox readOnlyPane;
|
||||||
@FXML private Label readOnlyLabel;
|
@FXML private Label readOnlyLabel;
|
||||||
@@ -124,6 +128,7 @@ public class ChatPageController {
|
|||||||
|
|
||||||
// Where your icons live
|
// Where your icons live
|
||||||
private static final String ICON_BASE = "/org/to/telegramfinalproject/Icons/";
|
private static final String ICON_BASE = "/org/to/telegramfinalproject/Icons/";
|
||||||
|
private static ChatPageController instance;
|
||||||
private ChatEntry currentChat;
|
private ChatEntry currentChat;
|
||||||
private UUID me;
|
private UUID me;
|
||||||
|
|
||||||
@@ -148,6 +153,14 @@ public class ChatPageController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChatPageController() {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatPageController getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
private static String str(org.json.JSONObject j, String k) {
|
private static String str(org.json.JSONObject j, String k) {
|
||||||
try { return (j.has(k) && !j.isNull(k)) ? j.getString(k) : ""; } catch (Exception e) { return ""; }
|
try { return (j.has(k) && !j.isNull(k)) ? j.getString(k) : ""; } catch (Exception e) { return ""; }
|
||||||
}
|
}
|
||||||
@@ -813,7 +826,6 @@ public void showChat(ChatEntry entry) {
|
|||||||
}
|
}
|
||||||
AvatarFX.circleClip(userAvatar, 36);
|
AvatarFX.circleClip(userAvatar, 36);
|
||||||
|
|
||||||
|
|
||||||
// حالت اولیه (بدون انتظار هدر)
|
// حالت اولیه (بدون انتظار هدر)
|
||||||
if ("channel".equalsIgnoreCase(entry.getType())) {
|
if ("channel".equalsIgnoreCase(entry.getType())) {
|
||||||
boolean canPostLocal = entry.isOwner() || entry.isAdmin()
|
boolean canPostLocal = entry.isOwner() || entry.isAdmin()
|
||||||
@@ -830,6 +842,8 @@ public void showChat(ChatEntry entry) {
|
|||||||
// حالا هدر بیاد، دوباره نهاییاش میکنیم
|
// حالا هدر بیاد، دوباره نهاییاش میکنیم
|
||||||
fetchAndRenderHeader(entry);
|
fetchAndRenderHeader(entry);
|
||||||
|
|
||||||
|
// === (3-dot menu + header click) ===
|
||||||
|
configureHeaderActions(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -883,16 +897,18 @@ public void showChat(ChatEntry entry) {
|
|||||||
public void showChat(ChatEntry entry, ChatViewMode mode) {
|
public void showChat(ChatEntry entry, ChatViewMode mode) {
|
||||||
this.currentChat = entry;
|
this.currentChat = entry;
|
||||||
|
|
||||||
// --- Header ---
|
// --- Header (fallback until server responds) ---
|
||||||
chatTitle.setText(entry.getName());
|
chatTitle.setText(entry.getName());
|
||||||
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
||||||
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
Image img = AvatarLocalResolver.load(entry.getImageUrl());
|
||||||
if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType());
|
if (img != null) userAvatar.setImage(img);
|
||||||
|
else setDefaultHeaderAvatarByType(entry.getType());
|
||||||
} else {
|
} else {
|
||||||
setDefaultHeaderAvatarByType(entry.getType());
|
setDefaultHeaderAvatarByType(entry.getType());
|
||||||
}
|
}
|
||||||
AvatarFX.circleClip(userAvatar, 36);
|
AvatarFX.circleClip(userAvatar, 36);
|
||||||
|
|
||||||
|
// Fetch live header info from server
|
||||||
fetchAndRenderHeader(entry);
|
fetchAndRenderHeader(entry);
|
||||||
|
|
||||||
// --- Messages ---
|
// --- Messages ---
|
||||||
@@ -900,13 +916,222 @@ public void showChat(ChatEntry entry) {
|
|||||||
loadMessages(entry);
|
loadMessages(entry);
|
||||||
markAsRead(entry);
|
markAsRead(entry);
|
||||||
|
|
||||||
// --- حالت UI (Composer / Join / Add Contact) ---
|
// --- UI mode (Composer / Join / Add Contact) ---
|
||||||
applyMode(mode);
|
applyMode(mode);
|
||||||
|
|
||||||
// فوکوس اگر در حالت نرمال هستیم
|
// Focus if normal mode
|
||||||
if (mode == ChatViewMode.NORMAL) {
|
if (mode == ChatViewMode.NORMAL) {
|
||||||
Platform.runLater(() -> messageInput.requestFocus());
|
Platform.runLater(() -> messageInput.requestFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wire up menu + header click
|
||||||
|
configureHeaderActions(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureHeaderActions(ChatEntry entry) {
|
||||||
|
moreMenu.getItems().clear();
|
||||||
|
|
||||||
|
switch (entry.getType().toLowerCase(Locale.ROOT)) {
|
||||||
|
case "private" -> {
|
||||||
|
MenuItem viewProfile = new MenuItem("View profile");
|
||||||
|
viewProfile.setOnAction(e -> openInfoScene(entry));
|
||||||
|
|
||||||
|
MenuItem deleteChat = new MenuItem("Delete chat");
|
||||||
|
deleteChat.setStyle("-fx-text-fill: red;");
|
||||||
|
deleteChat.setOnAction(e -> deleteChatButton(entry));
|
||||||
|
|
||||||
|
moreMenu.getItems().addAll(viewProfile, deleteChat);
|
||||||
|
}
|
||||||
|
case "group" -> {
|
||||||
|
MenuItem viewGroup = new MenuItem("View group info");
|
||||||
|
viewGroup.setOnAction(e -> openInfoScene(entry));
|
||||||
|
|
||||||
|
MenuItem leaveGroup = new MenuItem("Leave group");
|
||||||
|
leaveGroup.setOnAction(e -> leaveGroupButton(entry));
|
||||||
|
|
||||||
|
moreMenu.getItems().addAll(viewGroup, leaveGroup);
|
||||||
|
}
|
||||||
|
case "channel" -> {
|
||||||
|
MenuItem viewChannel = new MenuItem("View channel info");
|
||||||
|
viewChannel.setOnAction(e -> openInfoScene(entry));
|
||||||
|
|
||||||
|
MenuItem leaveChannel = new MenuItem("Leave channel");
|
||||||
|
leaveChannel.setOnAction(e -> leaveChannelButton(entry));
|
||||||
|
|
||||||
|
moreMenu.getItems().addAll(viewChannel, leaveChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clicking header (but not the 3-dot) → open info scene
|
||||||
|
chatHeader.setOnMouseClicked(e -> {
|
||||||
|
if (!moreButton.equals(e.getTarget())) {
|
||||||
|
openInfoScene(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openInfoScene(ChatEntry entry) {
|
||||||
|
JSONObject req = new JSONObject();
|
||||||
|
|
||||||
|
switch (entry.getType().toLowerCase()) {
|
||||||
|
case "private" -> {
|
||||||
|
// First ask server for the target_id of this private chat
|
||||||
|
JSONObject targetReq = new JSONObject()
|
||||||
|
.put("action", "get_private_chat_target")
|
||||||
|
.put("chat_id", entry.getId().toString());
|
||||||
|
|
||||||
|
JSONObject targetResp = ActionHandler.sendWithResponse(targetReq);
|
||||||
|
if (targetResp == null || !"success".equalsIgnoreCase(targetResp.optString("status"))) {
|
||||||
|
Platform.runLater(() ->
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
targetResp != null ? targetResp.optString("message") : "Failed to fetch chat target.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetId = targetResp.optJSONObject("data").optString("target_id", null);
|
||||||
|
if (targetId == null || targetId.isBlank()) {
|
||||||
|
Platform.runLater(() ->
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"No target user found for this chat.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
req.put("action", "view_profile")
|
||||||
|
.put("target_id", targetId);
|
||||||
|
}
|
||||||
|
case "group" -> {
|
||||||
|
req.put("action", "view_group")
|
||||||
|
.put("group_id", entry.getId().toString());
|
||||||
|
}
|
||||||
|
case "channel" -> {
|
||||||
|
req.put("action", "view_channel")
|
||||||
|
.put("channel_id", entry.getId().toString());
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
Platform.runLater(() ->
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Unsupported chat type: " + entry.getType(),
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
Platform.runLater(() ->
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
resp != null ? resp.optString("message") : "Server not responding.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
Platform.runLater(() ->
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Malformed server response.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader;
|
||||||
|
Node overlay = null;
|
||||||
|
|
||||||
|
switch (entry.getType().toLowerCase()) {
|
||||||
|
case "private" -> {
|
||||||
|
loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/user_info.fxml"));
|
||||||
|
overlay = loader.load();
|
||||||
|
UserInfoController c = loader.getController();
|
||||||
|
c.setProfileDataFromJson(entry, data);
|
||||||
|
}
|
||||||
|
case "group" -> {
|
||||||
|
loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/group_info.fxml"));
|
||||||
|
overlay = loader.load();
|
||||||
|
GroupInfoController c = loader.getController();
|
||||||
|
c.setGroupDataFromJson(entry, data);
|
||||||
|
}
|
||||||
|
case "channel" -> {
|
||||||
|
loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/channel_info.fxml"));
|
||||||
|
overlay = loader.load();
|
||||||
|
ChannelInfoController c = loader.getController();
|
||||||
|
c.setChannelDataFromJson(entry, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay != null) {
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Could not load info scene.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteChatButton(ChatEntry entry) {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "delete_chat")
|
||||||
|
.put("receiver_id", entry.getId().toString())
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
// TODO: close chat + refresh sidebar
|
||||||
|
MainController.getInstance().closeCurrentChat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leaveGroupButton(ChatEntry entry) {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "leave_group")
|
||||||
|
.put("group_id", entry.getId().toString())
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
MainController.getInstance().closeCurrentChat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void leaveChannelButton(ChatEntry entry) {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "leave_channel")
|
||||||
|
.put("channel_id", entry.getId().toString())
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
MainController.getInstance().closeCurrentChat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMessages(ChatEntry entry) {
|
private void loadMessages(ChatEntry entry) {
|
||||||
@@ -1845,7 +2070,7 @@ private void addBubble(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String userStatusText(boolean online, String lastSeenIso) {
|
public String userStatusText(boolean online, String lastSeenIso) {
|
||||||
if (online) return "online";
|
if (online) return "online";
|
||||||
|
|
||||||
LocalDateTime ts = parseWhen(lastSeenIso);
|
LocalDateTime ts = parseWhen(lastSeenIso);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.to.telegramfinalproject.UI;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
|
public class GroupInfoController {
|
||||||
|
public void setGroupDataFromJson(ChatEntry entry, JSONObject data) {
|
||||||
|
// read name, status, bio, image url from JSON
|
||||||
|
// update labels/images accordingly
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,9 +41,6 @@ public class MainController {
|
|||||||
private boolean blockedByMeFlag = false;
|
private boolean blockedByMeFlag = false;
|
||||||
private boolean blockedMeFlag = false;
|
private boolean blockedMeFlag = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private enum SearchMode {
|
private enum SearchMode {
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
CHAT
|
CHAT
|
||||||
@@ -635,6 +632,37 @@ public class MainController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void closeCurrentChat() {
|
||||||
|
// 1. Clear the chat area
|
||||||
|
chatDisplayArea.getChildren().clear();
|
||||||
|
|
||||||
|
// 2. Re-add the placeholder label
|
||||||
|
placeholderLabel.setVisible(true);
|
||||||
|
placeholderLabel.setManaged(true);
|
||||||
|
placeholderLabel.setText("Select a chat to start messaging");
|
||||||
|
|
||||||
|
chatDisplayArea.getChildren().add(placeholderLabel);
|
||||||
|
|
||||||
|
// 3. Ask the chat list to refresh
|
||||||
|
MainController.getInstance().refreshChatListUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showAlert(String title, String message, Alert.AlertType type) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Alert alert = new Alert(type);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
|
||||||
|
// Tie it to the main window so it centers nicely
|
||||||
|
if (mainRoot.getScene() != null && mainRoot.getScene().getWindow() != null) {
|
||||||
|
alert.initOwner(mainRoot.getScene().getWindow());
|
||||||
|
}
|
||||||
|
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Search UI state =====
|
// ===== Search UI state =====
|
||||||
private final java.util.List<SearchResult> searchBacking = new java.util.ArrayList<>();
|
private final java.util.List<SearchResult> searchBacking = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
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.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
|
public class UserInfoController {
|
||||||
|
|
||||||
|
@FXML private VBox profileCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton;
|
||||||
|
|
||||||
|
@FXML private ImageView profileImage;
|
||||||
|
@FXML private Label profileName;
|
||||||
|
@FXML private Label profileStatus;
|
||||||
|
@FXML private VBox bioBlock;
|
||||||
|
@FXML private Label userBio;
|
||||||
|
@FXML private VBox usernameBlock;
|
||||||
|
@FXML private Label userId;
|
||||||
|
|
||||||
|
@FXML private Button moreButton;
|
||||||
|
@FXML private ContextMenu moreMenu;
|
||||||
|
@FXML private MenuItem deleteChatItem;
|
||||||
|
@FXML private MenuItem blockItem;
|
||||||
|
@FXML private MenuItem unblockItem;
|
||||||
|
|
||||||
|
private String otherUserId; // internal_uuid of the other user
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
closeButton.setOnAction(e ->
|
||||||
|
MainController.getInstance().closeOverlay(profileCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e ->
|
||||||
|
MainController.getInstance().closeOverlay(profileCard.getParent()));
|
||||||
|
|
||||||
|
// Show menu manually
|
||||||
|
moreButton.setOnAction(e -> {
|
||||||
|
if (moreMenu != null) {
|
||||||
|
moreMenu.show(moreButton, javafx.geometry.Side.BOTTOM, 0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide menu when clicking outside
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (moreButton.getScene() != null) {
|
||||||
|
moreButton.getScene().addEventFilter(
|
||||||
|
javafx.scene.input.MouseEvent.MOUSE_PRESSED, ev -> {
|
||||||
|
if (moreMenu.isShowing()
|
||||||
|
&& !moreButton.localToScreen(moreButton.getBoundsInLocal())
|
||||||
|
.contains(ev.getScreenX(), ev.getScreenY())) {
|
||||||
|
moreMenu.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteChatItem.setOnAction(e -> handleDeleteChat());
|
||||||
|
blockItem.setOnAction(e -> handleBlock());
|
||||||
|
unblockItem.setOnAction(e -> handleUnblock());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Backend JSON → UI */
|
||||||
|
public void setProfileDataFromJson(ChatEntry entry, JSONObject data) {
|
||||||
|
// --- Profile name ---
|
||||||
|
String name = data.optString("profile_name", entry.getName());
|
||||||
|
profileName.setText(name);
|
||||||
|
|
||||||
|
// --- Status ---
|
||||||
|
String status = ChatPageController.getInstance().userStatusText(
|
||||||
|
data.optBoolean("is_online", false),
|
||||||
|
data.optString("last_seen", null)
|
||||||
|
);
|
||||||
|
profileStatus.setText(status);
|
||||||
|
|
||||||
|
// --- Bio ---
|
||||||
|
String bio = data.optString("bio", "");
|
||||||
|
if (bio != null && !bio.isBlank()) {
|
||||||
|
userBio.setText(bio);
|
||||||
|
bioBlock.setVisible(true);
|
||||||
|
bioBlock.setManaged(true);
|
||||||
|
} else {
|
||||||
|
bioBlock.setVisible(false);
|
||||||
|
bioBlock.setManaged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Username ---
|
||||||
|
String username = data.optString("user_id", "");
|
||||||
|
if (username != null && !username.isBlank()) {
|
||||||
|
userId.setText("@" + username);
|
||||||
|
usernameBlock.setVisible(true);
|
||||||
|
usernameBlock.setManaged(true);
|
||||||
|
} else {
|
||||||
|
usernameBlock.setVisible(false);
|
||||||
|
usernameBlock.setManaged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Profile picture ---
|
||||||
|
String imgUrl = data.optString("image_url", "");
|
||||||
|
if (imgUrl != null && !imgUrl.isBlank()) {
|
||||||
|
try {
|
||||||
|
Image img = AvatarLocalResolver.load(imgUrl);
|
||||||
|
if (img != null) profileImage.setImage(img);
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
} else {
|
||||||
|
profileImage.setImage(
|
||||||
|
new Image(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Other user ID (needed for block/delete) ---
|
||||||
|
this.otherUserId = data.optString("other_user_id", entry.getId().toString());
|
||||||
|
|
||||||
|
// --- Block/Unblock ---
|
||||||
|
boolean blocked = data.optBoolean("blocked", false)
|
||||||
|
|| data.optBoolean("is_blocked", false)
|
||||||
|
|| data.optBoolean("blocked_by_me", false);
|
||||||
|
updateBlockMenu(blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDeleteChat() {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "delete_chat")
|
||||||
|
.put("receiver_id", otherUserId)
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
MainController.getInstance().closeOverlay(profileCard.getParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBlock() {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "block_user")
|
||||||
|
.put("target_id", otherUserId)
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
updateBlockMenu(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUnblock() {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "unblock_user")
|
||||||
|
.put("target_id", otherUserId)
|
||||||
|
.put("viewer_id", Session.getUserUUID());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
updateBlockMenu(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBlockMenu(boolean isBlocked) {
|
||||||
|
blockItem.setVisible(!isBlocked);
|
||||||
|
unblockItem.setVisible(isBlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 83 KiB |
@@ -1079,3 +1079,56 @@
|
|||||||
-fx-border-color: red;
|
-fx-border-color: red;
|
||||||
-fx-border-radius: 6;
|
-fx-border-radius: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === User Info (Dark) === */
|
||||||
|
.user-info-card {
|
||||||
|
-fx-background-color: #1f1f1f;
|
||||||
|
-fx-background-radius: 8;
|
||||||
|
-fx-padding: 16;
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.5), 12, 0, 0, 3);
|
||||||
|
}
|
||||||
|
.user-info-title {
|
||||||
|
-fx-font-size: 16px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #f2f2f2;
|
||||||
|
}
|
||||||
|
.user-info-name {
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #fff;
|
||||||
|
}
|
||||||
|
.user-info-status {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: #aaa;
|
||||||
|
}
|
||||||
|
.user-info-main {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: #f0f0f0;
|
||||||
|
}
|
||||||
|
.user-info-sub {
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-text-fill: #999;
|
||||||
|
}
|
||||||
|
.user-info-separator {
|
||||||
|
-fx-background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Shared buttons === */
|
||||||
|
.danger-button {
|
||||||
|
-fx-background-color: #e74c3c;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
-fx-padding: 4 12;
|
||||||
|
}
|
||||||
|
.danger-button:hover {
|
||||||
|
-fx-background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Profile image === */
|
||||||
|
.profile-picture {
|
||||||
|
-fx-background-color: #2c2c2c;
|
||||||
|
-fx-background-radius: 50%;
|
||||||
|
-fx-border-color: #444;
|
||||||
|
-fx-border-radius: 50%;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1070,3 +1070,56 @@
|
|||||||
-fx-border-color: red;
|
-fx-border-color: red;
|
||||||
-fx-border-radius: 6;
|
-fx-border-radius: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === User Info (Light) === */
|
||||||
|
.user-info-card {
|
||||||
|
-fx-background-color: #ffffff;
|
||||||
|
-fx-background-radius: 8;
|
||||||
|
-fx-padding: 16;
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.1), 8, 0, 0, 2);
|
||||||
|
}
|
||||||
|
.user-info-title {
|
||||||
|
-fx-font-size: 16px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #111;
|
||||||
|
}
|
||||||
|
.user-info-name {
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #111;
|
||||||
|
}
|
||||||
|
.user-info-status {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: #555;
|
||||||
|
}
|
||||||
|
.user-info-main {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: #222;
|
||||||
|
}
|
||||||
|
.user-info-sub {
|
||||||
|
-fx-font-size: 11px;
|
||||||
|
-fx-text-fill: #888;
|
||||||
|
}
|
||||||
|
.user-info-separator {
|
||||||
|
-fx-background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Shared buttons === */
|
||||||
|
.danger-button {
|
||||||
|
-fx-background-color: #e74c3c;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
-fx-padding: 4 12;
|
||||||
|
}
|
||||||
|
.danger-button:hover {
|
||||||
|
-fx-background-color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Profile image === */
|
||||||
|
.profile-picture {
|
||||||
|
-fx-background-color: #f0f0f0;
|
||||||
|
-fx-background-radius: 50%;
|
||||||
|
-fx-border-color: #ddd;
|
||||||
|
-fx-border-radius: 50%;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.scene.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.ChannelInfoController"
|
||||||
|
prefHeight="400.0" prefWidth="600.0">
|
||||||
|
|
||||||
|
</AnchorPane>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import java.util.*?>
|
||||||
|
<?import javafx.scene.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.GroupInfoController"
|
||||||
|
prefHeight="400.0" prefWidth="600.0">
|
||||||
|
|
||||||
|
</AnchorPane>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.UserInfoController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Dimmed background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Profile card -->
|
||||||
|
<VBox fx:id="profileCard" styleClass="profile-card" spacing="16"
|
||||||
|
prefWidth="360" maxWidth="360"
|
||||||
|
prefHeight="480" maxHeight="480">
|
||||||
|
|
||||||
|
<!-- ===== Header: User Info (left) + actions (right) ===== -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="User Info" styleClass="user-info-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/> <!-- pushes buttons to right -->
|
||||||
|
|
||||||
|
<!-- More / overflow -->
|
||||||
|
<Button fx:id="moreButton" styleClass="icon-button">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitWidth="18" fitHeight="18" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/more_dark.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
<contextMenu>
|
||||||
|
<ContextMenu fx:id="moreMenu">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="deleteChatItem" text="Delete chat" style="-fx-text-fill: red;">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/delete_red.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem fx:id="blockItem" text="Block" style="-fx-text-fill: red;">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/block_red.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem fx:id="unblockItem" text="Unblock" visible="false" style="-fx-text-fill: red;">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/block_red.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</MenuItem>
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Close -->
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Profile: picture + name/status -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="12">
|
||||||
|
<ImageView fx:id="profileImage" fitWidth="80" fitHeight="80" preserveRatio="true"
|
||||||
|
styleClass="profile-picture"/>
|
||||||
|
<VBox alignment="CENTER_LEFT" spacing="4">
|
||||||
|
<Label fx:id="profileName" text="User Name" styleClass="profile-name"/>
|
||||||
|
<Label fx:id="profileStatus" text="last seen recently" styleClass="profile-status"/>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- Info blocks -->
|
||||||
|
<HBox alignment="TOP_LEFT">
|
||||||
|
<Pane prefWidth="30"/> <!-- left spacer -->
|
||||||
|
|
||||||
|
<VBox spacing="16" styleClass="info-blocks">
|
||||||
|
|
||||||
|
<!-- Bio -->
|
||||||
|
<VBox fx:id="bioBlock" spacing="2">
|
||||||
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/bio_dark.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<Label fx:id="userBio" wrapText="true" styleClass="profile-info-main"/>
|
||||||
|
</HBox>
|
||||||
|
<Label text="Bio" styleClass="profile-info-sub"/>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<!-- Username -->
|
||||||
|
<VBox fx:id="usernameBlock" spacing="2">
|
||||||
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
<ImageView fitWidth="16" fitHeight="16" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/user_id_dark.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<Label fx:id="userId" styleClass="profile-info-main"/>
|
||||||
|
</HBox>
|
||||||
|
<Label text="Username" styleClass="profile-info-sub"/>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 207 B |
|
After Width: | Height: | Size: 892 B |
|
After Width: | Height: | Size: 560 B |
|
After Width: | Height: | Size: 474 B |