diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index e64917d..259fe86 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -679,4 +679,83 @@ public class GroupDatabase { // return result; // } + public static JSONObject getGroupInfo(UUID groupId, UUID viewerUuid) throws SQLException { + JSONObject result = new JSONObject(); + + try (Connection conn = ConnectionDb.connect()) { + // === Group header (name, member count, etc.) + String groupQuery = """ + SELECT g.internal_uuid, g.group_id, g.group_name, g.image_url, + COUNT(m.user_id) as member_count + FROM groups g + LEFT JOIN group_members m ON g.internal_uuid = m.group_id + WHERE g.internal_uuid = ? + GROUP BY g.internal_uuid, g.group_id, g.group_name, g.image_url + """; + + try (PreparedStatement ps = conn.prepareStatement(groupQuery)) { + ps.setObject(1, groupId); // ✅ compare UUID with UUID + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + result.put("internal_uuid", rs.getString("internal_uuid")); + result.put("group_id", rs.getString("group_id")); // varchar handle + result.put("group_name", rs.getString("group_name")); + result.put("member_count", rs.getInt("member_count")); + result.put("image_url", rs.getString("image_url")); + } else { + return null; // no such group + } + } + + // === Members (id, name, role, status, image) + JSONArray membersArr = new JSONArray(); + + String membersQuery = """ + SELECT u.internal_uuid, u.profile_name, u.user_id, u.image_url, + gm.role, u.status, u.last_seen + FROM group_members gm + JOIN users u ON gm.user_id = u.internal_uuid + WHERE gm.group_id = ? + """; + + try (PreparedStatement ps = conn.prepareStatement(membersQuery)) { + ps.setObject(1, groupId); // ✅ gm.group_id is UUID + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + JSONObject member = new JSONObject(); + member.put("user_id", rs.getString("internal_uuid")); + member.put("profile_name", rs.getString("profile_name")); + member.put("username", rs.getString("user_id")); + member.put("image_url", rs.getString("image_url")); + member.put("role", rs.getString("role")); + member.put("status", rs.getString("status")); + member.put("last_seen", rs.getString("last_seen")); + membersArr.put(member); + } + } + + result.put("members", membersArr); + + // === Viewer role (to decide if they can delete group, etc.) + String roleQuery = """ + SELECT role + FROM group_members + WHERE group_id = ? AND user_id = ? + """; + + try (PreparedStatement ps = conn.prepareStatement(roleQuery)) { + ps.setObject(1, groupId); + ps.setObject(2, viewerUuid); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + result.put("my_role", rs.getString("role")); + } else { + result.put("my_role", ""); // not a member + } + } + } + + return result; + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/HelloApplication.java b/src/main/java/org/to/telegramfinalproject/HelloApplication.java deleted file mode 100644 index 5751500..0000000 --- a/src/main/java/org/to/telegramfinalproject/HelloApplication.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.to.telegramfinalproject; - -import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Scene; -import javafx.stage.Stage; - -import java.io.IOException; - -public class HelloApplication extends Application { - @Override - public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 320, 240); - stage.setTitle("Hello!"); - stage.setScene(scene); - stage.show(); - } - - public static void main(String[] args) { - launch(); - } -} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/HelloController.java b/src/main/java/org/to/telegramfinalproject/HelloController.java deleted file mode 100644 index 0641baf..0000000 --- a/src/main/java/org/to/telegramfinalproject/HelloController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.to.telegramfinalproject; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; - -public class HelloController { - @FXML - private Label welcomeText; - - @FXML - protected void onHelloButtonClick() { - welcomeText.setText("Welcome to JavaFX Application!"); - } -} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index c2a6ca8..a3c63fe 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -3005,7 +3005,29 @@ public class ClientHandler implements Runnable { break; } + case "view_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + try { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID viewerId = UUID.fromString(requestJson.getString("viewer_id")); + + // Query group details + JSONObject groupData = GroupDatabase.getGroupInfo(groupId, viewerId); + + if (groupData == null) { + response = new ResponseModel("error", "Group not found."); + } else { + response = new ResponseModel("success", "Group info fetched.", groupData); + } + } catch (Exception e) { + response = new ResponseModel("error", "Error processing group info: " + e.getMessage()); + } + break; + } default: diff --git a/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java index 9f25c6e..4e62c3a 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/AddMembersController.java @@ -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 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() { diff --git a/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java index e92249a..1539b5c 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/BlockedUsersController.java @@ -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() ); diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index 38ed2dd..a5546e3 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -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 diff --git a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java index e0d4a76..fcf9d60 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/GroupInfoController.java @@ -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()); } } 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/ManageMembersController.java b/src/main/java/org/to/telegramfinalproject/UI/ManageMembersController.java new file mode 100644 index 0000000..bb50218 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/ManageMembersController.java @@ -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 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 + ); + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java index 620b4ab..7ebe0ca 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/NewChannelController.java @@ -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; } 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/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index 83ee30c..9d25297 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/TelegramFeaturesController.java b/src/main/java/org/to/telegramfinalproject/UI/TelegramFeaturesController.java new file mode 100644 index 0000000..a3d4c1c --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/TelegramFeaturesController.java @@ -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()); + } + }); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/TelegramQnAController.java b/src/main/java/org/to/telegramfinalproject/UI/TelegramQnAController.java new file mode 100644 index 0000000..c98bcfe --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/TelegramQnAController.java @@ -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()); + } + }); + } +} \ No newline at end of file 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 0e28a60..a1fa9df 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -1133,17 +1133,234 @@ -fx-border-width: 1; } -/* ===== Dark theme overrides ===== */ -.root.dark .tf-title { -fx-text-fill: #f3f4f6; } -.root.dark .tf-caption { -fx-text-fill: #b8bdc7; } -.root.dark .tf-item-desc { -fx-text-fill: #9ca3af; } - -/* TitledPane header/content */ -.root.dark .titled-pane > .title { - -fx-background-color: #2b2f33; - -fx-text-fill: #f3f4f6; -} -.root.dark .titled-pane > *.content { - -fx-background-color: #1f2327; +.member-row { + -fx-alignment: CENTER_LEFT; } +.member-scroll { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.profile-card { + -fx-background-radius: 12; + -fx-background-color: #1f2a36; /* dark gray card */ + -fx-padding: 16; +} + +.profile-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* light text */ +} + + +.member-row { + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* Telegram light text */ +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; /* grayish */ +} + +.member-role { + -fx-font-size: 12px; + -fx-text-fill: #4fa8f0; /* blue */ + -fx-font-weight: bold; +} + +/* Group name (bold, light text) */ +.info-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #e8f1f8; /* light gray/white */ +} + +/* Members count (blue link style like Telegram dark mode) */ +.info-subtitle { + -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; +} + +.overlay-background { + -fx-background-color: rgba(0,0,0,0.4); +} + +.tf-root { + -fx-background-color: #1f2a36; + -fx-background-radius: 10; + -fx-padding: 16; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.5), 10, 0, 0, 4); +} + +.tf-toolbar { + -fx-padding: 8 12 8 12; +} + +.tf-title { + -fx-font-size: 18px; + -fx-font-weight: bold; + -fx-text-fill: #229ED9; /* Telegram blue */ +} + +.tf-caption { + -fx-font-size: 12px; + -fx-text-fill: #aaa; +} + +.tf-section { + -fx-spacing: 8; + -fx-padding: 8 0 8 0; +} + +/* ScrollPane backgrounds */ +.scroll-pane { + -fx-background-color: transparent; + -fx-background: transparent; +} + +.scroll-pane .viewport { + -fx-background-color: transparent; +} + +.tf-sections { + -fx-background-color: #1f2a36; /* same as .tf-root */ +} + +/* Section + item text colors */ +.tf-section-title { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #ffffff; + -fx-padding: 4 0 2 0; + -fx-border-color: #333; + -fx-border-width: 0 0 1 0; +} + +.tf-item-title { + -fx-font-weight: bold; + -fx-font-size: 13px; + -fx-text-fill: #f5f5f5; +} + +.tf-item-desc { + -fx-font-size: 12px; + -fx-text-fill: #cccccc; +} + +.tf-status { + -fx-font-size: 11px; + -fx-padding: 2 6 2 6; + -fx-background-radius: 6; +} +.tf-status-available { + -fx-text-fill: #a5d6a7; + -fx-background-color: #1b5e20; +} +.tf-status-progress { + -fx-text-fill: #ffb74d; + -fx-background-color: #e65100; +} 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 0a38750..fe18596 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -1124,38 +1124,220 @@ -fx-border-width: 1; } -/* ===== Base (shared) ===== */ -.tf-root { -fx-background-color: transparent; } - -.tf-toolbar { -fx-padding: 10 12; -fx-alignment: CENTER_LEFT; } -.tf-title { -fx-font-size: 18px; -fx-font-weight: 700; } -.tf-caption { -fx-opacity: .75; } - -.tf-section { -fx-padding: 10 12 14 12; } -.tf-item-desc { -fx-wrap-text: true; -fx-font-size: 13px; -fx-line-spacing: 1; } - -/* TitledPane (Accordion items) */ -.titled-pane > .title { - -fx-padding: 10 12; - -fx-background-radius: 10; -} -.titled-pane > *.content { - -fx-background-radius: 10; - -fx-background-insets: 0; - -fx-padding: 8 4 12 4; +.member-row { + -fx-alignment: CENTER_LEFT; } -/* ===== Light theme overrides ===== */ -.root.light .tf-title { -fx-text-fill: #111111; } -.root.light .tf-caption { -fx-text-fill: #555555; } -.root.light .tf-item-desc { -fx-text-fill: #444444; } - -/* TitledPane header/content */ -.root.light .titled-pane > .title { - -fx-background-color: #f1f1f1; - -fx-text-fill: #111111; +.member-scroll { + -fx-background-color: transparent; + -fx-border-color: transparent; } -.root.light .titled-pane > *.content { + +.profile-card { + -fx-background-radius: 12; + -fx-background-color: #ffffff; /* pure white card */ + -fx-padding: 16; +} + +.profile-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* dark text */ +} + +.member-row { + -fx-padding: 6 0; +} + +.member-name { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* Telegram dark gray for names */ +} + +.member-status { + -fx-font-size: 12px; + -fx-text-fill: #8ea1b2; /* grayish */ +} + +.member-role { + -fx-font-size: 12px; + -fx-text-fill: #4fa8f0; /* blue */ + -fx-font-weight: bold; +} + +/* Group name (bold, dark text) */ +.info-title { + -fx-font-size: 16px; + -fx-font-weight: bold; + -fx-text-fill: #0f141a; /* deep gray */ +} + +/* Members count (blue link style like Telegram) */ +.info-subtitle { + -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; +} + +.overlay-background { + -fx-background-color: rgba(0,0,0,0.4); +} + +.tf-root { -fx-background-color: #ffffff; + -fx-background-radius: 10; + -fx-padding: 16; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 10, 0, 0, 4); } +.tf-toolbar { + -fx-padding: 8 12 8 12; +} + +.tf-title { + -fx-font-size: 18px; + -fx-font-weight: bold; + -fx-text-fill: #229ED9; /* Telegram blue */ +} + +.tf-caption { + -fx-font-size: 12px; + -fx-text-fill: #888; +} + + +/* ScrollPane backgrounds */ +.scroll-pane { + -fx-background-color: transparent; + -fx-background: transparent; +} + +.scroll-pane .viewport { + -fx-background-color: transparent; +} + +.tf-sections { + -fx-background-color: transparent; +} + +.tf-section { + -fx-spacing: 8; + -fx-padding: 8 0 8 0; +} + +.tf-section-title { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #2c2c2c; + -fx-padding: 4 0 2 0; + -fx-border-color: #ddd; + -fx-border-width: 0 0 1 0; +} + +.tf-item-title { + -fx-font-weight: bold; + -fx-font-size: 13px; +} + +.tf-item-desc { + -fx-font-size: 12px; + -fx-text-fill: #555; +} + +.tf-status { + -fx-font-size: 11px; + -fx-padding: 2 6 2 6; + -fx-background-radius: 6; +} +.tf-status-available { + -fx-text-fill: #2e7d32; + -fx-background-color: #e8f5e9; +} +.tf-status-progress { + -fx-text-fill: #e65100; + -fx-background-color: #fff3e0; +} 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 @@ -