Manage Administrator UI + backend implemented.
This commit is contained in:
@@ -758,4 +758,27 @@ public class GroupDatabase {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static JSONObject getAdminPermissions(UUID groupId, UUID adminId) throws SQLException {
|
||||||
|
String sql = """
|
||||||
|
SELECT permissions
|
||||||
|
FROM group_members
|
||||||
|
WHERE group_id = ? AND user_id = ? AND role = 'admin'
|
||||||
|
""";
|
||||||
|
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setObject(1, groupId);
|
||||||
|
ps.setObject(2, adminId);
|
||||||
|
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
if (rs.next()) {
|
||||||
|
String jsonStr = rs.getString("permissions");
|
||||||
|
if (jsonStr != null && !jsonStr.isBlank()) {
|
||||||
|
return new JSONObject(jsonStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // not found or no permissions
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3029,6 +3029,38 @@ public class ClientHandler implements Runnable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "get_group_admin_permissions": {
|
||||||
|
if (currentUser == null) {
|
||||||
|
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
UUID groupId = UUID.fromString(requestJson.getString("group_id"));
|
||||||
|
UUID adminId = UUID.fromString(requestJson.getString("admin_id"));
|
||||||
|
|
||||||
|
// Only owner/admins can view
|
||||||
|
String myRole = GroupDatabase.getGroupRole(groupId, currentUser.getInternal_uuid());
|
||||||
|
if (!"owner".equalsIgnoreCase(myRole) && !"admin".equalsIgnoreCase(myRole)) {
|
||||||
|
response = new ResponseModel("error", "You are not authorized to view admin permissions.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject perms = GroupDatabase.getAdminPermissions(groupId, adminId);
|
||||||
|
|
||||||
|
if (perms != null) {
|
||||||
|
JSONObject data = new JSONObject().put("permissions", perms);
|
||||||
|
response = new ResponseModel("success", "Permissions fetched successfully.", data);
|
||||||
|
} else {
|
||||||
|
response = new ResponseModel("error", "Admin permissions not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
response = new ResponseModel("error", "Error fetching admin permissions: " + e.getMessage());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
response = new ResponseModel("error", "Unknown action: " + action);
|
response = new ResponseModel("error", "Unknown action: " + action);
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package org.to.telegramfinalproject.UI;
|
||||||
|
|
||||||
|
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.UUID;
|
||||||
|
|
||||||
|
public class AddAdminsController {
|
||||||
|
|
||||||
|
@FXML private VBox addAdminsCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton;
|
||||||
|
@FXML private Button closeFooterButton;
|
||||||
|
@FXML private ScrollPane membersScroll;
|
||||||
|
@FXML private VBox membersList;
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||||
|
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
for (int i = 0; i < members.length(); i++) {
|
||||||
|
JSONObject m = members.getJSONObject(i);
|
||||||
|
|
||||||
|
String role = m.optString("role", "member");
|
||||||
|
if ("owner".equalsIgnoreCase(role) || "admin".equalsIgnoreCase(role)) {
|
||||||
|
continue; // skip existing admins/owner
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Button promoteBtn = new Button("Promote");
|
||||||
|
promoteBtn.getStyleClass().add("link-btn");
|
||||||
|
promoteBtn.setOnAction(e -> promoteToAdmin(m.optString("internal_uuid"), row));
|
||||||
|
|
||||||
|
row.getChildren().addAll(avatar, details, spacer, promoteBtn);
|
||||||
|
membersList.getChildren().add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void promoteToAdmin(String internalUuid, HBox row) {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/set_group_admin_permissions.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
SetGroupAdminPermissionsController controller = loader.getController();
|
||||||
|
controller.setTarget(UUID.fromString(groupId), UUID.fromString(internalUuid), false);
|
||||||
|
|
||||||
|
// Pass a callback to remove row if promotion succeeds
|
||||||
|
controller.setOnSuccess(() -> membersList.getChildren().remove(row));
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error", "Could not load permissions scene.", Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ManageAdminsController {
|
||||||
|
|
||||||
|
@FXML private VBox adminsCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton;
|
||||||
|
@FXML private Button closeFooterButton;
|
||||||
|
@FXML private Button addAdminButton;
|
||||||
|
|
||||||
|
@FXML private ScrollPane adminsScroll;
|
||||||
|
@FXML private VBox adminsList;
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||||
|
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||||
|
|
||||||
|
addAdminButton.setOnAction(e -> openAddAdminOverlay());
|
||||||
|
|
||||||
|
// Smooth scroll feel
|
||||||
|
adminsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||||
|
adminsScroll.setPannable(true);
|
||||||
|
adminsScroll.setFitToWidth(true);
|
||||||
|
adminsScroll.setFitToHeight(false);
|
||||||
|
adminsScroll.getContent().setOnScroll(event -> {
|
||||||
|
double deltaY = event.getDeltaY() * 0.003;
|
||||||
|
adminsScroll.setVvalue(adminsScroll.getVvalue() - deltaY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupData(String groupId, JSONArray admins) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
|
||||||
|
adminsList.getChildren().clear();
|
||||||
|
|
||||||
|
// Convert to list for sorting
|
||||||
|
List<JSONObject> adminList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < admins.length(); i++) {
|
||||||
|
adminList.add(admins.getJSONObject(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort: owner first, then admins
|
||||||
|
adminList.sort((a, b) -> {
|
||||||
|
String roleA = a.optString("role", "member");
|
||||||
|
String roleB = b.optString("role", "member");
|
||||||
|
if ("owner".equalsIgnoreCase(roleA) && !"owner".equalsIgnoreCase(roleB)) return -1;
|
||||||
|
if ("owner".equalsIgnoreCase(roleB) && !"owner".equalsIgnoreCase(roleA)) return 1;
|
||||||
|
return 0; // keep relative order for admins
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add rows
|
||||||
|
for (JSONObject a : adminList) {
|
||||||
|
addAdminRow(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAdminRow(JSONObject a) {
|
||||||
|
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 = a.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(a.optString("profile_name", "Unknown"));
|
||||||
|
name.getStyleClass().add("member-name");
|
||||||
|
|
||||||
|
Label status = new Label(
|
||||||
|
a.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);
|
||||||
|
|
||||||
|
// Remove admin (cannot remove owner)
|
||||||
|
String role = a.optString("role", "admin");
|
||||||
|
if (!"owner".equalsIgnoreCase(role)) {
|
||||||
|
Button removeBtn = new Button("Remove");
|
||||||
|
removeBtn.getStyleClass().add("link-btn");
|
||||||
|
removeBtn.setOnAction(e -> removeAdmin(a.optString("user_id"), row));
|
||||||
|
row.getChildren().add(removeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
adminsList.getChildren().add(row);
|
||||||
|
|
||||||
|
// User clicks on a admin --> open set admin permission's scene
|
||||||
|
row.setOnMouseClicked(e -> {
|
||||||
|
if (!"owner".equalsIgnoreCase(role)) {
|
||||||
|
try {
|
||||||
|
// request current permissions from server
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "get_group_admin_permissions")
|
||||||
|
.put("group_id", groupId)
|
||||||
|
.put("admin_id", a.optString("user_id"));
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
JSONObject oldPerms = (resp != null) ? resp.optJSONObject("permissions") : null;
|
||||||
|
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/set_group_admin_permissions.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
SetGroupAdminPermissionsController controller = loader.getController();
|
||||||
|
controller.setTarget(
|
||||||
|
UUID.fromString(groupId),
|
||||||
|
UUID.fromString(a.optString("user_id")),
|
||||||
|
true,
|
||||||
|
oldPerms
|
||||||
|
);
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert("Error", "Could not open permissions scene.", Alert.AlertType.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeAdmin(String userId, HBox row) {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "remove_admin_from_group")
|
||||||
|
.put("group_id", groupId)
|
||||||
|
.put("target_user_id", userId);
|
||||||
|
|
||||||
|
System.out.println("Sending remove_admin_from_group request: " + req.toString(2));
|
||||||
|
|
||||||
|
row.setDisable(true);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
Platform.runLater(() -> adminsList.getChildren().remove(row));
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
row.setDisable(false);
|
||||||
|
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||||
|
resp != null ? resp.optString("message", "Failed to remove admin.") : "No response from server.",
|
||||||
|
ButtonType.OK);
|
||||||
|
a.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openAddAdminOverlay() {
|
||||||
|
try {
|
||||||
|
// === Query the server for full members list ===
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "view_group_members")
|
||||||
|
.put("group_id", groupId);
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
|
||||||
|
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Could not fetch members.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
JSONArray members = (data != null) ? data.optJSONArray("members") : new JSONArray();
|
||||||
|
|
||||||
|
// === Load Add Admins FXML ===
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/add_admins.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
AddAdminsController controller = loader.getController();
|
||||||
|
controller.setGroupData(groupId, members); // send raw members, filtering is done in AddAdminsController
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error", "Could not load Add Admins scene.", Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,14 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ManageGroupController {
|
public class ManageGroupController {
|
||||||
|
|
||||||
@@ -44,18 +46,34 @@ public class ManageGroupController {
|
|||||||
changePicButton.setOnAction(e -> choosePicture());
|
changePicButton.setOnAction(e -> choosePicture());
|
||||||
saveButton.setOnAction(e -> saveChanges());
|
saveButton.setOnAction(e -> saveChanges());
|
||||||
|
|
||||||
manageAdminsButton.setOnAction(e -> openManageAdminsScene());
|
manageAdminsButton.setOnAction(e -> openManageAdminsScene(data));
|
||||||
manageMembersButton.setOnAction(e -> openManageMembersScene(data));
|
manageMembersButton.setOnAction(e -> openManageMembersScene(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openManageAdminsScene() {
|
private void openManageAdminsScene(JSONObject data) {
|
||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
"/org/to/telegramfinalproject/Fxml/manage_admins.fxml"));
|
"/org/to/telegramfinalproject/Fxml/manage_admins.fxml"));
|
||||||
Node overlay = loader.load();
|
Node overlay = loader.load();
|
||||||
|
|
||||||
// ManageAdminsController controller = loader.getController();
|
ManageAdminsController controller = loader.getController();
|
||||||
// controller.setGroupId(UUID.fromString(groupId));
|
|
||||||
|
String gid = data.optString("internal_uuid");
|
||||||
|
JSONArray members = data.optJSONArray("members");
|
||||||
|
JSONArray admins = new JSONArray();
|
||||||
|
|
||||||
|
// filter only admins
|
||||||
|
if (members != null) {
|
||||||
|
for (int i = 0; i < members.length(); i++) {
|
||||||
|
JSONObject m = members.getJSONObject(i);
|
||||||
|
String role = m.optString("role", "member");
|
||||||
|
if ("admin".equalsIgnoreCase(role) || "owner".equalsIgnoreCase(role)) {
|
||||||
|
admins.put(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.setGroupData(gid, admins);
|
||||||
|
|
||||||
MainController.getInstance().showOverlay(overlay);
|
MainController.getInstance().showOverlay(overlay);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -101,8 +119,8 @@ public class ManageGroupController {
|
|||||||
groupImage.setImage(new Image(originalImageUrl, true));
|
groupImage.setImage(new Image(originalImageUrl, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
adminCount.setText(String.valueOf(countRole(data, "admin")));
|
adminCount.setText(String.valueOf(countRole(data, "admin") + 1));
|
||||||
memberCount.setText(String.valueOf(countRole(data, "member")));
|
memberCount.setText(String.valueOf(countRole(data, "member") + Integer.parseInt(adminCount.getText())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countRole(JSONObject groupData, String role) {
|
private int countRole(JSONObject groupData, String role) {
|
||||||
@@ -112,7 +130,7 @@ public class ManageGroupController {
|
|||||||
for (int i = 0; i < arr.length(); i++) {
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
if (role.equalsIgnoreCase(arr.getJSONObject(i).optString("role"))) count++;
|
if (role.equalsIgnoreCase(arr.getJSONObject(i).optString("role"))) count++;
|
||||||
}
|
}
|
||||||
return count + 1;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void choosePicture() {
|
private void choosePicture() {
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package org.to.telegramfinalproject.UI;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SetGroupAdminPermissionsController {
|
||||||
|
|
||||||
|
@FXML private VBox permissionsCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton;
|
||||||
|
@FXML private Button cancelButton;
|
||||||
|
@FXML private Button saveButton;
|
||||||
|
|
||||||
|
@FXML private CheckBox canAddMember;
|
||||||
|
@FXML private CheckBox canRemoveMember;
|
||||||
|
@FXML private CheckBox canAddAdmin;
|
||||||
|
@FXML private CheckBox canRemoveAdmin;
|
||||||
|
@FXML private CheckBox canEditGroup;
|
||||||
|
|
||||||
|
private UUID groupId;
|
||||||
|
private UUID userId;
|
||||||
|
private Runnable onSuccess;
|
||||||
|
|
||||||
|
private JSONObject oldPermissions; // store existing permissions
|
||||||
|
private boolean isEditMode = false; // true if editing existing admin
|
||||||
|
|
||||||
|
public void setTarget(UUID groupId, UUID userId, boolean isEditMode) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.userId = userId;
|
||||||
|
this.isEditMode = isEditMode;
|
||||||
|
|
||||||
|
if (isEditMode) {
|
||||||
|
loadCurrentPermissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget(UUID groupId, UUID userId, boolean isEditMode, JSONObject oldPermissions) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.userId = userId;
|
||||||
|
this.isEditMode = isEditMode;
|
||||||
|
this.oldPermissions = oldPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSuccess(Runnable r) {
|
||||||
|
this.onSuccess = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||||
|
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||||
|
|
||||||
|
saveButton.setOnAction(e -> savePermissions());
|
||||||
|
|
||||||
|
// Register for dark mode updates
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (permissionsCard.getScene() != null) {
|
||||||
|
ThemeManager.getInstance().registerScene(permissionsCard.getScene());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCurrentPermissions() {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "get_group_admin_permissions")
|
||||||
|
.put("group_id", groupId.toString())
|
||||||
|
.put("admin_id", userId.toString());
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
System.out.println("Permissions response: " + resp); // full server response
|
||||||
|
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
// "data" is an object containing { "permissions": { ... } }
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
System.out.println("Data object: " + data);
|
||||||
|
|
||||||
|
if (data != null) {
|
||||||
|
JSONObject perms = data.optJSONObject("permissions");
|
||||||
|
System.out.println("Extracted permissions object: " + perms);
|
||||||
|
|
||||||
|
if (perms != null) {
|
||||||
|
oldPermissions = perms;
|
||||||
|
|
||||||
|
// Update checkboxes safely on FX thread
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
canAddMember.setSelected(perms.optBoolean("can_add_member", false));
|
||||||
|
canRemoveMember.setSelected(perms.optBoolean("can_remove_member", false));
|
||||||
|
canAddAdmin.setSelected(perms.optBoolean("can_add_admin", false));
|
||||||
|
canRemoveAdmin.setSelected(perms.optBoolean("can_remove_admin", false));
|
||||||
|
canEditGroup.setSelected(perms.optBoolean("can_edit_group", false));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
System.out.println("⚠️ Permissions object was null inside data.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("⚠️ Data object was null in response.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("⚠️ Response was null or status != success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePermissions() {
|
||||||
|
JSONObject newPermissions = new JSONObject()
|
||||||
|
.put("can_add_member", canAddMember.isSelected())
|
||||||
|
.put("can_remove_member", canRemoveMember.isSelected())
|
||||||
|
.put("can_add_admin", canAddAdmin.isSelected())
|
||||||
|
.put("can_remove_admin", canRemoveAdmin.isSelected())
|
||||||
|
.put("can_edit_group", canEditGroup.isSelected());
|
||||||
|
|
||||||
|
JSONObject req;
|
||||||
|
if (isEditMode) {
|
||||||
|
// --- Editing existing admin permissions ---
|
||||||
|
req = new JSONObject()
|
||||||
|
.put("action", "edit_admin_permissions")
|
||||||
|
.put("chat_id", groupId.toString())
|
||||||
|
.put("chat_type", "group") // since this controller is only for groups
|
||||||
|
.put("admin_id", userId.toString())
|
||||||
|
.put("permissions", newPermissions);
|
||||||
|
} else {
|
||||||
|
// --- Adding a new admin ---
|
||||||
|
req = new JSONObject()
|
||||||
|
.put("action", "add_admin_to_group")
|
||||||
|
.put("group_id", groupId.toString())
|
||||||
|
.put("user_id", userId.toString())
|
||||||
|
.put("permissions", newPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
MainController.getInstance().closeOverlay(permissionsCard.getParent());
|
||||||
|
if (onSuccess != null) onSuccess.run(); // update UI after success
|
||||||
|
} else {
|
||||||
|
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||||
|
resp != null ? resp.optString("message") : "Failed to save admin permissions.",
|
||||||
|
ButtonType.OK);
|
||||||
|
a.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
-fx-text-fill: #ffffff;
|
-fx-text-fill: #ffffff;
|
||||||
-fx-padding: 4 0 2 0;
|
-fx-padding: 4 0 2 0;
|
||||||
-fx-border-color: #333;
|
-fx-border-color: #333;
|
||||||
-fx-border-width: 0 0 1 0;
|
-fx-border-width: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-item-title {
|
.tf-item-title {
|
||||||
@@ -1364,3 +1364,49 @@
|
|||||||
-fx-text-fill: #ffb74d;
|
-fx-text-fill: #ffb74d;
|
||||||
-fx-background-color: #e65100;
|
-fx-background-color: #e65100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Permissions card checkboxes */
|
||||||
|
.check-box {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: -fx-text-base-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Permissions overlay === */
|
||||||
|
.permissions-card {
|
||||||
|
-fx-background-color: #1f1f1f;
|
||||||
|
-fx-background-radius: 12;
|
||||||
|
-fx-padding: 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkboxes */
|
||||||
|
.permissions-card .check-box {
|
||||||
|
-fx-text-fill: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .check-box:selected .box {
|
||||||
|
-fx-background-color: #3390ec;
|
||||||
|
-fx-border-color: #3390ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .check-box .box {
|
||||||
|
-fx-background-color: #2c2c2c;
|
||||||
|
-fx-border-color: #555;
|
||||||
|
-fx-border-radius: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.permissions-card .button {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .button.primary-btn {
|
||||||
|
-fx-background-color: #3390ec;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .button.secondary-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: #555;
|
||||||
|
-fx-text-fill: #aaa;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1315,7 +1315,7 @@
|
|||||||
-fx-text-fill: #2c2c2c;
|
-fx-text-fill: #2c2c2c;
|
||||||
-fx-padding: 4 0 2 0;
|
-fx-padding: 4 0 2 0;
|
||||||
-fx-border-color: #ddd;
|
-fx-border-color: #ddd;
|
||||||
-fx-border-width: 0 0 1 0;
|
-fx-border-width: 0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tf-item-title {
|
.tf-item-title {
|
||||||
@@ -1341,3 +1341,49 @@
|
|||||||
-fx-text-fill: #e65100;
|
-fx-text-fill: #e65100;
|
||||||
-fx-background-color: #fff3e0;
|
-fx-background-color: #fff3e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Permissions card checkboxes */
|
||||||
|
.check-box {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: -fx-text-base-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Permissions overlay === */
|
||||||
|
.permissions-card {
|
||||||
|
-fx-background-color: white;
|
||||||
|
-fx-background-radius: 12;
|
||||||
|
-fx-padding: 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkboxes */
|
||||||
|
.permissions-card .check-box {
|
||||||
|
-fx-text-fill: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .check-box:selected .box {
|
||||||
|
-fx-background-color: #3390ec; /* blue tick */
|
||||||
|
-fx-border-color: #3390ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .check-box .box {
|
||||||
|
-fx-background-color: white;
|
||||||
|
-fx-border-color: #ccc;
|
||||||
|
-fx-border-radius: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.permissions-card .button {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .button.primary-btn {
|
||||||
|
-fx-background-color: #3390ec;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-card .button.secondary-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: #ccc;
|
||||||
|
-fx-text-fill: #222;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.AddAdminsController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Card -->
|
||||||
|
<VBox fx:id="addAdminsCard" styleClass="profile-card" spacing="12"
|
||||||
|
prefWidth="340" maxWidth="340"
|
||||||
|
prefHeight="480" maxHeight="480">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Add Admins" styleClass="profile-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- Members list -->
|
||||||
|
<ScrollPane fx:id="membersScroll" fitToWidth="true" VBox.vgrow="ALWAYS"
|
||||||
|
styleClass="member-scroll">
|
||||||
|
<content>
|
||||||
|
<VBox fx:id="membersList" spacing="10">
|
||||||
|
<padding>
|
||||||
|
<Insets top="10" right="10" bottom="10" left="10"/>
|
||||||
|
</padding>
|
||||||
|
</VBox>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<HBox spacing="10" alignment="CENTER_RIGHT">
|
||||||
|
<Button fx:id="closeFooterButton" text="Close" styleClass="secondary-btn"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.ManageAdminsController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Card -->
|
||||||
|
<VBox fx:id="adminsCard" styleClass="profile-card" spacing="12"
|
||||||
|
prefWidth="360" maxWidth="360"
|
||||||
|
prefHeight="500" maxHeight="500">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Administrators" styleClass="profile-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- Scroll list -->
|
||||||
|
<ScrollPane fx:id="adminsScroll" fitToWidth="true" styleClass="member-scroll" VBox.vgrow="ALWAYS">
|
||||||
|
<content>
|
||||||
|
<VBox fx:id="adminsList" spacing="10"/>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<HBox spacing="10" alignment="CENTER_RIGHT">
|
||||||
|
<Button fx:id="addAdminButton" text="Add admin" styleClass="secondary-btn" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeFooterButton" text="Close" styleClass="primary-btn"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.SetGroupAdminPermissionsController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Card -->
|
||||||
|
<VBox fx:id="permissionsCard" styleClass="profile-card" spacing="16"
|
||||||
|
prefWidth="320" maxWidth="320"
|
||||||
|
prefHeight="350" maxHeight="350">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Set Admin Permissions" styleClass="profile-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator/>
|
||||||
|
|
||||||
|
<!-- Permissions list -->
|
||||||
|
<VBox spacing="8">
|
||||||
|
<CheckBox fx:id="canAddMember" text="Can add members"/>
|
||||||
|
<CheckBox fx:id="canRemoveMember" text="Can remove members"/>
|
||||||
|
<CheckBox fx:id="canAddAdmin" text="Can add admins"/>
|
||||||
|
<CheckBox fx:id="canRemoveAdmin" text="Can remove admins"/>
|
||||||
|
<CheckBox fx:id="canEditGroup" text="Can edit group"/>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<Pane VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
|
<Separator/>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<HBox alignment="CENTER_RIGHT" spacing="10">
|
||||||
|
<Button fx:id="cancelButton" text="Cancel" styleClass="secondary-btn"/>
|
||||||
|
<Button fx:id="saveButton" text="Save" styleClass="primary-btn"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
Reference in New Issue
Block a user