Merge remote-tracking branch 'origin/Main-UI' into Main-UI
This commit is contained in:
@@ -35,6 +35,13 @@ public class AddMembersController {
|
||||
private String groupDisplayId;
|
||||
private File groupImageFile;
|
||||
|
||||
public enum Mode {
|
||||
CREATE, // from New Group
|
||||
ADD // from Group Info
|
||||
}
|
||||
|
||||
private Mode mode = Mode.CREATE; // default
|
||||
|
||||
// انتخابها
|
||||
private final Set<Contact> selectedContacts = new HashSet<>();
|
||||
// همهی کانتکتها
|
||||
@@ -42,7 +49,7 @@ public class AddMembersController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// اسکرول نرم
|
||||
// Smooth scroll feel
|
||||
contactsScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
@@ -81,21 +88,28 @@ public class AddMembersController {
|
||||
renderContacts(allContacts);
|
||||
}
|
||||
|
||||
// این متد را NewGroupController بعد از ساخت گروه صدا بزند
|
||||
// Called when creating a new group from sidebar
|
||||
public void setGroupInfo(UUID internalId, String groupName, String displayId, File groupImageFile) {
|
||||
this.groupInternalId = internalId;
|
||||
this.groupName = groupName;
|
||||
this.groupDisplayId = displayId;
|
||||
this.groupImageFile = groupImageFile;
|
||||
this.mode = Mode.CREATE;
|
||||
updateActionButtonText();
|
||||
}
|
||||
|
||||
// — اگر هنوز امضای قدیمی را صدا میزنی، موقتاً این اوِرلود هست (displayId را میگیرد اما internal_id لازم است) —
|
||||
public void setGroupInfo(String groupName, String groupId, File groupImageFile) {
|
||||
// ⚠️ فقط برای سازگاری موقت؛ حتماً NewGroupController را طوری بهروزرسانی کن
|
||||
// که internal_id را بدهد (امضای بالایی).
|
||||
// Called when adding to an existing group
|
||||
public void setGroupForAdd(UUID internalId, String groupName) {
|
||||
this.groupInternalId = internalId;
|
||||
this.groupName = groupName;
|
||||
this.groupDisplayId = groupId;
|
||||
this.groupImageFile = groupImageFile;
|
||||
this.mode = Mode.ADD;
|
||||
updateActionButtonText();
|
||||
}
|
||||
|
||||
private void updateActionButtonText() {
|
||||
if (createButton != null) {
|
||||
createButton.setText(mode == Mode.CREATE ? "Create" : "Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadContactsFromSession() {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class BlockedUsersController {
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateBackIcon(newVal));
|
||||
updateBackIcon(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// اسکرول نرم
|
||||
// Smooth scroll feel
|
||||
blockedList.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
|
||||
@@ -1132,28 +1132,38 @@ public class ChatPageController {
|
||||
}
|
||||
|
||||
private void configureHeaderActions(ChatEntry entry) {
|
||||
chatMoreMenu.getItems().clear();
|
||||
// Hide everything by default
|
||||
archiveItem.setVisible(false);
|
||||
viewProfileItem.setVisible(false);
|
||||
deleteChatItem.setVisible(false);
|
||||
viewGroupItem.setVisible(false);
|
||||
leaveGroupItem.setVisible(false);
|
||||
viewChannelItem.setVisible(false);
|
||||
leaveChannelItem.setVisible(false);
|
||||
|
||||
switch (entry.getType().toLowerCase(Locale.ROOT)) {
|
||||
case "private" -> {
|
||||
archiveItem.setVisible(true);
|
||||
viewProfileItem.setVisible(true);
|
||||
deleteChatItem.setVisible(true);
|
||||
|
||||
archiveItem.setOnAction(e -> toggleArchive(entry));
|
||||
viewProfileItem.setOnAction(e -> openInfoScene(entry));
|
||||
deleteChatItem.setOnAction(e -> deleteChatButton(entry));
|
||||
chatMoreMenu.getItems().addAll(archiveItem, viewProfileItem, deleteChatItem);
|
||||
}
|
||||
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));
|
||||
chatMoreMenu.getItems().addAll(viewGroup, leaveGroup);
|
||||
viewGroupItem.setVisible(true);
|
||||
leaveGroupItem.setVisible(true);
|
||||
|
||||
viewGroupItem.setOnAction(e -> openInfoScene(entry));
|
||||
leaveGroupItem.setOnAction(e -> leaveGroupButton(entry));
|
||||
}
|
||||
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));
|
||||
chatMoreMenu.getItems().addAll(viewChannel, leaveChannel);
|
||||
viewChannelItem.setVisible(true);
|
||||
leaveChannelItem.setVisible(true);
|
||||
|
||||
viewChannelItem.setOnAction(e -> openInfoScene(entry));
|
||||
leaveChannelItem.setOnAction(e -> leaveChannelButton(entry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1205,7 +1215,8 @@ public class ChatPageController {
|
||||
}
|
||||
case "group" -> {
|
||||
req.put("action", "view_group")
|
||||
.put("group_id", entry.getId().toString());
|
||||
.put("group_id", entry.getId().toString())
|
||||
.put("viewer_id", Session.getUserUUID());
|
||||
}
|
||||
case "channel" -> {
|
||||
req.put("action", "view_channel")
|
||||
@@ -3615,6 +3626,14 @@ public class ChatPageController {
|
||||
if (deleteChatItem != null && deleteChatItem.getGraphic() instanceof ImageView iv) {
|
||||
iv.setImage(loadIcon("delete_red.png")); // stays red in both themes
|
||||
}
|
||||
|
||||
if (viewGroupItem != null && viewGroupItem.getGraphic() instanceof ImageView iv) {
|
||||
iv.setImage(loadIcon("group" + suffix));
|
||||
}
|
||||
|
||||
if (viewChannelItem != null && viewChannelItem.getGraphic() instanceof ImageView iv) {
|
||||
iv.setImage(loadIcon("group" + suffix));
|
||||
}
|
||||
}
|
||||
|
||||
// ChatPageController
|
||||
|
||||
@@ -1,11 +1,325 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GroupInfoController {
|
||||
|
||||
@FXML private VBox groupCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
@FXML private ImageView groupImage;
|
||||
@FXML private Label groupName;
|
||||
@FXML private Label memberCount;
|
||||
|
||||
@FXML private Button infoMoreButton;
|
||||
@FXML private ContextMenu infoMoreMenu;
|
||||
@FXML private MenuItem addMemberItem;
|
||||
@FXML private MenuItem manageGroupItem;
|
||||
@FXML private MenuItem deleteGroupItem;
|
||||
@FXML private ImageView moreIcon;
|
||||
|
||||
@FXML private VBox membersList;
|
||||
@FXML private Label membersHeader;
|
||||
@FXML private Button addMemberButton;
|
||||
@FXML private ImageView membersIcon;
|
||||
@FXML private ScrollPane membersScroll;
|
||||
|
||||
private String groupId; // the UUID of this group
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(groupCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(groupCard.getParent()));
|
||||
|
||||
infoMoreButton.setOnAction(e -> {
|
||||
if (infoMoreMenu != null) infoMoreMenu.show(infoMoreButton, javafx.geometry.Side.BOTTOM, 0, 0);
|
||||
});
|
||||
|
||||
if (manageGroupItem != null) {
|
||||
manageGroupItem.setOnAction(e -> openManageGroupScene());
|
||||
}
|
||||
|
||||
deleteGroupItem.setOnAction(e -> handleDeleteGroup());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (groupCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(groupCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateIcons(newVal);
|
||||
});
|
||||
|
||||
updateIcons(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// Smooth scroll feel
|
||||
membersScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
membersScroll.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
ScrollBar vBar = (ScrollBar) membersScroll.lookup(".scroll-bar:vertical");
|
||||
if (vBar != null) {
|
||||
membersScroll.setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
double newValue = vBar.getValue() - deltaY;
|
||||
vBar.setValue(Math.max(0, Math.min(newValue, 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (addMemberButton != null) {
|
||||
addMemberButton.setOnAction(e -> openAddMemberScene());
|
||||
}
|
||||
if (addMemberItem != null) {
|
||||
addMemberItem.setOnAction(e -> openAddMemberScene());
|
||||
}
|
||||
}
|
||||
|
||||
public void setGroupDataFromJson(ChatEntry entry, JSONObject data) {
|
||||
// read name, status, bio, image url from JSON
|
||||
// update labels/images accordingly
|
||||
this.groupId = data.optString("internal_uuid", entry.getId().toString());
|
||||
|
||||
groupName.setText(data.optString("group_name", entry.getName()));
|
||||
memberCount.setText(data.optInt("member_count", 0) + " members");
|
||||
|
||||
// --- Role-based UI ---
|
||||
String myRole = data.optString("my_role", "member").toLowerCase();
|
||||
|
||||
// Delete group → only owner
|
||||
deleteGroupItem.setVisible("owner".equals(myRole));
|
||||
|
||||
// Add member (button + menu item) → owner or admin
|
||||
boolean canAdd = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
addMemberButton.setVisible(canAdd);
|
||||
addMemberButton.setManaged(canAdd);
|
||||
if (addMemberItem != null) {
|
||||
addMemberItem.setVisible(canAdd);
|
||||
}
|
||||
|
||||
// More menu (3-dot) → hide entirely for plain members
|
||||
boolean showMore = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
infoMoreButton.setVisible(showMore);
|
||||
infoMoreButton.setManaged(showMore);
|
||||
|
||||
// --- Group picture ---
|
||||
String imgUrl = data.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
try {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) groupImage.setImage(img);
|
||||
} catch (Exception ignore) {}
|
||||
} else {
|
||||
groupImage.setImage(
|
||||
new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_group_profile.png"))
|
||||
);
|
||||
}
|
||||
|
||||
// --- Members list ---
|
||||
membersList.getChildren().clear();
|
||||
var arr = data.optJSONArray("members");
|
||||
if (arr != null) {
|
||||
membersHeader.setText(arr.length() + " MEMBERS");
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject m = arr.getJSONObject(i);
|
||||
addMemberRow(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberRow(JSONObject m) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// === Avatar ===
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
AvatarFX.circleClip(avatar, 36);
|
||||
|
||||
String imgUrl = m.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// === Name + Status ===
|
||||
VBox nameBox = new VBox(2);
|
||||
Label name = new Label(m.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
// Status = online / last seen recently
|
||||
String status;
|
||||
if (m.optBoolean("is_online", false)) {
|
||||
status = "online";
|
||||
} else {
|
||||
status = ChatPageController.getInstance().userStatusText(
|
||||
false,
|
||||
m.optString("last_seen", null)
|
||||
);
|
||||
}
|
||||
Label statusLbl = new Label(status);
|
||||
statusLbl.getStyleClass().add("member-status");
|
||||
|
||||
nameBox.getChildren().addAll(name, statusLbl);
|
||||
|
||||
// === Role (owner/admin/member) ===
|
||||
Label role = new Label();
|
||||
String roleStr = m.optString("role", "");
|
||||
if (!roleStr.isBlank()) {
|
||||
role.setText(roleStr.toLowerCase());
|
||||
role.getStyleClass().add("member-role");
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, nameBox, spacer, role);
|
||||
membersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void openManageGroupScene() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_group")
|
||||
.put("group_id", groupId)
|
||||
.put("viewer_id", Session.getUserUUID());
|
||||
|
||||
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 = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_group.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageGroupController controller = loader.getController();
|
||||
controller.setGroupData(data); // pass the full JSON
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Manage Group scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Error while fetching group info: " + e.getMessage(),
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openAddMemberScene() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddMembersController controller = loader.getController();
|
||||
controller.setGroupForAdd(UUID.fromString(groupId), String.valueOf(groupName));
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Add Member scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeleteGroup() {
|
||||
System.out.println("Deleting group...");
|
||||
// TODO: implement backend call
|
||||
}
|
||||
|
||||
private void updateIcons(boolean dark) {
|
||||
String suffix = dark ? "_light.png" : "_dark.png";
|
||||
|
||||
moreIcon.setImage(loadImage(ICON_PATH + "more" + suffix));
|
||||
membersIcon.setImage(loadImage(ICON_PATH + "group" + suffix));
|
||||
addMemberButton.setGraphic(makeIcon(ICON_PATH + "add_member" + suffix));
|
||||
membersIcon.setImage(loadImage(ICON_PATH + "group_member" + suffix));
|
||||
|
||||
}
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) return null;
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
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.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ManageGroupController {
|
||||
|
||||
@FXML private VBox manageGroupCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton, changePicButton, cancelButton, saveButton;
|
||||
@FXML private ImageView groupImage;
|
||||
@FXML private TextField groupNameField;
|
||||
@FXML private Label adminCount, memberCount;
|
||||
@FXML private TextField groupIdField;
|
||||
@FXML private Button manageAdminsButton;
|
||||
@FXML private Button manageMembersButton;
|
||||
|
||||
private String groupId; // Internal UUID of the current group
|
||||
private String originalName;
|
||||
private String originalGroupId;
|
||||
private String originalImageUrl;
|
||||
private File selectedImageFile;
|
||||
|
||||
private JSONObject data;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
|
||||
changePicButton.setOnAction(e -> choosePicture());
|
||||
saveButton.setOnAction(e -> saveChanges());
|
||||
|
||||
manageAdminsButton.setOnAction(e -> openManageAdminsScene());
|
||||
manageMembersButton.setOnAction(e -> openManageMembersScene(data));
|
||||
}
|
||||
|
||||
private void openManageAdminsScene() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_admins.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
// ManageAdminsController controller = loader.getController();
|
||||
// controller.setGroupId(UUID.fromString(groupId));
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Admins scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void openManageMembersScene(JSONObject data) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_members.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageMembersController controller = loader.getController();
|
||||
// Pass groupId and members list from the JSON data
|
||||
controller.setGroupData(
|
||||
data.optString("internal_uuid"),
|
||||
data.optJSONArray("members")
|
||||
);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Members scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void setGroupData(JSONObject data) {
|
||||
this.data = data;
|
||||
this.groupId = data.optString("internal_uuid");
|
||||
|
||||
originalName = data.optString("group_name", "");
|
||||
originalGroupId = data.optString("group_id", "");
|
||||
originalImageUrl = data.optString("image_url", "");
|
||||
|
||||
groupNameField.setText(originalName);
|
||||
groupIdField.setText(originalGroupId);
|
||||
|
||||
if (!originalImageUrl.isBlank()) {
|
||||
groupImage.setImage(new Image(originalImageUrl, true));
|
||||
}
|
||||
|
||||
adminCount.setText(String.valueOf(countRole(data, "admin")));
|
||||
memberCount.setText(String.valueOf(countRole(data, "member")));
|
||||
}
|
||||
|
||||
private int countRole(JSONObject groupData, String role) {
|
||||
var arr = groupData.optJSONArray("members");
|
||||
if (arr == null) return 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
if (role.equalsIgnoreCase(arr.getJSONObject(i).optString("role"))) count++;
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
private void choosePicture() {
|
||||
FileChooser fc = new FileChooser();
|
||||
fc.setTitle("Select group picture");
|
||||
selectedImageFile = fc.showOpenDialog(manageGroupCard.getScene().getWindow());
|
||||
if (selectedImageFile != null) {
|
||||
groupImage.setImage(new Image(selectedImageFile.toURI().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
String newName = groupNameField.getText().trim();
|
||||
String newGroupId = groupIdField.getText().trim(); // make sure you added this field
|
||||
String newImageUrl = (selectedImageFile != null)
|
||||
? selectedImageFile.toURI().toString()
|
||||
: originalImageUrl;
|
||||
|
||||
// Check if anything actually changed
|
||||
boolean changed =
|
||||
!Objects.equals(originalName, newName) ||
|
||||
!Objects.equals(originalGroupId, newGroupId) ||
|
||||
!Objects.equals(originalImageUrl, newImageUrl);
|
||||
|
||||
if (!changed) {
|
||||
// Nothing changed → just close overlay
|
||||
MainController.getInstance().closeOverlay(manageGroupCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
// Build request with all fields (server expects them)
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "edit_group_info")
|
||||
.put("group_id", groupId) // internal_uuid
|
||||
.put("new_group_id", newGroupId) // display id
|
||||
.put("name", newName);
|
||||
|
||||
if (newImageUrl != null && !newImageUrl.isBlank()) {
|
||||
req.put("image_url", newImageUrl);
|
||||
} else {
|
||||
req.put("image_url", JSONObject.NULL);
|
||||
}
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
MainController.getInstance().closeOverlay(manageGroupCard.getParent());
|
||||
} else {
|
||||
String msg = (resp != null)
|
||||
? resp.optString("message", "Failed to update group")
|
||||
: "No response from server";
|
||||
Alert a = new Alert(Alert.AlertType.ERROR, msg, ButtonType.OK);
|
||||
a.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageMembersController {
|
||||
|
||||
@FXML private VBox membersCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Button addMembersButton;
|
||||
@FXML private ScrollPane membersScroll;
|
||||
@FXML private VBox membersList;
|
||||
|
||||
private String groupId;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
|
||||
addMembersButton.setOnAction(e -> {
|
||||
// open add members overlay
|
||||
openAddMembersOverlay(groupId);
|
||||
});
|
||||
|
||||
// Smooth scroll feel
|
||||
membersScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
membersScroll.setPannable(true);
|
||||
membersScroll.setFitToWidth(true);
|
||||
membersScroll.setFitToHeight(false);
|
||||
membersScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
membersScroll.setVvalue(membersScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void setGroupData(String groupId, JSONArray members) {
|
||||
this.groupId = groupId;
|
||||
membersList.getChildren().clear();
|
||||
|
||||
// Convert safely into JSONObject list
|
||||
java.util.List<JSONObject> parsed = new java.util.ArrayList<>();
|
||||
|
||||
for (int i = 0; i < members.length(); i++) {
|
||||
Object raw = members.get(i);
|
||||
|
||||
if (raw instanceof JSONObject obj) {
|
||||
parsed.add(obj);
|
||||
} else if (raw instanceof java.util.Map<?, ?> map) {
|
||||
parsed.add(new JSONObject(map));
|
||||
} else {
|
||||
System.err.println("Skipping invalid member element at index " + i + ": " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: owners first, then others
|
||||
parsed.sort((a, b) -> {
|
||||
boolean aOwner = "owner".equalsIgnoreCase(a.optString("role"));
|
||||
boolean bOwner = "owner".equalsIgnoreCase(b.optString("role"));
|
||||
return Boolean.compare(!aOwner, !bOwner); // false < true → owner first
|
||||
});
|
||||
|
||||
// Add rows
|
||||
for (JSONObject m : parsed) {
|
||||
addMemberRow(m);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberRow(JSONObject m) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = m.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(m.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(
|
||||
m.optBoolean("is_online", false) ? "online"
|
||||
: "last seen recently"
|
||||
);
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Only non-owners can be removed
|
||||
String role = m.optString("role", "member");
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
Button removeBtn = new Button("Remove");
|
||||
removeBtn.getStyleClass().add("link-btn");
|
||||
removeBtn.setOnAction(e -> removeMember(m.optString("user_id"), row));
|
||||
row.getChildren().add(removeBtn);
|
||||
}
|
||||
|
||||
membersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void removeMember(String userId, HBox row) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "remove_member_from_group")
|
||||
.put("group_id", groupId) // must be the group's internal_uuid
|
||||
.put("user_id", userId); // target user's internal_uuid
|
||||
|
||||
// Disable the button while request is in progress
|
||||
row.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
membersList.getChildren().remove(row);
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
row.setDisable(false); // re-enable on error
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message", "Failed to remove member.")
|
||||
: "No response from server.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void openAddMembersOverlay(String groupId) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddMembersController controller = loader.getController();
|
||||
// Pass the groupId as UUID
|
||||
controller.setGroupForAdd(UUID.fromString(groupId), "");
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Add Members scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class NewChannelController {
|
||||
|
||||
createButton.setOnAction(e -> onCreateChannel());
|
||||
|
||||
// محدودیت توضیح
|
||||
// Limit description
|
||||
final int MAX_LENGTH = 255;
|
||||
channelDescField.addEventFilter(javafx.scene.input.KeyEvent.KEY_TYPED, e -> {
|
||||
if (channelDescField.getText().length() >= MAX_LENGTH) e.consume();
|
||||
@@ -79,7 +79,7 @@ public class NewChannelController {
|
||||
});
|
||||
descCounter.setText("0 / 255");
|
||||
|
||||
// پاک کردن استایل خطا هنگام تایپ
|
||||
// Reset error when user types again
|
||||
channelNameField.textProperty().addListener((obs, ov, nv) -> {
|
||||
if (!nv.trim().isEmpty()) {
|
||||
channelNameField.getStyleClass().remove("error");
|
||||
@@ -135,7 +135,8 @@ public class NewChannelController {
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
createButton.setDisable(false);
|
||||
showToast("Create failed: " + (resp == null ? "no response" : resp.optString("message","")));
|
||||
channelIdField.getStyleClass().add("error");
|
||||
channelIdLabel.getStyleClass().add("error");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,8 +110,10 @@ public class NewGroupController {
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> showToast("Create failed: " +
|
||||
(resp == null ? "no response" : resp.optString("message",""))));
|
||||
Platform.runLater(() -> {
|
||||
groupIdField.getStyleClass().add("error");
|
||||
groupIdLabel.getStyleClass().add("error");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,7 +184,4 @@ public class NewGroupController {
|
||||
a.initOwner(overlayRoot.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -364,10 +364,31 @@ public class SidebarMenuController {
|
||||
}
|
||||
}
|
||||
|
||||
private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); }
|
||||
private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); }
|
||||
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;
|
||||
@@ -420,4 +441,4 @@ public class SidebarMenuController {
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class TelegramFeaturesController {
|
||||
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private ScrollPane featureScroll;
|
||||
@FXML private StackPane rootOverlay;
|
||||
@FXML private BorderPane contentCard;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Clicking background closes overlay
|
||||
overlayBackground.setOnMouseClicked( e -> {
|
||||
MainController.getInstance().closeOverlay(rootOverlay);
|
||||
});
|
||||
|
||||
// Smooth scroll feel
|
||||
featureScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
featureScroll.setPannable(true);
|
||||
featureScroll.setFitToWidth(true);
|
||||
featureScroll.setFitToHeight(false);
|
||||
featureScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
featureScroll.setVvalue(featureScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (contentCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(contentCard.getScene());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class TelegramQnAController {
|
||||
|
||||
@FXML private StackPane rootOverlay;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private ScrollPane qnaScroll;
|
||||
@FXML private BorderPane contentCard;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
// Clicking outside closes overlay
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(rootOverlay)
|
||||
);
|
||||
|
||||
// Smooth scroll feel
|
||||
qnaScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
qnaScroll.setPannable(true);
|
||||
qnaScroll.setFitToWidth(true);
|
||||
qnaScroll.setFitToHeight(false);
|
||||
qnaScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
qnaScroll.setVvalue(qnaScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (contentCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(contentCard.getScene());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user