Manage Channel UI + backend implemented.
This commit is contained in:
@@ -208,7 +208,62 @@ public class ChannelInfoController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openManageChannel() {
|
private void openManageChannel() {
|
||||||
// TODO similar to ManageGroupController
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "view_channel")
|
||||||
|
.put("channel_id", channelId.toString()); // ✅ matches server case
|
||||||
|
|
||||||
|
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_channel.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
ManageChannelController controller = loader.getController();
|
||||||
|
controller.setChannelData(data); // ✅ pass JSON to controller
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Could not load Manage Channel scene.",
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||||
|
"Error",
|
||||||
|
"Error while fetching channel info: " + e.getMessage(),
|
||||||
|
Alert.AlertType.ERROR
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openAddSubscriberScene() {
|
private void openAddSubscriberScene() {
|
||||||
|
|||||||
@@ -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 ManageChannelAdminsController {
|
||||||
|
|
||||||
|
@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 channelId; // internal_uuid of channel
|
||||||
|
|
||||||
|
@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 setChannelData(String channelId, JSONArray admins) {
|
||||||
|
this.channelId = channelId;
|
||||||
|
|
||||||
|
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", "subscriber");
|
||||||
|
String roleB = b.optString("role", "subscriber");
|
||||||
|
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 an admin → open set channel admin permissions scene
|
||||||
|
row.setOnMouseClicked(e -> {
|
||||||
|
if (!"owner".equalsIgnoreCase(role)) {
|
||||||
|
try {
|
||||||
|
// request current permissions from server
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "get_channel_admin_permissions")
|
||||||
|
.put("channel_id", channelId)
|
||||||
|
.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_channel_admin_permissions.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
// SetChannelAdminPermissionsController controller = loader.getController();
|
||||||
|
// controller.setTarget(
|
||||||
|
// UUID.fromString(channelId),
|
||||||
|
// 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_channel")
|
||||||
|
.put("channel_id", channelId)
|
||||||
|
.put("target_user_id", userId);
|
||||||
|
|
||||||
|
System.out.println("Sending remove_admin_from_channel 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 subscribers list ===
|
||||||
|
// JSONObject req = new JSONObject()
|
||||||
|
// .put("action", "view_channel_subscribers")
|
||||||
|
// .put("channel_id", channelId);
|
||||||
|
//
|
||||||
|
// JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
//
|
||||||
|
// if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
// MainController.getInstance().showAlert(
|
||||||
|
// "Error",
|
||||||
|
// "Could not fetch subscribers.",
|
||||||
|
// Alert.AlertType.ERROR
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// JSONObject data = resp.optJSONObject("data");
|
||||||
|
// JSONArray subscribers = (data != null) ? data.optJSONArray("subscribers") : new JSONArray();
|
||||||
|
//
|
||||||
|
// // === Load Add Admins FXML (same FXML reused) ===
|
||||||
|
// FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
// "/org/to/telegramfinalproject/Fxml/add_admins.fxml"));
|
||||||
|
// Node overlay = loader.load();
|
||||||
|
//
|
||||||
|
// AddChannelAdminsController controller = loader.getController();
|
||||||
|
// controller.setChannelData(channelId, subscribers); // send raw subscribers, filtering is done in AddChannelAdminsController
|
||||||
|
//
|
||||||
|
// MainController.getInstance().showOverlay(overlay);
|
||||||
|
//
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// MainController.getInstance().showAlert(
|
||||||
|
// "Error", "Could not load Add Admins scene.", Alert.AlertType.ERROR
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
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.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ManageChannelController {
|
||||||
|
|
||||||
|
@FXML private VBox manageChannelCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton, changePicButton, cancelButton, saveButton;
|
||||||
|
@FXML private ImageView channelImage;
|
||||||
|
@FXML private TextField channelNameField;
|
||||||
|
@FXML private TextField channelIdField;
|
||||||
|
@FXML private TextArea channelDescriptionField;
|
||||||
|
@FXML private Label adminCount, subscriberCount;
|
||||||
|
@FXML private Button manageAdminsButton;
|
||||||
|
@FXML private Button manageSubscribersButton;
|
||||||
|
|
||||||
|
private String channelId; // Internal UUID of the channel
|
||||||
|
private String originalName;
|
||||||
|
private String originalDisplayId;
|
||||||
|
private String originalImageUrl;
|
||||||
|
private String originalDescription;
|
||||||
|
private File selectedImageFile;
|
||||||
|
|
||||||
|
private JSONObject data;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||||
|
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||||
|
|
||||||
|
changePicButton.setOnAction(e -> choosePicture());
|
||||||
|
saveButton.setOnAction(e -> saveChanges());
|
||||||
|
|
||||||
|
manageAdminsButton.setOnAction(e -> openManageAdminsScene(data));
|
||||||
|
manageSubscribersButton.setOnAction(e -> openManageSubscribersScene(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannelData(JSONObject data) {
|
||||||
|
this.data = data;
|
||||||
|
this.channelId = data.optString("internal_uuid");
|
||||||
|
|
||||||
|
originalName = data.optString("channel_name", "");
|
||||||
|
originalDisplayId = data.optString("channel_id", "");
|
||||||
|
originalImageUrl = data.optString("image_url", "");
|
||||||
|
originalDescription = data.optString("description", "");
|
||||||
|
|
||||||
|
channelNameField.setText(originalName);
|
||||||
|
channelIdField.setText(originalDisplayId);
|
||||||
|
channelDescriptionField.setText(originalDescription);
|
||||||
|
|
||||||
|
if (!originalImageUrl.isBlank()) {
|
||||||
|
channelImage.setImage(new Image(originalImageUrl, true));
|
||||||
|
}
|
||||||
|
adminCount.setText(String.valueOf(countRole(data, "admin") + 1)); // owner + admins
|
||||||
|
subscriberCount.setText(String.valueOf(countRole(data, "subscriber") + Integer.parseInt(adminCount.getText())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countRole(JSONObject channelData, String role) {
|
||||||
|
var arr = channelData.optJSONArray("subscribers");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void choosePicture() {
|
||||||
|
FileChooser fc = new FileChooser();
|
||||||
|
fc.setTitle("Select channel picture");
|
||||||
|
selectedImageFile = fc.showOpenDialog(manageChannelCard.getScene().getWindow());
|
||||||
|
if (selectedImageFile != null) {
|
||||||
|
channelImage.setImage(new Image(selectedImageFile.toURI().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveChanges() {
|
||||||
|
String newName = channelNameField.getText().trim();
|
||||||
|
String newChannelId = channelIdField.getText().trim();
|
||||||
|
String newDescription = channelDescriptionField.getText().trim();
|
||||||
|
String newImageUrl = (selectedImageFile != null)
|
||||||
|
? selectedImageFile.toURI().toString()
|
||||||
|
: originalImageUrl;
|
||||||
|
|
||||||
|
boolean changed =
|
||||||
|
!Objects.equals(originalName, newName) ||
|
||||||
|
!Objects.equals(originalDisplayId, newChannelId) ||
|
||||||
|
!Objects.equals(originalDescription, newDescription) ||
|
||||||
|
!Objects.equals(originalImageUrl, newImageUrl);
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
MainController.getInstance().closeOverlay(manageChannelCard.getParent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "edit_channel_info")
|
||||||
|
.put("channel_id", channelId) // internal UUID
|
||||||
|
.put("new_channel_id", newChannelId)
|
||||||
|
.put("name", newName)
|
||||||
|
.put("description", newDescription);
|
||||||
|
|
||||||
|
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(manageChannelCard.getParent());
|
||||||
|
} else {
|
||||||
|
String msg = (resp != null)
|
||||||
|
? resp.optString("message", "Failed to update channel")
|
||||||
|
: "No response from server";
|
||||||
|
Alert a = new Alert(Alert.AlertType.ERROR, msg, ButtonType.OK);
|
||||||
|
a.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openManageAdminsScene(JSONObject data) {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/manage_channel_admins.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
ManageChannelAdminsController controller = loader.getController();
|
||||||
|
// Pass channelId and subscribers list from JSON
|
||||||
|
controller.setChannelData(
|
||||||
|
data.optString("internal_uuid"),
|
||||||
|
data.optJSONArray("subscribers")
|
||||||
|
);
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error", "Could not load Manage Channel Admins scene.", Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openManageSubscribersScene(JSONObject data) {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/manage_subscribers.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
ManageSubscribersController controller = loader.getController();
|
||||||
|
// Pass channelId and subscribers list from the JSON data
|
||||||
|
controller.setChannelData(
|
||||||
|
data.optString("internal_uuid"),
|
||||||
|
data.optJSONArray("subscribers")
|
||||||
|
);
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error", "Could not load Manage Subscribers scene.", Alert.AlertType.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
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.UUID;
|
||||||
|
|
||||||
|
public class ManageSubscribersController {
|
||||||
|
|
||||||
|
@FXML private VBox subscribersCard;
|
||||||
|
@FXML private Pane overlayBackground;
|
||||||
|
@FXML private Button closeButton;
|
||||||
|
@FXML private Button closeFooterButton;
|
||||||
|
@FXML private Button addSubscribersButton;
|
||||||
|
@FXML private ScrollPane subscribersScroll;
|
||||||
|
@FXML private VBox subscribersList;
|
||||||
|
|
||||||
|
private String channelId; // internal UUID
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||||
|
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||||
|
|
||||||
|
addSubscribersButton.setOnAction(e -> openAddSubscribersOverlay(channelId));
|
||||||
|
|
||||||
|
// Smooth scroll
|
||||||
|
subscribersScroll.getStylesheets().add(
|
||||||
|
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||||
|
);
|
||||||
|
subscribersScroll.setPannable(true);
|
||||||
|
subscribersScroll.setFitToWidth(true);
|
||||||
|
subscribersScroll.setFitToHeight(false);
|
||||||
|
subscribersScroll.getContent().setOnScroll(event -> {
|
||||||
|
double deltaY = event.getDeltaY() * 0.003;
|
||||||
|
subscribersScroll.setVvalue(subscribersScroll.getVvalue() - deltaY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannelData(String channelId, JSONArray subscribers) {
|
||||||
|
this.channelId = channelId;
|
||||||
|
subscribersList.getChildren().clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < subscribers.length(); i++) {
|
||||||
|
Object raw = subscribers.get(i);
|
||||||
|
JSONObject sub = (raw instanceof JSONObject)
|
||||||
|
? (JSONObject) raw
|
||||||
|
: new JSONObject((java.util.Map<?, ?>) raw);
|
||||||
|
|
||||||
|
addSubscriberRow(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSubscriberRow(JSONObject sub) {
|
||||||
|
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 = sub.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(sub.optString("profile_name", "Unknown"));
|
||||||
|
name.getStyleClass().add("member-name");
|
||||||
|
|
||||||
|
Label status = new Label(
|
||||||
|
sub.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 = sub.optString("role", "subscriber");
|
||||||
|
if (!"owner".equalsIgnoreCase(role)) {
|
||||||
|
Button removeBtn = new Button("Remove");
|
||||||
|
removeBtn.getStyleClass().add("link-btn");
|
||||||
|
removeBtn.setOnAction(e -> removeSubscriber(sub.optString("user_id"), row));
|
||||||
|
row.getChildren().add(removeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribersList.getChildren().add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSubscriber(String userId, HBox row) {
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "remove_subscriber_from_channel")
|
||||||
|
.put("channel_id", channelId)
|
||||||
|
.put("user_id", userId);
|
||||||
|
|
||||||
|
row.setDisable(true);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
|
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
Platform.runLater(() -> subscribersList.getChildren().remove(row));
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
row.setDisable(false);
|
||||||
|
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||||
|
resp != null ? resp.optString("message", "Failed to remove subscriber.")
|
||||||
|
: "No response from server.",
|
||||||
|
ButtonType.OK);
|
||||||
|
a.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openAddSubscribersOverlay(String channelId) {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
|
||||||
|
Node overlay = loader.load();
|
||||||
|
|
||||||
|
AddSubscriberController controller = loader.getController();
|
||||||
|
controller.setChannelInfo(UUID.fromString(channelId), "", "", null, "");
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(overlay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
MainController.getInstance().showAlert(
|
||||||
|
"Error", "Could not load Add Subscribers scene.", Alert.AlertType.ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1441,3 +1441,21 @@
|
|||||||
-fx-font-size: 12px;
|
-fx-font-size: 12px;
|
||||||
-fx-text-fill: #777;
|
-fx-text-fill: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark theme: Channel Manage styles */
|
||||||
|
.description-input {
|
||||||
|
-fx-background-color: #1f2a36; /* darker to match card */
|
||||||
|
-fx-control-inner-background: #1f2a36;
|
||||||
|
-fx-border-color: #444444;
|
||||||
|
-fx-border-radius: 6;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
-fx-padding: 6;
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: #dddddd; /* normal text */
|
||||||
|
-fx-prompt-text-fill: #888888; /* dimmed placeholder text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-input:focused {
|
||||||
|
-fx-border-color: #4a90e2; /* Telegram blue */
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(74,144,226,0.5), 6, 0.4, 0, 0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1392,3 +1392,19 @@
|
|||||||
-fx-font-size: 12px;
|
-fx-font-size: 12px;
|
||||||
-fx-text-fill: #777;
|
-fx-text-fill: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Light theme: Channel Manage styles */
|
||||||
|
.description-input {
|
||||||
|
-fx-background-color: #ffffff;
|
||||||
|
-fx-border-color: #d0d0d0;
|
||||||
|
-fx-border-radius: 6;
|
||||||
|
-fx-background-radius: 6;
|
||||||
|
-fx-padding: 6;
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-input:focused {
|
||||||
|
-fx-border-color: #4a90e2; /* Telegram blue highlight */
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(74,144,226,0.4), 4, 0.3, 0, 0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.ManageChannelController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Card -->
|
||||||
|
<VBox fx:id="manageChannelCard" styleClass="profile-card" spacing="16"
|
||||||
|
prefWidth="360" maxWidth="360"
|
||||||
|
prefHeight="520" maxHeight="520">
|
||||||
|
|
||||||
|
<!-- ===== Header ===== -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Edit channel" styleClass="profile-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- ===== Channel picture + fields ===== -->
|
||||||
|
<VBox alignment="CENTER" spacing="12">
|
||||||
|
<!-- Channel picture -->
|
||||||
|
<Button fx:id="changePicButton" styleClass="circle-pic-btn">
|
||||||
|
<graphic>
|
||||||
|
<ImageView fx:id="channelImage" fitWidth="80" fitHeight="80" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Avatars/default_channel_profile.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Channel name -->
|
||||||
|
<TextField fx:id="channelNameField" promptText="Channel name" styleClass="input-field"/>
|
||||||
|
|
||||||
|
<!-- Channel ID -->
|
||||||
|
<TextField fx:id="channelIdField" promptText="Channel ID (unique)" styleClass="input-field"/>
|
||||||
|
|
||||||
|
<!-- Channel description -->
|
||||||
|
<TextArea fx:id="channelDescriptionField"
|
||||||
|
promptText="Description (optional)"
|
||||||
|
wrapText="true"
|
||||||
|
prefRowCount="3"
|
||||||
|
VBox.vgrow="ALWAYS"
|
||||||
|
styleClass="description-input"/>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<!-- ===== Options ===== -->
|
||||||
|
<VBox spacing="10" styleClass="info-blocks" VBox.vgrow="ALWAYS">
|
||||||
|
<!-- Administrators -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Administrators" styleClass="info-row-label"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Label fx:id="adminCount" text="0" styleClass="info-row-value"/>
|
||||||
|
<Button fx:id="manageAdminsButton" text="Manage" styleClass="manage-link-btn"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- Subscribers -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Subscribers" styleClass="info-row-label"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Label fx:id="subscriberCount" text="0" styleClass="info-row-value"/>
|
||||||
|
<Button fx:id="manageSubscribersButton" text="Manage" styleClass="manage-link-btn"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<!-- ===== Footer ===== -->
|
||||||
|
<HBox spacing="10" alignment="CENTER_RIGHT">
|
||||||
|
<Button fx:id="cancelButton" text="Cancel" styleClass="secondary-btn"/>
|
||||||
|
<Button fx:id="saveButton" text="Save" styleClass="primary-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.ManageChannelAdminsController"
|
||||||
|
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="Channel 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,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
|
||||||
|
<StackPane xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.to.telegramfinalproject.UI.ManageSubscribersController"
|
||||||
|
styleClass="overlay-root">
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<Pane fx:id="overlayBackground" styleClass="overlay-background"/>
|
||||||
|
|
||||||
|
<!-- Card -->
|
||||||
|
<VBox fx:id="subscribersCard" styleClass="profile-card" spacing="12"
|
||||||
|
prefWidth="360" maxWidth="360"
|
||||||
|
prefHeight="520" maxHeight="520">
|
||||||
|
|
||||||
|
<!-- ===== Header ===== -->
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="Subscribers" styleClass="profile-title" HBox.hgrow="ALWAYS"/>
|
||||||
|
<Pane HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button fx:id="closeButton" styleClass="icon-button" text="✕"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- ===== Subscribers List ===== -->
|
||||||
|
<ScrollPane fx:id="subscribersScroll" fitToWidth="true" styleClass="member-scroll" VBox.vgrow="ALWAYS">
|
||||||
|
<content>
|
||||||
|
<VBox fx:id="subscribersList" spacing="10"/>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
|
||||||
|
<Separator styleClass="section-separator"/>
|
||||||
|
|
||||||
|
<!-- ===== Footer ===== -->
|
||||||
|
<HBox spacing="12" alignment="CENTER_RIGHT">
|
||||||
|
<Button fx:id="addSubscribersButton" text="Add subscribers" styleClass="secondary-btn"/>
|
||||||
|
<Button fx:id="closeFooterButton" text="Close" styleClass="primary-btn"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
Reference in New Issue
Block a user