Create group avd channel in UI
This commit is contained in:
@@ -54,6 +54,15 @@ public class ChatEntry {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChatEntry(UUID internalId, String type, String name, String displayId) {
|
||||||
|
this.internalId = internalId;
|
||||||
|
this.type = type;
|
||||||
|
this.displayId = displayId;
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isOwner() {
|
public boolean isOwner() {
|
||||||
return isOwner;
|
return isOwner;
|
||||||
}
|
}
|
||||||
@@ -74,6 +83,7 @@ public class ChatEntry {
|
|||||||
return internalId;
|
return internalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getDisplayId() {
|
public String getDisplayId() {
|
||||||
return displayId;
|
return displayId;
|
||||||
}
|
}
|
||||||
@@ -180,4 +190,21 @@ public class ChatEntry {
|
|||||||
this.lastMessageTime = t;
|
this.lastMessageTime = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ChatEntry fromServer(UUID internalId,
|
||||||
|
String type,
|
||||||
|
String name,
|
||||||
|
String displayId,
|
||||||
|
String imageUrl,
|
||||||
|
boolean isOwner,
|
||||||
|
boolean isAdmin) {
|
||||||
|
ChatEntry e = new ChatEntry(internalId, type, name, displayId); // اگر سازندهات فرق دارد، مطابق آن بساز
|
||||||
|
e.setImageUrl(imageUrl);
|
||||||
|
e.setOwner(isOwner);
|
||||||
|
e.setAdmin(isAdmin);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -887,24 +887,28 @@ public class ClientHandler implements Runnable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String channelId = requestJson.getString("channel_id");
|
String channelId = requestJson.getString("channel_id");
|
||||||
String channelName = requestJson.getString("channel_name");
|
String channelName = requestJson.getString("channel_name");
|
||||||
String userIdStr = requestJson.getString("user_id");
|
String userIdStr = requestJson.getString("user_id");
|
||||||
String imageUrl = requestJson.optString("image_url", null);
|
String imageUrl = requestJson.optString("image_url", null);
|
||||||
|
String description = requestJson.optString("description", null); // اختیاری
|
||||||
|
|
||||||
UUID creatorUUID = UUID.fromString(userIdStr);
|
UUID creatorUUID = UUID.fromString(userIdStr);
|
||||||
|
|
||||||
|
// اگر سرویسات ورودی توضیح را میپذیرد، از متد اورلودشده استفاده کن:
|
||||||
|
// boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
|
||||||
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
|
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
|
||||||
|
|
||||||
if (created) {
|
if (created) {
|
||||||
Channel createdChannel = ChannelDatabase.findByChannelId(channelId);
|
Channel createdChannel = ChannelDatabase.findByChannelId(channelId);
|
||||||
if (createdChannel != null) {
|
if (createdChannel != null) {
|
||||||
JSONObject data = new JSONObject();
|
org.json.JSONObject data = new org.json.JSONObject();
|
||||||
data.put("internal_id", createdChannel.getInternal_uuid().toString());
|
data.put("internal_id", createdChannel.getInternal_uuid().toString());
|
||||||
data.put("id", createdChannel.getChannel_id());
|
data.put("id", createdChannel.getChannel_id());
|
||||||
data.put("name", createdChannel.getChannel_name());
|
data.put("name", createdChannel.getChannel_name());
|
||||||
data.put("image_url", createdChannel.getImage_url());
|
data.put("image_url", createdChannel.getImage_url());
|
||||||
data.put("type", "channel");
|
data.put("type", "channel");
|
||||||
|
if (description != null) data.put("description", description);
|
||||||
|
|
||||||
response = new ResponseModel("success", "Channel created.", data);
|
response = new ResponseModel("success", "Channel created.", data);
|
||||||
} else {
|
} else {
|
||||||
@@ -1163,44 +1167,104 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case "add_member_to_group": {
|
// case "add_member_to_group": {
|
||||||
|
// if (currentUser == null) {
|
||||||
|
// response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// UUID groupId = UUID.fromString(requestJson.getString("group_id"));
|
||||||
|
// UUID targetUserId = UUID.fromString(requestJson.getString("user_id"));
|
||||||
|
//
|
||||||
|
// if (!GroupPermissionUtil.canAddMembers(groupId, currentUser.getInternal_uuid())) {
|
||||||
|
// response = new ResponseModel("error", "You are not allowed to add members.");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (GroupDatabase.isUserInGroup(targetUserId, groupId)) {
|
||||||
|
// response = new ResponseModel("error", "User is already a member.");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// boolean success = GroupDatabase.addMemberToGroup(targetUserId, groupId);
|
||||||
|
//
|
||||||
|
// Group group = GroupDatabase.findByInternalUUID(groupId);
|
||||||
|
//
|
||||||
|
// //RealTime
|
||||||
|
// if (success && group != null) {
|
||||||
|
// RealTimeEventDispatcher.notifyAddedToChat(
|
||||||
|
// "group",
|
||||||
|
// group.getInternal_uuid(),
|
||||||
|
// group.getGroup_name(),
|
||||||
|
// group.getImage_url(),
|
||||||
|
// targetUserId
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// response = success
|
||||||
|
// ? new ResponseModel("success", "Member added to group.")
|
||||||
|
// : new ResponseModel("error", "Failed to add member.");
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
case "add_members_to_group": {
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
UUID groupId = UUID.fromString(requestJson.getString("group_id"));
|
UUID groupId = UUID.fromString(requestJson.getString("group_id"));
|
||||||
UUID targetUserId = UUID.fromString(requestJson.getString("user_id"));
|
|
||||||
|
|
||||||
if (!GroupPermissionUtil.canAddMembers(groupId, currentUser.getInternal_uuid())) {
|
if (!GroupPermissionUtil.canAddMembers(groupId, currentUser.getInternal_uuid())) {
|
||||||
response = new ResponseModel("error", "You are not allowed to add members.");
|
response = new ResponseModel("error", "You are not allowed to add members.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GroupDatabase.isUserInGroup(targetUserId, groupId)) {
|
org.json.JSONArray arr = requestJson.optJSONArray("user_ids");
|
||||||
response = new ResponseModel("error", "User is already a member.");
|
if (arr == null || arr.isEmpty()) {
|
||||||
|
response = new ResponseModel("error", "user_ids is empty.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean success = GroupDatabase.addMemberToGroup(targetUserId, groupId);
|
|
||||||
|
|
||||||
Group group = GroupDatabase.findByInternalUUID(groupId);
|
Group group = GroupDatabase.findByInternalUUID(groupId);
|
||||||
|
if (group == null) {
|
||||||
//RealTime
|
response = new ResponseModel("error", "Group not found.");
|
||||||
if (success && group != null) {
|
break;
|
||||||
RealTimeEventDispatcher.notifyAddedToChat(
|
|
||||||
"group",
|
|
||||||
group.getInternal_uuid(),
|
|
||||||
group.getGroup_name(),
|
|
||||||
group.getImage_url(),
|
|
||||||
targetUserId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response = success
|
int added = 0, skipped = 0, failed = 0;
|
||||||
? new ResponseModel("success", "Member added to group.")
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
: new ResponseModel("error", "Failed to add member.");
|
try {
|
||||||
break;
|
UUID targetUserId = UUID.fromString(arr.getString(i));
|
||||||
|
if (GroupDatabase.isUserInGroup(targetUserId, groupId)) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean ok = GroupDatabase.addMemberToGroup(targetUserId, groupId);
|
||||||
|
if (ok) {
|
||||||
|
added++;
|
||||||
|
RealTimeEventDispatcher.notifyAddedToChat(
|
||||||
|
"group",
|
||||||
|
group.getInternal_uuid(),
|
||||||
|
group.getGroup_name(),
|
||||||
|
group.getImage_url(),
|
||||||
|
targetUserId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = new JSONObject()
|
||||||
|
.put("added", added)
|
||||||
|
.put("skipped", skipped)
|
||||||
|
.put("failed", failed)
|
||||||
|
.put("group_id", group.getInternal_uuid().toString());
|
||||||
|
|
||||||
|
response = new ResponseModel("success", "Batch add finished.", data);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package org.to.telegramfinalproject.UI;
|
package org.to.telegramfinalproject.UI;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -25,68 +29,23 @@ public class AddMembersController {
|
|||||||
@FXML private Button cancelButton;
|
@FXML private Button cancelButton;
|
||||||
@FXML private Button createButton;
|
@FXML private Button createButton;
|
||||||
|
|
||||||
// Keep selected contacts
|
// گروه هدف
|
||||||
private final Set<Contact> selectedContacts = new HashSet<>();
|
private UUID groupInternalId;
|
||||||
|
|
||||||
private String groupName;
|
private String groupName;
|
||||||
private String groupId;
|
private String groupDisplayId;
|
||||||
private File groupImageFile;
|
private File groupImageFile;
|
||||||
|
|
||||||
// Sample data for testing
|
// انتخابها
|
||||||
private final List<Contact> allContacts = Arrays.asList(
|
private final Set<Contact> selectedContacts = new HashSet<>();
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
// همهی کانتکتها
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
private final List<Contact> allContacts = new ArrayList<>();
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
updateMemberCount();
|
// اسکرول نرم
|
||||||
|
contactsScroll.getStylesheets().add(
|
||||||
// Sort + render initially
|
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||||
List<Contact> sorted = allContacts.stream()
|
);
|
||||||
.sorted(Comparator.comparing(Contact::getName))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
renderContacts(sorted);
|
|
||||||
|
|
||||||
// Search filter
|
|
||||||
searchField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
String filter = newVal.toLowerCase();
|
|
||||||
List<Contact> filtered = allContacts.stream()
|
|
||||||
.filter(c -> c.getName().toLowerCase().contains(filter))
|
|
||||||
.sorted(Comparator.comparing(Contact::getName))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
renderContacts(filtered);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-focus
|
|
||||||
Platform.runLater(() -> searchField.requestFocus());
|
|
||||||
|
|
||||||
// Cancel closes overlay
|
|
||||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
|
||||||
|
|
||||||
// Close when clicking outside
|
|
||||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
|
||||||
|
|
||||||
// Create action
|
|
||||||
createButton.setOnAction(e -> {
|
|
||||||
if (selectedContacts.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createGroup();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Smooth scroll
|
|
||||||
contactsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
|
||||||
contactsScroll.setPannable(true);
|
contactsScroll.setPannable(true);
|
||||||
contactsScroll.setFitToWidth(true);
|
contactsScroll.setFitToWidth(true);
|
||||||
contactsScroll.setFitToHeight(false);
|
contactsScroll.setFitToHeight(false);
|
||||||
@@ -95,64 +54,78 @@ public class AddMembersController {
|
|||||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme icon handling
|
// آیکن سرچ با تم
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (addMembersCard.getScene() != null) {
|
if (addMembersCard.getScene() != null) {
|
||||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal));
|
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||||
|
|
||||||
|
// بستن اُورلی
|
||||||
|
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||||
|
|
||||||
|
// دکمه Add (افزودن اعضا)
|
||||||
|
createButton.setOnAction(e -> onAddMembers());
|
||||||
|
|
||||||
|
// سرچ
|
||||||
|
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
|
||||||
|
|
||||||
|
Platform.runLater(() -> searchField.requestFocus());
|
||||||
|
|
||||||
|
// دیتا را از Session بارگذاری کن
|
||||||
|
loadContactsFromSession();
|
||||||
|
updateMemberCount();
|
||||||
|
renderContacts(allContacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createGroup() {
|
// این متد را NewGroupController بعد از ساخت گروه صدا بزند
|
||||||
// String groupName = this.groupName; // set earlier from setGroupInfo()
|
public void setGroupInfo(UUID internalId, String groupName, String displayId, File groupImageFile) {
|
||||||
// File groupImage = this.groupImageFile; // also passed earlier
|
this.groupInternalId = internalId;
|
||||||
//
|
this.groupName = groupName;
|
||||||
// // Collect selected members
|
this.groupDisplayId = displayId;
|
||||||
// List<String> memberIds = selectedContacts.stream()
|
this.groupImageFile = groupImageFile;
|
||||||
// .map(Contact::getId) // you need some unique identifier for contacts
|
}
|
||||||
// .collect(Collectors.toList());
|
|
||||||
//
|
// — اگر هنوز امضای قدیمی را صدا میزنی، موقتاً این اوِرلود هست (displayId را میگیرد اما internal_id لازم است) —
|
||||||
// // Build JSON payload for server
|
public void setGroupInfo(String groupName, String groupId, File groupImageFile) {
|
||||||
// JSONObject req = new JSONObject();
|
// ⚠️ فقط برای سازگاری موقت؛ حتماً NewGroupController را طوری بهروزرسانی کن
|
||||||
// req.put("action", "create_group");
|
// که internal_id را بدهد (امضای بالایی).
|
||||||
// req.put("name", groupName);
|
this.groupName = groupName;
|
||||||
// req.put("members", memberIds);
|
this.groupDisplayId = groupId;
|
||||||
//
|
this.groupImageFile = groupImageFile;
|
||||||
// if (groupImage != null) {
|
}
|
||||||
// req.put("image_path", groupImage.getAbsolutePath());
|
|
||||||
// // or upload the file separately depending on your backend design
|
private void loadContactsFromSession() {
|
||||||
// }
|
allContacts.clear();
|
||||||
//
|
var u = Session.currentUser;
|
||||||
// try {
|
var arr = (u == null) ? null : u.optJSONArray("contact_list");
|
||||||
// JSONObject res = NetworkClient.sendWithResponse(req); // your socket wrapper
|
if (arr != null) {
|
||||||
// if ("success".equals(res.getString("status"))) {
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
// // Get new group chat ID from server
|
var c = arr.optJSONObject(i);
|
||||||
// String chatId = res.getString("chat_id");
|
if (c == null) continue;
|
||||||
//
|
String name = c.optString("profile_name", "");
|
||||||
// // ✅ Close overlay
|
String id = c.optString("contact_id", ""); // ← internal_uuid مخاطب
|
||||||
// MainController.getInstance().closeOverlay(overlayRoot);
|
if (id.isBlank()) continue;
|
||||||
//
|
String imageUrl = c.optString("image_url",
|
||||||
// // ✅ Open chat immediately
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||||
// FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
String status = Optional.ofNullable(c.optString("last_seen", ""))
|
||||||
// "/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
.filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse("");
|
||||||
// Node chatPage = loader.load();
|
allContacts.add(new Contact(id, name, status, imageUrl));
|
||||||
//
|
}
|
||||||
// ChatPageController chatController = loader.getController();
|
}
|
||||||
// chatController.setChat(groupName,
|
// اگر خالی بود، چیزی نشون نده (یا میتونی دمو بسازی)
|
||||||
// groupImage != null ? groupImage.toURI().toString()
|
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
|
||||||
// : "/org/to/telegramfinalproject/Avatars/default_group.png");
|
}
|
||||||
//
|
|
||||||
// MainController.getInstance().getChatDisplayArea().getChildren().setAll(chatPage);
|
private void applyFilter(String q) {
|
||||||
//
|
String f = (q == null) ? "" : q.trim().toLowerCase(Locale.ROOT);
|
||||||
// } else {
|
List<Contact> filtered = allContacts.stream()
|
||||||
// showAlert("Failed to create group: " + res.getString("message"));
|
.filter(c -> c.getName().toLowerCase(Locale.ROOT).contains(f))
|
||||||
// }
|
.collect(Collectors.toList());
|
||||||
// } catch (Exception ex) {
|
renderContacts(filtered);
|
||||||
// ex.printStackTrace();
|
|
||||||
// showAlert("Error creating group.");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderContacts(List<Contact> contacts) {
|
private void renderContacts(List<Contact> contacts) {
|
||||||
@@ -160,12 +133,10 @@ public class AddMembersController {
|
|||||||
|
|
||||||
if (contacts.isEmpty()) {
|
if (contacts.isEmpty()) {
|
||||||
StackPane emptyPane = new StackPane();
|
StackPane emptyPane = new StackPane();
|
||||||
emptyPane.setPrefHeight(300);
|
emptyPane.setPrefHeight(240);
|
||||||
emptyPane.setAlignment(Pos.CENTER);
|
emptyPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
Label emptyLabel = new Label("No contacts found");
|
Label emptyLabel = new Label("No contacts found");
|
||||||
emptyLabel.getStyleClass().add("no-contacts-label");
|
emptyLabel.getStyleClass().add("no-contacts-label");
|
||||||
|
|
||||||
emptyPane.getChildren().add(emptyLabel);
|
emptyPane.getChildren().add(emptyLabel);
|
||||||
contactsList.getChildren().add(emptyPane);
|
contactsList.getChildren().add(emptyPane);
|
||||||
return;
|
return;
|
||||||
@@ -175,10 +146,8 @@ public class AddMembersController {
|
|||||||
HBox item = new HBox(10);
|
HBox item = new HBox(10);
|
||||||
item.getStyleClass().add("contact-item");
|
item.getStyleClass().add("contact-item");
|
||||||
|
|
||||||
// Avatar
|
// آواتار
|
||||||
ImageView avatar = new ImageView(new Image(
|
ImageView avatar = new ImageView(loadAvatar(c.getImageUrl()));
|
||||||
Objects.requireNonNull(getClass().getResourceAsStream(c.getImageUrl()))
|
|
||||||
));
|
|
||||||
avatar.setFitWidth(48);
|
avatar.setFitWidth(48);
|
||||||
avatar.setFitHeight(48);
|
avatar.setFitHeight(48);
|
||||||
avatar.setPreserveRatio(true);
|
avatar.setPreserveRatio(true);
|
||||||
@@ -188,32 +157,100 @@ public class AddMembersController {
|
|||||||
nameLabel.getStyleClass().add("contact-name");
|
nameLabel.getStyleClass().add("contact-name");
|
||||||
Label statusLabel = new Label(c.getStatus());
|
Label statusLabel = new Label(c.getStatus());
|
||||||
statusLabel.getStyleClass().add("contact-status");
|
statusLabel.getStyleClass().add("contact-status");
|
||||||
|
|
||||||
details.getChildren().addAll(nameLabel, statusLabel);
|
details.getChildren().addAll(nameLabel, statusLabel);
|
||||||
|
|
||||||
item.getChildren().addAll(avatar, details);
|
Region spacer = new Region();
|
||||||
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
|
|
||||||
// Click to toggle selection
|
CheckBox cb = new CheckBox();
|
||||||
item.setOnMouseClicked(e -> toggleSelection(c));
|
cb.setSelected(selectedContacts.contains(c));
|
||||||
|
cb.selectedProperty().addListener((obs, ov, nv) -> {
|
||||||
|
if (nv) selectedContacts.add(c); else selectedContacts.remove(c);
|
||||||
|
updateMemberCount();
|
||||||
|
updateSelectedMembersPane();
|
||||||
|
// برای هایلایت
|
||||||
|
if (nv) item.getStyleClass().add("contact-selected");
|
||||||
|
else item.getStyleClass().remove("contact-selected");
|
||||||
|
});
|
||||||
|
|
||||||
// Highlight if already selected
|
item.getChildren().addAll(avatar, details, spacer, cb);
|
||||||
if (selectedContacts.contains(c)) {
|
|
||||||
item.getStyleClass().add("contact-selected");
|
// کلیک روی ردیف = toggle
|
||||||
}
|
item.setOnMouseClicked(e -> {
|
||||||
|
boolean newVal = !cb.isSelected();
|
||||||
|
cb.setSelected(newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
// هایلایت انتخابشده
|
||||||
|
if (selectedContacts.contains(c)) item.getStyleClass().add("contact-selected");
|
||||||
|
|
||||||
contactsList.getChildren().add(item);
|
contactsList.getChildren().add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSelection(Contact contact) {
|
private Image loadAvatar(String path) {
|
||||||
if (selectedContacts.contains(contact)) {
|
try {
|
||||||
selectedContacts.remove(contact);
|
// 1) اگر ریسورس داخلی باشد
|
||||||
} else {
|
var in = getClass().getResourceAsStream(path);
|
||||||
selectedContacts.add(contact);
|
if (in != null) return new Image(in);
|
||||||
|
|
||||||
|
// 2) اگر URL کامل یا file URI
|
||||||
|
return new Image(path, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new Image(
|
||||||
|
Objects.requireNonNull(
|
||||||
|
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
updateMemberCount();
|
}
|
||||||
updateSelectedMembersPane();
|
|
||||||
renderContacts(allContacts);
|
private void onAddMembers() {
|
||||||
|
if (groupInternalId == null) {
|
||||||
|
showToast("Group internal_id is missing. Make sure setGroupInfo(UUID, ...) was called.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedContacts.isEmpty()) {
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> ids = selectedContacts.stream().map(Contact::getId).toList();
|
||||||
|
|
||||||
|
// تلاش برای batch
|
||||||
|
JSONObject batchReq = new JSONObject()
|
||||||
|
.put("action", "add_members_to_group")
|
||||||
|
.put("group_id", groupInternalId.toString())
|
||||||
|
.put("user_ids", new JSONArray(ids));
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(batchReq);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
showToast("Members added.");
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: تکبهتک
|
||||||
|
boolean allOk = true;
|
||||||
|
for (String uid : ids) {
|
||||||
|
JSONObject single = new JSONObject()
|
||||||
|
.put("action", "add_member_to_group")
|
||||||
|
.put("group_id", groupInternalId.toString())
|
||||||
|
.put("user_id", uid);
|
||||||
|
JSONObject r = ActionHandler.sendWithResponse(single);
|
||||||
|
if (r == null || !"success".equalsIgnoreCase(r.optString("status"))) {
|
||||||
|
allOk = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean finalAllOk = allOk;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
showToast(finalAllOk ? "Members added." : "Some members failed.");
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectedMembersPane() {
|
private void updateSelectedMembersPane() {
|
||||||
@@ -233,39 +270,41 @@ public class AddMembersController {
|
|||||||
String iconPath = darkMode
|
String iconPath = darkMode
|
||||||
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
||||||
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
||||||
|
|
||||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||||
icon.setFitWidth(16);
|
icon.setFitWidth(16);
|
||||||
icon.setFitHeight(16);
|
icon.setFitHeight(16);
|
||||||
searchIcon.setGraphic(icon);
|
searchIcon.setGraphic(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGroupInfo(String groupName, String groupId, File groupImageFile) {
|
private void showToast(String msg) {
|
||||||
this.groupName = groupName;
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
this.groupId = groupId;
|
a.initOwner(addMembersCard.getScene().getWindow());
|
||||||
this.groupImageFile = groupImageFile;
|
a.show();
|
||||||
|
|
||||||
// You can use these later when creating the group
|
|
||||||
System.out.println("Group name passed: " + groupName);
|
|
||||||
if (groupImageFile != null) {
|
|
||||||
System.out.println("Group image: " + groupImageFile.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner class for contact data
|
// ================== مدل Contact ==================
|
||||||
public static class Contact {
|
public static class Contact {
|
||||||
|
private final String id; // internal_uuid کاربر
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String status;
|
private final String status;
|
||||||
private final String imageUrl;
|
private final String imageUrl;
|
||||||
|
|
||||||
public Contact(String name, String status, String imageUrl) {
|
public Contact(String id, String name, String status, String imageUrl) {
|
||||||
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.imageUrl = imageUrl;
|
this.imageUrl = imageUrl;
|
||||||
}
|
}
|
||||||
|
public String getId() { return id; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
public String getStatus() { return status; }
|
public String getStatus() { return status; }
|
||||||
public String getImageUrl() { return imageUrl; }
|
public String getImageUrl() { return imageUrl; }
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Contact c)) return false;
|
||||||
|
return Objects.equals(id, c.id);
|
||||||
|
}
|
||||||
|
@Override public int hashCode() { return Objects.hash(id); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -25,69 +29,23 @@ public class AddSubscriberController {
|
|||||||
@FXML private Button skipButton;
|
@FXML private Button skipButton;
|
||||||
@FXML private Button addButton;
|
@FXML private Button addButton;
|
||||||
|
|
||||||
// Keep selected contacts
|
// اطلاعات کانال
|
||||||
private final Set<Contact> selectedContacts = new HashSet<>();
|
private UUID channelInternalId;
|
||||||
|
|
||||||
private String channelName;
|
private String channelName;
|
||||||
private String channelId;
|
private String channelDisplayId;
|
||||||
private File channelImageFile;
|
private File channelImageFile;
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
// Sample data for testing
|
// انتخابها و منبع داده
|
||||||
private final List<Contact> allContacts = Arrays.asList(
|
private final Set<Contact> selected = new HashSet<>();
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
private final List<Contact> allContacts = new ArrayList<>();
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Ali", "last seen recently", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Iman", "last seen a long time ago", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Amir", "last seen within a month", "/org/to/telegramfinalproject/Avatars/default_user_profile.png"),
|
|
||||||
new Contact("Sara", "online", "/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
updateMemberCount();
|
// اسکرول نرم
|
||||||
|
contactsScroll.getStylesheets().add(
|
||||||
// Sort + render initially
|
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||||
renderContacts(allContacts.stream()
|
);
|
||||||
.sorted(Comparator.comparing(Contact::getName))
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
|
|
||||||
// Search filter
|
|
||||||
searchField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
String filter = newVal.toLowerCase();
|
|
||||||
List<Contact> filtered = allContacts.stream()
|
|
||||||
.filter(c -> c.getName().toLowerCase().contains(filter))
|
|
||||||
.sorted(Comparator.comparing(Contact::getName))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
renderContacts(filtered);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-focus
|
|
||||||
Platform.runLater(() -> searchField.requestFocus());
|
|
||||||
|
|
||||||
// Skip → ignore selection and create chat
|
|
||||||
skipButton.setOnAction(e -> {
|
|
||||||
createChannel(); // always creates, even if no members selected
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add → requires at least one member
|
|
||||||
addButton.setOnAction(e -> {
|
|
||||||
if (!selectedContacts.isEmpty()) {
|
|
||||||
createChannel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close when clicking outside
|
|
||||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
|
||||||
|
|
||||||
// Smooth scroll
|
|
||||||
contactsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
|
||||||
contactsScroll.setPannable(true);
|
contactsScroll.setPannable(true);
|
||||||
contactsScroll.setFitToWidth(true);
|
contactsScroll.setFitToWidth(true);
|
||||||
contactsScroll.setFitToHeight(false);
|
contactsScroll.setFitToHeight(false);
|
||||||
@@ -96,25 +54,74 @@ public class AddSubscriberController {
|
|||||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme icon handling
|
// تم آیکن
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (addMembersCard.getScene() != null) {
|
if (addMembersCard.getScene() != null) {
|
||||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal));
|
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||||
|
|
||||||
|
// بستن اُورلی
|
||||||
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||||
|
skipButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||||
|
addButton.setOnAction(e -> onAddSubscribers());
|
||||||
|
|
||||||
|
// سرچ
|
||||||
|
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
|
||||||
|
Platform.runLater(() -> searchField.requestFocus());
|
||||||
|
|
||||||
|
// لود کانتکتها از Session
|
||||||
|
loadContactsFromSession();
|
||||||
|
updateCount();
|
||||||
|
renderContacts(allContacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createChannel() {
|
/** NewChannelController این را بعد از ساخت کانال صدا بزند */
|
||||||
// TODO: real server request → for now just print and close overlay
|
public void setChannelInfo(UUID internalId, String name, String displayId, File imageFile, String description) {
|
||||||
System.out.println("✅ Creating group/channel: " + channelName);
|
this.channelInternalId = internalId;
|
||||||
System.out.println("Selected members: " + selectedContacts.stream()
|
this.channelName = name;
|
||||||
.map(Contact::getName).collect(Collectors.joining(", ")));
|
this.channelDisplayId = displayId;
|
||||||
|
this.channelImageFile = imageFile;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
// برای سازگاری با امضای قدیمی (اختیاری)
|
||||||
|
public void setChannelInfo(String name, String id, String description, File image) {
|
||||||
|
this.channelName = name;
|
||||||
|
this.channelDisplayId = id;
|
||||||
|
this.channelImageFile = image;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: open chat immediately (like you did with newGroup)
|
private void loadContactsFromSession() {
|
||||||
|
allContacts.clear();
|
||||||
|
var u = Session.currentUser;
|
||||||
|
var arr = (u == null) ? null : u.optJSONArray("contact_list");
|
||||||
|
if (arr != null) {
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
var c = arr.optJSONObject(i);
|
||||||
|
if (c == null) continue;
|
||||||
|
String name = c.optString("profile_name", "");
|
||||||
|
String id = c.optString("contact_id", ""); // internal_uuid
|
||||||
|
if (id.isBlank()) continue;
|
||||||
|
String imageUrl = c.optString("image_url",
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||||
|
String status = Optional.ofNullable(c.optString("last_seen", ""))
|
||||||
|
.filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse("");
|
||||||
|
allContacts.add(new Contact(id, name, status, imageUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFilter(String q) {
|
||||||
|
String f = (q == null) ? "" : q.trim().toLowerCase(Locale.ROOT);
|
||||||
|
List<Contact> filtered = allContacts.stream()
|
||||||
|
.filter(c -> c.getName().toLowerCase(Locale.ROOT).contains(f))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
renderContacts(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderContacts(List<Contact> contacts) {
|
private void renderContacts(List<Contact> contacts) {
|
||||||
@@ -122,12 +129,10 @@ public class AddSubscriberController {
|
|||||||
|
|
||||||
if (contacts.isEmpty()) {
|
if (contacts.isEmpty()) {
|
||||||
StackPane emptyPane = new StackPane();
|
StackPane emptyPane = new StackPane();
|
||||||
emptyPane.setPrefHeight(300);
|
emptyPane.setPrefHeight(240);
|
||||||
emptyPane.setAlignment(Pos.CENTER);
|
emptyPane.setAlignment(Pos.CENTER);
|
||||||
|
|
||||||
Label emptyLabel = new Label("No contacts found");
|
Label emptyLabel = new Label("No contacts found");
|
||||||
emptyLabel.getStyleClass().add("no-contacts-label");
|
emptyLabel.getStyleClass().add("no-contacts-label");
|
||||||
|
|
||||||
emptyPane.getChildren().add(emptyLabel);
|
emptyPane.getChildren().add(emptyLabel);
|
||||||
contactsList.getChildren().add(emptyPane);
|
contactsList.getChildren().add(emptyPane);
|
||||||
return;
|
return;
|
||||||
@@ -137,10 +142,7 @@ public class AddSubscriberController {
|
|||||||
HBox item = new HBox(10);
|
HBox item = new HBox(10);
|
||||||
item.getStyleClass().add("contact-item");
|
item.getStyleClass().add("contact-item");
|
||||||
|
|
||||||
// Avatar
|
ImageView avatar = new ImageView(loadAvatar(c.getImageUrl()));
|
||||||
ImageView avatar = new ImageView(new Image(
|
|
||||||
Objects.requireNonNull(getClass().getResourceAsStream(c.getImageUrl()))
|
|
||||||
));
|
|
||||||
avatar.setFitWidth(48);
|
avatar.setFitWidth(48);
|
||||||
avatar.setFitHeight(48);
|
avatar.setFitHeight(48);
|
||||||
avatar.setPreserveRatio(true);
|
avatar.setPreserveRatio(true);
|
||||||
@@ -150,79 +152,141 @@ public class AddSubscriberController {
|
|||||||
nameLabel.getStyleClass().add("contact-name");
|
nameLabel.getStyleClass().add("contact-name");
|
||||||
Label statusLabel = new Label(c.getStatus());
|
Label statusLabel = new Label(c.getStatus());
|
||||||
statusLabel.getStyleClass().add("contact-status");
|
statusLabel.getStyleClass().add("contact-status");
|
||||||
|
|
||||||
details.getChildren().addAll(nameLabel, statusLabel);
|
details.getChildren().addAll(nameLabel, statusLabel);
|
||||||
|
|
||||||
item.getChildren().addAll(avatar, details);
|
Region spacer = new Region();
|
||||||
|
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||||
|
|
||||||
// Click to toggle selection
|
CheckBox cb = new CheckBox();
|
||||||
item.setOnMouseClicked(e -> toggleSelection(c));
|
cb.setSelected(selected.contains(c));
|
||||||
|
cb.selectedProperty().addListener((obs, ov, nv) -> {
|
||||||
|
if (nv) selected.add(c); else selected.remove(c);
|
||||||
|
updateCount();
|
||||||
|
updateSelectedPane();
|
||||||
|
if (nv) item.getStyleClass().add("contact-selected");
|
||||||
|
else item.getStyleClass().remove("contact-selected");
|
||||||
|
});
|
||||||
|
|
||||||
// Highlight if already selected
|
item.getChildren().addAll(avatar, details, spacer, cb);
|
||||||
if (selectedContacts.contains(c)) {
|
|
||||||
item.getStyleClass().add("contact-selected");
|
item.setOnMouseClicked(e -> cb.setSelected(!cb.isSelected()));
|
||||||
}
|
|
||||||
|
if (selected.contains(c)) item.getStyleClass().add("contact-selected");
|
||||||
|
|
||||||
contactsList.getChildren().add(item);
|
contactsList.getChildren().add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSelection(Contact contact) {
|
private Image loadAvatar(String path) {
|
||||||
if (selectedContacts.contains(contact)) {
|
try {
|
||||||
selectedContacts.remove(contact);
|
var in = getClass().getResourceAsStream(path);
|
||||||
} else {
|
if (in != null) return new Image(in);
|
||||||
selectedContacts.add(contact);
|
return new Image(path, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new Image(
|
||||||
|
Objects.requireNonNull(
|
||||||
|
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
updateMemberCount();
|
|
||||||
updateSelectedMembersPane();
|
|
||||||
renderContacts(allContacts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectedMembersPane() {
|
private void onAddSubscribers() {
|
||||||
|
if (channelInternalId == null) {
|
||||||
|
showToast("Channel internal_id is missing. Make sure setChannelInfo(UUID, ...) was called.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selected.isEmpty()) {
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> ids = selected.stream().map(Contact::getId).toList();
|
||||||
|
|
||||||
|
JSONObject batchReq = new JSONObject()
|
||||||
|
.put("action", "add_subscribers_to_channel")
|
||||||
|
.put("channel_id", channelInternalId.toString())
|
||||||
|
.put("user_ids", new JSONArray(ids));
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
JSONObject resp = ActionHandler.sendWithResponse(batchReq);
|
||||||
|
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
showToast("Subscribers added.");
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: تکبهتک
|
||||||
|
boolean allOk = true;
|
||||||
|
for (String uid : ids) {
|
||||||
|
JSONObject single = new JSONObject()
|
||||||
|
.put("action", "add_subscriber_to_channel")
|
||||||
|
.put("channel_id", channelInternalId.toString())
|
||||||
|
.put("user_id", uid);
|
||||||
|
JSONObject r = ActionHandler.sendWithResponse(single);
|
||||||
|
if (r == null || !"success".equalsIgnoreCase(r.optString("status"))) {
|
||||||
|
allOk = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean finalAllOk = allOk;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
showToast(finalAllOk ? "Subscribers added." : "Some subscribers failed.");
|
||||||
|
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSelectedPane() {
|
||||||
selectedMembersPane.getChildren().clear();
|
selectedMembersPane.getChildren().clear();
|
||||||
for (Contact c : selectedContacts) {
|
for (Contact c : selected) {
|
||||||
Label chip = new Label(c.getName());
|
Label chip = new Label(c.getName());
|
||||||
chip.getStyleClass().add("member-chip");
|
chip.getStyleClass().add("member-chip");
|
||||||
selectedMembersPane.getChildren().add(chip);
|
selectedMembersPane.getChildren().add(chip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMemberCount() {
|
private void updateCount() {
|
||||||
memberCountLabel.setText(selectedContacts.size() + " / 200000");
|
memberCountLabel.setText(selected.size() + " / 200000");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSearchIcon(boolean darkMode) {
|
private void updateSearchIcon(boolean darkMode) {
|
||||||
String iconPath = darkMode
|
String iconPath = darkMode
|
||||||
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
||||||
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
||||||
|
|
||||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||||
icon.setFitWidth(16);
|
icon.setFitWidth(16);
|
||||||
icon.setFitHeight(16);
|
icon.setFitHeight(16);
|
||||||
searchIcon.setGraphic(icon);
|
searchIcon.setGraphic(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setChannelInfo(String name, String id, String description, File image) {
|
private void showToast(String msg) {
|
||||||
this.channelName = name;
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
this.channelId = id;
|
a.initOwner(addMembersCard.getScene().getWindow());
|
||||||
this.channelImageFile = image;
|
a.show();
|
||||||
this.description = description;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner class for contact data
|
// ===== مدل Contact =====
|
||||||
public static class Contact {
|
public static class Contact {
|
||||||
|
private final String id; // internal_uuid کاربر
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String status;
|
private final String status;
|
||||||
private final String imageUrl;
|
private final String imageUrl;
|
||||||
|
|
||||||
public Contact(String name, String status, String imageUrl) {
|
public Contact(String id, String name, String status, String imageUrl) {
|
||||||
this.name = name;
|
this.id = id; this.name = name; this.status = status; this.imageUrl = imageUrl;
|
||||||
this.status = status;
|
|
||||||
this.imageUrl = imageUrl;
|
|
||||||
}
|
}
|
||||||
|
public String getId() { return id; }
|
||||||
public String getName() { return name; }
|
public String getName() { return name; }
|
||||||
public String getStatus() { return status; }
|
public String getStatus() { return status; }
|
||||||
public String getImageUrl() { return imageUrl; }
|
public String getImageUrl() { return imageUrl; }
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Contact c)) return false;
|
||||||
|
return Objects.equals(id, c.id);
|
||||||
|
}
|
||||||
|
@Override public int hashCode() { return Objects.hash(id); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ public class MainController {
|
|||||||
@FXML private ImageView noResultIcon;
|
@FXML private ImageView noResultIcon;
|
||||||
@FXML private ScrollPane globalSearchScroll;
|
@FXML private ScrollPane globalSearchScroll;
|
||||||
@FXML private VBox globalSearchResultsContainer;
|
@FXML private VBox globalSearchResultsContainer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private enum SearchMode {
|
private enum SearchMode {
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
CHAT
|
CHAT
|
||||||
@@ -1113,6 +1116,25 @@ public class MainController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addChatAndSelect(org.to.telegramfinalproject.Models.ChatEntry entry) {
|
||||||
|
// در Session نگهداری
|
||||||
|
if (org.to.telegramfinalproject.Client.Session.chatList.stream()
|
||||||
|
.noneMatch(c -> c.getId().equals(entry.getId()))) {
|
||||||
|
org.to.telegramfinalproject.Client.Session.chatList.add(0, entry);
|
||||||
|
org.to.telegramfinalproject.Client.Session.activeChats.add(0, entry);
|
||||||
|
}
|
||||||
|
// سایدبارت اگر متدی برای رفرش دارد صداش بزن (اسمش را با کلاس خودت هماهنگ کن)
|
||||||
|
try {
|
||||||
|
this.refreshChatListUI(); // اگر نداری، این خط را حذف کن
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
|
// نمایش پیج چت
|
||||||
|
if (getChatPageController() != null) {
|
||||||
|
getChatPageController().showChat(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,15 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.UUID;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class NewChannelController {
|
public class NewChannelController {
|
||||||
|
|
||||||
@@ -28,7 +32,7 @@ public class NewChannelController {
|
|||||||
@FXML private ImageView cameraIcon;
|
@FXML private ImageView cameraIcon;
|
||||||
@FXML private Button cancelButton;
|
@FXML private Button cancelButton;
|
||||||
@FXML private Button createButton;
|
@FXML private Button createButton;
|
||||||
@FXML private StackPane overlayRoot; // the root
|
@FXML private StackPane overlayRoot;
|
||||||
@FXML private Label descCounter;
|
@FXML private Label descCounter;
|
||||||
@FXML private Label channelIdLabel;
|
@FXML private Label channelIdLabel;
|
||||||
@FXML private TextField channelIdField;
|
@FXML private TextField channelIdField;
|
||||||
@@ -37,12 +41,10 @@ public class NewChannelController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Load default camera icon
|
|
||||||
cameraIcon.setImage(new Image(
|
cameraIcon.setImage(new Image(
|
||||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
||||||
));
|
));
|
||||||
|
|
||||||
// Camera button action → choose image
|
|
||||||
cameraButton.setOnAction(e -> {
|
cameraButton.setOnAction(e -> {
|
||||||
FileChooser chooser = new FileChooser();
|
FileChooser chooser = new FileChooser();
|
||||||
chooser.setTitle("Choose Channel Picture");
|
chooser.setTitle("Choose Channel Picture");
|
||||||
@@ -56,109 +58,135 @@ public class NewChannelController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cancel → close overlay
|
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||||
cancelButton.setOnAction(e -> {
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close when clicking outside card
|
createButton.setOnAction(e -> onCreateChannel());
|
||||||
overlayBackground.setOnMouseClicked(e -> {
|
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create button → validate name + submit
|
|
||||||
createButton.setOnAction(e -> {
|
|
||||||
String channelName = channelNameField.getText().trim();
|
|
||||||
String channelId = channelIdField.getText().trim();
|
|
||||||
String description = channelDescField.getText().trim();
|
|
||||||
|
|
||||||
if (channelName.isEmpty()) {
|
|
||||||
// Apply error style
|
|
||||||
channelNameField.getStyleClass().add("error");
|
|
||||||
channelNameLabel.getStyleClass().add("error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channelId.isEmpty()) {
|
|
||||||
// Apply error style
|
|
||||||
channelIdField.getStyleClass().add("error");
|
|
||||||
channelIdLabel.getStyleClass().add("error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Load Add Members overlay
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
|
||||||
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
|
|
||||||
StackPane addSubscribersOverlay = loader.load();
|
|
||||||
|
|
||||||
// Pass channel info to AddMembersController
|
|
||||||
AddSubscriberController controller = loader.getController();
|
|
||||||
controller.setChannelInfo(channelName, channelId, description, channelImageFile);
|
|
||||||
|
|
||||||
// Close the current "New Channel" overlay
|
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
|
|
||||||
// Then open the "Add Members" overlay
|
|
||||||
MainController.getInstance().showOverlay(addSubscribersOverlay);
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// محدودیت توضیح
|
||||||
final int MAX_LENGTH = 255;
|
final int MAX_LENGTH = 255;
|
||||||
channelDescField.addEventFilter(javafx.scene.input.KeyEvent.KEY_TYPED, e -> {
|
channelDescField.addEventFilter(javafx.scene.input.KeyEvent.KEY_TYPED, e -> {
|
||||||
if (channelDescField.getText().length() >= MAX_LENGTH) {
|
if (channelDescField.getText().length() >= MAX_LENGTH) e.consume();
|
||||||
e.consume(); // stop extra character from being typed
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
channelDescField.textProperty().addListener((obs, oldText, newText) -> {
|
channelDescField.textProperty().addListener((obs, oldText, newText) -> {
|
||||||
if (newText.length() > MAX_LENGTH) {
|
if (newText.length() > MAX_LENGTH) {
|
||||||
channelDescField.setText(newText.substring(0, MAX_LENGTH));
|
channelDescField.setText(newText.substring(0, MAX_LENGTH));
|
||||||
channelDescField.positionCaret(MAX_LENGTH);
|
channelDescField.positionCaret(MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current length out of max
|
|
||||||
int current = channelDescField.getText().length();
|
int current = channelDescField.getText().length();
|
||||||
descCounter.setText(current + " / " + MAX_LENGTH);
|
descCounter.setText(current + " / " + MAX_LENGTH);
|
||||||
|
descCounter.setStyle(current == MAX_LENGTH ? "-fx-text-fill: red;" : "");
|
||||||
// Style when limit reached
|
|
||||||
if (current == MAX_LENGTH) {
|
|
||||||
descCounter.setStyle("-fx-text-fill: red;");
|
|
||||||
} else {
|
|
||||||
descCounter.setStyle(""); // fallback to CSS
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
descCounter.setText("0 / 255");
|
||||||
|
|
||||||
// Initial value
|
// پاک کردن استایل خطا هنگام تایپ
|
||||||
descCounter.setText("0 / " + MAX_LENGTH);
|
channelNameField.textProperty().addListener((obs, ov, nv) -> {
|
||||||
|
if (!nv.trim().isEmpty()) {
|
||||||
// Reset error state when typing
|
|
||||||
channelNameField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
if (!newVal.trim().isEmpty()) {
|
|
||||||
channelNameField.getStyleClass().remove("error");
|
channelNameField.getStyleClass().remove("error");
|
||||||
channelNameLabel.getStyleClass().remove("error");
|
channelNameLabel.getStyleClass().remove("error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
channelIdField.textProperty().addListener((obs, ov, nv) -> {
|
||||||
// Reset error state when typing
|
if (!nv.trim().isEmpty()) {
|
||||||
channelIdField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
if (!newVal.trim().isEmpty()) {
|
|
||||||
channelIdField.getStyleClass().remove("error");
|
channelIdField.getStyleClass().remove("error");
|
||||||
channelIdLabel.getStyleClass().remove("error");
|
channelIdLabel.getStyleClass().remove("error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-focus channel name on open
|
|
||||||
Platform.runLater(() -> channelNameField.requestFocus());
|
Platform.runLater(() -> channelNameField.requestFocus());
|
||||||
|
|
||||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (newChannelCard.getScene() != null) {
|
if (newChannelCard.getScene() != null) {
|
||||||
ThemeManager.getInstance().registerScene(newChannelCard.getScene());
|
ThemeManager.getInstance().registerScene(newChannelCard.getScene());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onCreateChannel() {
|
||||||
|
String name = nz(channelNameField.getText()).trim();
|
||||||
|
String dispId = nz(channelIdField.getText()).trim();
|
||||||
|
String description = nz(channelDescField.getText()).trim();
|
||||||
|
|
||||||
|
boolean ok = true;
|
||||||
|
if (name.isEmpty()) { channelNameField.getStyleClass().add("error"); channelNameLabel.getStyleClass().add("error"); ok = false; }
|
||||||
|
if (dispId.isEmpty()) { channelIdField.getStyleClass().add("error"); channelIdLabel.getStyleClass().add("error"); ok = false; }
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
final String me = Session.getUserUUID();
|
||||||
|
if (me == null || me.isBlank()) {
|
||||||
|
showToast("Cannot create channel: current user UUID missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: اگر آپلود تصویر داری، فایل را آپلود کن و URL نهایی را اینجا بفرست
|
||||||
|
final String imageUrl = null;
|
||||||
|
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "create_channel")
|
||||||
|
.put("channel_id", dispId) // آیدی نمایشی/عمومی
|
||||||
|
.put("channel_name", name)
|
||||||
|
.put("user_id", me) // internal_uuid سازنده
|
||||||
|
.put("image_url", imageUrl) // اختیاری
|
||||||
|
.put("description", description); // اختیاری (سرور اگر ساپورت نکند نادیده میگیرد)
|
||||||
|
|
||||||
|
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",""))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
Platform.runLater(() -> showToast("Create failed: empty data."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID internalId = UUID.fromString(data.optString("internal_id"));
|
||||||
|
String returnedName = data.optString("name", name);
|
||||||
|
String returnedDisp = data.optString("id", dispId);
|
||||||
|
String returnedImg = data.optString("image_url", "");
|
||||||
|
|
||||||
|
// 1) چت کانال تازهساخته را به سایدبار اضافه کن و انتخاب کن
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ChatEntry entry = ChatEntry.fromServer(
|
||||||
|
internalId,
|
||||||
|
"channel",
|
||||||
|
returnedName,
|
||||||
|
returnedDisp,
|
||||||
|
returnedImg,
|
||||||
|
/*isOwner*/ true,
|
||||||
|
/*isAdmin*/ true
|
||||||
|
);
|
||||||
|
MainController.getInstance().addChatAndSelect(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) اُورلی افزودن سابسکرایبر را باز کن و internal_id را پاس بده
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
|
||||||
|
StackPane addSubsOverlay = loader.load();
|
||||||
|
|
||||||
|
AddSubscriberController controller = loader.getController();
|
||||||
|
controller.setChannelInfo(internalId, returnedName, returnedDisp, channelImageFile, description);
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(addSubsOverlay);
|
||||||
|
MainController.getInstance().closeOverlay(overlayRoot);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
showToast("Failed to open Add Subscribers.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToast(String msg) {
|
||||||
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
|
a.initOwner(overlayRoot.getScene().getWindow());
|
||||||
|
a.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String nz(String s){ return s==null? "": s; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,16 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class NewGroupController {
|
public class NewGroupController {
|
||||||
|
|
||||||
@@ -31,12 +39,10 @@ public class NewGroupController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Load default camera icon
|
|
||||||
cameraIcon.setImage(new Image(
|
cameraIcon.setImage(new Image(
|
||||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
||||||
));
|
));
|
||||||
|
|
||||||
// Camera button action → choose image
|
|
||||||
cameraButton.setOnAction(e -> {
|
cameraButton.setOnAction(e -> {
|
||||||
FileChooser chooser = new FileChooser();
|
FileChooser chooser = new FileChooser();
|
||||||
chooser.setTitle("Choose Group Picture");
|
chooser.setTitle("Choose Group Picture");
|
||||||
@@ -50,71 +56,121 @@ public class NewGroupController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelButton.setOnAction(e -> {
|
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||||
});
|
|
||||||
|
|
||||||
overlayBackground.setOnMouseClicked(e -> {
|
nextButton.setOnAction(e -> onNext());
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
});
|
|
||||||
|
|
||||||
nextButton.setOnAction(e -> {
|
// clear errors on typing
|
||||||
String groupName = groupNameField.getText().trim();
|
groupNameField.textProperty().addListener((obs, ov, nv) -> {
|
||||||
String groupId = groupIdField.getText().trim();
|
if (!nv.trim().isEmpty()) {
|
||||||
|
|
||||||
if (groupName.isEmpty()) {
|
|
||||||
// Apply error style
|
|
||||||
groupNameField.getStyleClass().add("error");
|
|
||||||
groupNameLabel.getStyleClass().add("error");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupId.isEmpty()) {
|
|
||||||
// Apply error style
|
|
||||||
groupIdField.getStyleClass().add("error");
|
|
||||||
groupIdLabel.getStyleClass().add("error");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
|
||||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
|
||||||
StackPane addMembersOverlay = loader.load();
|
|
||||||
|
|
||||||
// Optional: pass groupName and image to AddMembersController
|
|
||||||
AddMembersController controller = loader.getController();
|
|
||||||
controller.setGroupInfo(groupName, groupId, groupImageFile);
|
|
||||||
|
|
||||||
// Close the current "New Group" overlay
|
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
|
|
||||||
// Then open the Add Members overlay
|
|
||||||
MainController.getInstance().showOverlay(addMembersOverlay);
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset error state when typing
|
|
||||||
groupNameField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
if (!newVal.trim().isEmpty()) {
|
|
||||||
groupNameField.getStyleClass().remove("error");
|
groupNameField.getStyleClass().remove("error");
|
||||||
groupNameLabel.getStyleClass().remove("error");
|
groupNameLabel.getStyleClass().remove("error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
groupIdField.textProperty().addListener((obs, ov, nv) -> {
|
||||||
// Reset error state when typing
|
if (!nv.trim().isEmpty()) {
|
||||||
groupIdField.textProperty().addListener((obs, oldVal, newVal) -> {
|
|
||||||
if (!newVal.trim().isEmpty()) {
|
|
||||||
groupIdField.getStyleClass().remove("error");
|
groupIdField.getStyleClass().remove("error");
|
||||||
groupIdLabel.getStyleClass().remove("error");
|
groupIdLabel.getStyleClass().remove("error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto_focus search bar when overlay opens
|
|
||||||
Platform.runLater(() -> groupNameField.requestFocus());
|
Platform.runLater(() -> groupNameField.requestFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onNext() {
|
||||||
|
String groupName = groupNameField.getText() == null ? "" : groupNameField.getText().trim();
|
||||||
|
String groupId = groupIdField.getText() == null ? "" : groupIdField.getText().trim();
|
||||||
|
|
||||||
|
boolean ok = true;
|
||||||
|
if (groupName.isEmpty()) {
|
||||||
|
groupNameField.getStyleClass().add("error");
|
||||||
|
groupNameLabel.getStyleClass().add("error");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (groupId.isEmpty()) {
|
||||||
|
groupIdField.getStyleClass().add("error");
|
||||||
|
groupIdLabel.getStyleClass().add("error");
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
// ساخت گروه روی سرور
|
||||||
|
final String me = Session.getUserUUID(); // internal_uuid
|
||||||
|
if (me == null || me.isBlank()) {
|
||||||
|
showToast("Cannot create group: current user UUID missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر آپلود تصویر داری، اینجا عکس رو آپلود کن و imageUrl واقعی بفرست (TODO)
|
||||||
|
final String imageUrl = null;
|
||||||
|
|
||||||
|
JSONObject req = new JSONObject()
|
||||||
|
.put("action", "create_group")
|
||||||
|
.put("group_id", groupId)
|
||||||
|
.put("group_name", groupName)
|
||||||
|
.put("user_id", me) // ← internal_uuid سازنده
|
||||||
|
.put("image_url", imageUrl); // ← اختیاری
|
||||||
|
|
||||||
|
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",""))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
Platform.runLater(() -> showToast("Create failed: empty data."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID internalId = UUID.fromString(data.optString("internal_id"));
|
||||||
|
String returnedName = data.optString("name", groupName);
|
||||||
|
String returnedDisp = data.optString("id", groupId);
|
||||||
|
String returnedImg = data.optString("image_url", "");
|
||||||
|
|
||||||
|
// 1) بهصورت لوکال یک ChatEntry بساز و به سایدبار اضافه و سِلکت کن
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ChatEntry entry = ChatEntry.fromServer(
|
||||||
|
internalId,
|
||||||
|
"group",
|
||||||
|
returnedName,
|
||||||
|
returnedDisp,
|
||||||
|
returnedImg,
|
||||||
|
/*isOwner*/ true,
|
||||||
|
/*isAdmin*/ true
|
||||||
|
);
|
||||||
|
MainController.getInstance().addChatAndSelect(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2) پنجرهی Add Members را باز کن و internal_id گروه را پاس بده
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
|
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||||
|
StackPane addMembersOverlay = loader.load();
|
||||||
|
AddMembersController controller = loader.getController();
|
||||||
|
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
|
||||||
|
|
||||||
|
// این اُورلی را روی UI نشان بده
|
||||||
|
MainController.getInstance().showOverlay(addMembersOverlay);
|
||||||
|
|
||||||
|
// این اُورلی NewGroup را ببند
|
||||||
|
MainController.getInstance().closeOverlay(overlayRoot);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
showToast("Failed to open Add Members.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToast(String msg) {
|
||||||
|
// جایگزینش کن با سیستم نوتی شما
|
||||||
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
|
a.initOwner(overlayRoot.getScene().getWindow());
|
||||||
|
a.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user