diff --git a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java index b71f1ce..fcf9d60 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java @@ -12,10 +12,12 @@ 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 { @@ -55,6 +57,10 @@ public class GroupInfoController { 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(() -> { @@ -100,15 +106,26 @@ public class GroupInfoController { groupName.setText(data.optString("group_name", entry.getName())); memberCount.setText(data.optInt("member_count", 0) + " members"); - // show/hide Delete button if user is owner - String myRole = data.optString("my_role", "member"); - if ("owner".equalsIgnoreCase(myRole)) { - deleteGroupItem.setVisible(true); - } else { - deleteGroupItem.setVisible(false); + // --- 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); } - // Group picture + // 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 { @@ -122,7 +139,7 @@ public class GroupInfoController { ); } - // Members + // --- Members list --- membersList.getChildren().clear(); var arr = data.optJSONArray("members"); if (arr != null) { @@ -191,6 +208,66 @@ public class GroupInfoController { 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( @@ -198,8 +275,7 @@ public class GroupInfoController { Node overlay = loader.load(); AddMembersController controller = loader.getController(); - // 👇 Pass group id so AddMember scene knows which group to add to - controller.setGroupForAdd(groupId, groupName); + controller.setGroupForAdd(UUID.fromString(groupId), String.valueOf(groupName)); MainController.getInstance().showOverlay(overlay); } catch (IOException e) { diff --git a/src/main/java/org/to/telegramfinalproject/UI/ManageGroupController.java b/src/main/java/org/to/telegramfinalproject/UI/ManageGroupController.java new file mode 100644 index 0000000..daf2ea1 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/ManageGroupController.java @@ -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(); + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java index 5988989..7ebe0ca 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java @@ -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; } diff --git a/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java b/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java index 40df1fb..6363cb2 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/NewGroupController.java @@ -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(); } - - - } diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css index f85b051..f4a8338 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -1188,3 +1188,99 @@ -fx-font-size: 13px; -fx-text-fill: #4fa8f0; /* same Telegram blue */ } + +/* Inputs */ +.input-field, .input-area { + -fx-background-color: transparent; + -fx-border-width: 0 0 1 0; + -fx-border-color: #444; /* subtle underline */ + -fx-text-fill: #e6e6e6; /* light text */ + -fx-padding: 4 0 4 0; + -fx-font-size: 14px; +} +.input-area { + -fx-font-size: 12px; +} + +/* Info rows */ +.info-row-label { + -fx-font-size: 14px; + -fx-text-fill: #e6e6e6; +} +.info-row-value { + -fx-font-size: 14px; + -fx-text-fill: #4fa8f0; /* Telegram blue */ + -fx-font-weight: bold; +} + +/* Buttons */ +.primary-btn { + -fx-background-color: #4fa8f0; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-background-radius: 6; + -fx-padding: 6 14; +} +.secondary-btn { + -fx-background-color: transparent; + -fx-border-color: #8ea1b2; + -fx-border-radius: 6; + -fx-text-fill: #e6e6e6; + -fx-padding: 6 14; +} +.circle-pic-btn { + -fx-background-color: transparent; + -fx-padding: 0; +} + +.manage-link-btn { + -fx-background-color: transparent; + -fx-text-fill: #4fa8f0; /* brighter blue for dark mode */ + -fx-underline: false; + -fx-cursor: hand; + -fx-font-size: 13px; + -fx-padding: 0 4 0 4; +} + +.manage-link-btn:hover { + -fx-underline: true; +} +.manage-link-btn { + -fx-background-color: transparent; + -fx-text-fill: #4fa8f0; /* brighter blue for dark mode */ + -fx-underline: false; + -fx-cursor: hand; + -fx-font-size: 13px; + -fx-padding: 0 4 0 4; +} + +.manage-link-btn:hover { + -fx-underline: true; +} + +/* Shared */ +.member-row { + -fx-alignment: CENTER_LEFT; + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; +} + +.link-btn { + -fx-background-color: transparent; + -fx-text-fill: -fx-accent; + -fx-cursor: hand; + -fx-padding: 0 6 0 6; +} + +.link-btn:hover { + -fx-underline: true; +} \ No newline at end of file diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css index fd946ba..8238057 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -1178,3 +1178,87 @@ -fx-font-size: 13px; -fx-text-fill: #4fa8f0; /* Telegram blue */ } + +/* Inputs */ +.input-field, .input-area { + -fx-background-color: transparent; + -fx-border-width: 0 0 1 0; + -fx-border-color: #d0d0d0; /* light underline */ + -fx-text-fill: #2c2c2c; /* dark text */ + -fx-padding: 4 0 4 0; + -fx-font-size: 14px; +} +.input-area { + -fx-font-size: 12px; +} + +/* Info rows */ +.info-row-label { + -fx-font-size: 14px; + -fx-text-fill: #2c2c2c; +} +.info-row-value { + -fx-font-size: 14px; + -fx-text-fill: #0078d7; /* Telegram blue */ + -fx-font-weight: bold; +} + +/* Buttons */ +.primary-btn { + -fx-background-color: #0078d7; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-background-radius: 6; + -fx-padding: 6 14; +} +.secondary-btn { + -fx-background-color: transparent; + -fx-border-color: #8ea1b2; + -fx-border-radius: 6; + -fx-text-fill: #2c2c2c; + -fx-padding: 6 14; +} +.circle-pic-btn { + -fx-background-color: transparent; + -fx-padding: 0; +} + +.manage-link-btn { + -fx-background-color: transparent; + -fx-text-fill: #0078d7; /* Telegram blue */ + -fx-underline: false; + -fx-cursor: hand; + -fx-font-size: 13px; + -fx-padding: 0 4 0 4; +} + +.manage-link-btn:hover { + -fx-underline: true; +} + +/* Shared */ +.member-row { + -fx-alignment: CENTER_LEFT; + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; +} + +.link-btn { + -fx-background-color: transparent; + -fx-text-fill: -fx-accent; + -fx-cursor: hand; + -fx-padding: 0 6 0 6; +} + +.link-btn:hover { + -fx-underline: true; +} \ No newline at end of file diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/add_subscriber.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/add_subscriber.fxml index 71f354d..5c918e7 100644 --- a/src/main/resources/org/to/telegramfinalproject/Fxml/add_subscriber.fxml +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/add_subscriber.fxml @@ -19,7 +19,7 @@ -