This commit is contained in:
2025-09-08 11:19:05 +03:30
parent 41c30aff7b
commit da6687fd98
6 changed files with 16 additions and 173 deletions
@@ -29,7 +29,6 @@ public class AddMembersController {
@FXML private Button cancelButton; @FXML private Button cancelButton;
@FXML private Button createButton; @FXML private Button createButton;
// گروه هدف
private UUID groupInternalId; private UUID groupInternalId;
private String groupName; private String groupName;
private String groupDisplayId; private String groupDisplayId;
@@ -42,9 +41,7 @@ public class AddMembersController {
private Mode mode = Mode.CREATE; // default private Mode mode = Mode.CREATE; // default
// انتخاب‌ها
private final Set<Contact> selectedContacts = new HashSet<>(); private final Set<Contact> selectedContacts = new HashSet<>();
// همه‌ی کانتکت‌ها
private final List<Contact> allContacts = new ArrayList<>(); private final List<Contact> allContacts = new ArrayList<>();
@FXML @FXML
@@ -61,7 +58,6 @@ public class AddMembersController {
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY); contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
}); });
// آیکن سرچ با تم
Platform.runLater(() -> { Platform.runLater(() -> {
if (addMembersCard.getScene() != null) { if (addMembersCard.getScene() != null) {
ThemeManager.getInstance().registerScene(addMembersCard.getScene()); ThemeManager.getInstance().registerScene(addMembersCard.getScene());
@@ -70,19 +66,15 @@ public class AddMembersController {
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv)); 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())); cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent())); overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
// دکمه Add (افزودن اعضا)
createButton.setOnAction(e -> onAddMembers()); createButton.setOnAction(e -> onAddMembers());
// سرچ
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv)); searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
Platform.runLater(() -> searchField.requestFocus()); Platform.runLater(() -> searchField.requestFocus());
// دیتا را از Session بارگذاری کن
loadContactsFromSession(); loadContactsFromSession();
updateMemberCount(); updateMemberCount();
renderContacts(allContacts); renderContacts(allContacts);
@@ -132,7 +124,6 @@ public class AddMembersController {
allContacts.add(new Contact(id, name, status, imageUrl)); allContacts.add(new Contact(id, name, status, imageUrl));
} }
} }
// اگر خالی بود، چیزی نشون نده (یا می‌تونی دمو بسازی)
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER)); allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
} }
@@ -295,9 +286,8 @@ public class AddMembersController {
a.show(); a.show();
} }
// ================== مدل Contact ==================
public static class Contact { public static class Contact {
private final String id; // internal_uuid کاربر 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;
@@ -29,20 +29,17 @@ public class AddSubscriberController {
@FXML private Button skipButton; @FXML private Button skipButton;
@FXML private Button addButton; @FXML private Button addButton;
// اطلاعات کانال
private UUID channelInternalId; private UUID channelInternalId;
private String channelName; private String channelName;
private String channelDisplayId; private String channelDisplayId;
private File channelImageFile; private File channelImageFile;
private String description; private String description;
// انتخاب‌ها و منبع داده
private final Set<Contact> selected = new HashSet<>(); private final Set<Contact> selected = new HashSet<>();
private final List<Contact> allContacts = new ArrayList<>(); private final List<Contact> allContacts = new ArrayList<>();
@FXML @FXML
public void initialize() { public void initialize() {
// اسکرول نرم
contactsScroll.getStylesheets().add( contactsScroll.getStylesheets().add(
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm() getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
); );
@@ -54,7 +51,6 @@ public class AddSubscriberController {
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY); contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
}); });
// تم آیکن
Platform.runLater(() -> { Platform.runLater(() -> {
if (addMembersCard.getScene() != null) { if (addMembersCard.getScene() != null) {
ThemeManager.getInstance().registerScene(addMembersCard.getScene()); ThemeManager.getInstance().registerScene(addMembersCard.getScene());
@@ -63,22 +59,18 @@ public class AddSubscriberController {
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv)); 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())); overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
skipButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent())); skipButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
addButton.setOnAction(e -> onAddSubscribers()); addButton.setOnAction(e -> onAddSubscribers());
// سرچ
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv)); searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
Platform.runLater(() -> searchField.requestFocus()); Platform.runLater(() -> searchField.requestFocus());
// لود کانتکت‌ها از Session
loadContactsFromSession(); loadContactsFromSession();
updateCount(); updateCount();
renderContacts(allContacts); renderContacts(allContacts);
} }
/** NewChannelController این را بعد از ساخت کانال صدا بزند */
public void setChannelInfo(UUID internalId, String name, String displayId, File imageFile, String description) { public void setChannelInfo(UUID internalId, String name, String displayId, File imageFile, String description) {
this.channelInternalId = internalId; this.channelInternalId = internalId;
this.channelName = name; this.channelName = name;
@@ -87,7 +79,6 @@ public class AddSubscriberController {
this.description = description; this.description = description;
} }
// برای سازگاری با امضای قدیمی (اختیاری)
public void setChannelInfo(String name, String id, String description, File image) { public void setChannelInfo(String name, String id, String description, File image) {
this.channelName = name; this.channelName = name;
this.channelDisplayId = id; this.channelDisplayId = id;
@@ -226,7 +217,6 @@ public class AddSubscriberController {
return; return;
} }
// fallback: تک‌به‌تک
boolean allOk = true; boolean allOk = true;
for (String uid : ids) { for (String uid : ids) {
JSONObject single = new JSONObject() JSONObject single = new JSONObject()
@@ -277,9 +267,8 @@ public class AddSubscriberController {
a.show(); a.show();
} }
// ===== مدل Contact =====
public static class Contact { public static class Contact {
private final String id; // internal_uuid کاربر 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;
@@ -27,14 +27,12 @@ public class BlockedUsersController {
@FXML private ListView<HBox> blockedList; @FXML private ListView<HBox> blockedList;
@FXML private VBox blockedCard; @FXML private VBox blockedCard;
// نگاشت برای حذف سریع ردیف بعد از Unblock
private final Map<UUID, HBox> rowByUserId = new HashMap<>(); private final Map<UUID, HBox> rowByUserId = new HashMap<>();
@FXML @FXML
public void initialize() { public void initialize() {
backButton.setGraphic(makeIcon("/org/to/telegramfinalproject/Icons/back_button_dark.png")); backButton.setGraphic(makeIcon("/org/to/telegramfinalproject/Icons/back_button_dark.png"));
// بارگذاری واقعی از سرور
loadBlockedUsers(); loadBlockedUsers();
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayBackground.getParent())); closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayBackground.getParent()));
@@ -72,7 +70,6 @@ public class BlockedUsersController {
/* ===================== Networking ===================== */ /* ===================== Networking ===================== */
private void loadBlockedUsers() { private void loadBlockedUsers() {
// درخواست به سرور
new Thread(() -> { new Thread(() -> {
JSONObject req = new JSONObject().put("action", "get_blocked_users"); JSONObject req = new JSONObject().put("action", "get_blocked_users");
JSONObject resp = ActionHandler.sendWithResponse(req); JSONObject resp = ActionHandler.sendWithResponse(req);
@@ -102,8 +99,8 @@ public class BlockedUsersController {
new Thread(() -> { new Thread(() -> {
JSONObject req = new JSONObject() JSONObject req = new JSONObject()
.put("action", "toggle_block") .put("action", "toggle_block")
.put("user_id", me) // UUID من .put("user_id", me) // UUID
.put("target_id", targetId.toString()); // UUID طرف .put("target_id", targetId.toString()); // UUID
JSONObject resp = ActionHandler.sendWithResponse(req); JSONObject resp = ActionHandler.sendWithResponse(req);
boolean ok = resp != null && "success".equalsIgnoreCase(resp.optString("status")); boolean ok = resp != null && "success".equalsIgnoreCase(resp.optString("status"));
@@ -127,7 +124,6 @@ public class BlockedUsersController {
blockedList.getItems().clear(); blockedList.getItems().clear();
rowByUserId.clear(); rowByUserId.clear();
// انتظار میره هر آبجکت حداقل: internal_uuid, profile_name, user_id/username/display_id, image_url داشته باشه
IntStream.range(0, list.length()).forEach(i -> { IntStream.range(0, list.length()).forEach(i -> {
JSONObject o = list.optJSONObject(i); JSONObject o = list.optJSONObject(i);
if (o == null) return; if (o == null) return;
@@ -229,7 +225,6 @@ public class BlockedUsersController {
} }
private void showToast(String msg) { private void showToast(String msg) {
// ساده: از Alert استفاده کن؛ اگر Toast اختصاصی داری جایگزینش کن
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK); Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
if (a.getDialogPane().getScene() != null) { if (a.getDialogPane().getScene() != null) {
ThemeManager.getInstance().registerScene(a.getDialogPane().getScene()); ThemeManager.getInstance().registerScene(a.getDialogPane().getScene());
@@ -34,7 +34,6 @@ public class ChangeCredentialsController {
private boolean passwordVisible = false; private boolean passwordVisible = false;
private boolean confirmVisible = false; private boolean confirmVisible = false;
// ↓↓↓ متدی که از CheckPasswordController مقدار می‌گیرد
private String currentPassword; private String currentPassword;
public void setCurrentPassword(String p) { this.currentPassword = p; } public void setCurrentPassword(String p) { this.currentPassword = p; }
@@ -44,7 +43,6 @@ public class ChangeCredentialsController {
togglePasswordBtn.setOnAction(e -> togglePasswordVisibility()); togglePasswordBtn.setOnAction(e -> togglePasswordVisibility());
toggleConfirmBtn.setOnAction(e -> toggleConfirmVisibility()); toggleConfirmBtn.setOnAction(e -> toggleConfirmVisibility());
// «ذخیره و بستن» مثل ادیت پروفایل
saveButton.setOnAction(e -> saveAndClose()); saveButton.setOnAction(e -> saveAndClose());
closeButton.setOnAction(e -> saveAndClose()); closeButton.setOnAction(e -> saveAndClose());
cancelButton.setOnAction(e -> saveAndClose()); cancelButton.setOnAction(e -> saveAndClose());
@@ -78,7 +76,6 @@ public class ChangeCredentialsController {
Platform.runLater(() -> usernameField.requestFocus()); Platform.runLater(() -> usernameField.requestFocus());
} }
// ← از CheckPasswordController بلافاصله بعد از load صدا بزن
public void prefillFromSession() { public void prefillFromSession() {
var u = Session.currentUser; var u = Session.currentUser;
if (u == null) return; if (u == null) return;
@@ -112,12 +109,10 @@ public class ChangeCredentialsController {
toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁"); toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁");
} }
// ارسال تغییرات (در صورت پر بودن/تغییر) و بستن
private void saveAndClose() { private void saveAndClose() {
boolean anyError = false; boolean anyError = false;
boolean sentSomething = false; boolean sentSomething = false;
// 1) یوزرنیم
var cu = Session.currentUser; var cu = Session.currentUser;
String oldUsername = cu != null ? cu.optString("user_id", String oldUsername = cu != null ? cu.optString("user_id",
cu.optString("username", cu.optString("username",
@@ -129,7 +124,7 @@ public class ChangeCredentialsController {
JSONObject req = new JSONObject() JSONObject req = new JSONObject()
.put("action", "update_username") .put("action", "update_username")
.put("current_password", currentPassword) // با اینکه سرور فعلاً چک نمی‌کند، می‌فرستیم .put("current_password", currentPassword)
.put("new_username", newUsername); .put("new_username", newUsername);
JSONObject resp = ActionHandler.sendWithResponse(req); JSONObject resp = ActionHandler.sendWithResponse(req);
@@ -150,7 +145,7 @@ public class ChangeCredentialsController {
if (confirm.isEmpty() || !confirm.equals(newPwd)) { if (confirm.isEmpty() || !confirm.equals(newPwd)) {
addError(confirmPasswordField, confirmPasswordLabel); addError(confirmPasswordField, confirmPasswordLabel);
addError(visibleConfirmPasswordField, confirmPasswordLabel); addError(visibleConfirmPasswordField, confirmPasswordLabel);
return; // نذار بسته شه تا کاربر درست کنه return;
} }
sentSomething = true; sentSomething = true;
@@ -168,7 +163,6 @@ public class ChangeCredentialsController {
} }
} }
// اگر چیزی نفرستادیم یا همه‌چیز OK بود → ببند و رفرش Settings
if (!sentSomething || !anyError) { if (!sentSomething || !anyError) {
var sc = SettingsController.getInstance(); var sc = SettingsController.getInstance();
if (sc != null) sc.populateFromSession(); if (sc != null) sc.populateFromSession();
@@ -121,8 +121,7 @@ public class ChatItemController {
// ChatItemController.java // ChatItemController.java
@FXML private ImageView avatarImage; @FXML private ImageView avatarImage;
// اگر نوع چت نیاز داری برای دیفالت‌ها (private|group|channel) private String chatType;
private String chatType; // اگر داری از setChatData بگیرش و نگه دار
public void updateAvatar(String url) { public void updateAvatar(String url) {
try { try {
@@ -337,12 +337,10 @@ public class ChatPageController {
// Menu item actions // Menu item actions
viewProfileItem.setOnAction(e -> { viewProfileItem.setOnAction(e -> {
System.out.println("Viewing profile of " + chatName); System.out.println("Viewing profile of " + chatName);
// TODO: open profile UI
}); });
deleteChatItem.setOnAction(e -> { deleteChatItem.setOnAction(e -> {
System.out.println("Deleting chat with " + chatName); System.out.println("Deleting chat with " + chatName);
// TODO: delete logic
}); });
if (archiveItem != null) { if (archiveItem != null) {
@@ -511,7 +509,6 @@ public class ChatPageController {
Platform.runLater(() -> { Platform.runLater(() -> {
if (currentChat == null || !currentChat.getId().equals(chatId)) return; if (currentChat == null || !currentChat.getId().equals(chatId)) return;
// حباب outgoing
addBubble( addBubble(
true, // outgoing true, // outgoing
"You", // display name "You", // display name
@@ -594,7 +591,7 @@ public class ChatPageController {
private HBox addPendingMediaBubble(String messageId, File file, String type, String caption) { private HBox addPendingMediaBubble(String messageId, File file, String type, String caption) {
HBox root = new HBox(8); HBox root = new HBox(8);
root.setFillHeight(true); root.setFillHeight(true);
root.setAlignment(Pos.CENTER_RIGHT); // چون outgoing است root.setAlignment(Pos.CENTER_RIGHT);
// preview // preview
if ("IMAGE".equalsIgnoreCase(type)) { if ("IMAGE".equalsIgnoreCase(type)) {
@@ -1032,7 +1029,6 @@ public class ChatPageController {
if (archiveItem != null) archiveItem.setDisable(false); if (archiveItem != null) archiveItem.setDisable(false);
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) { if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
// 1) جابه‌جایی در Session
if (!currentlyArchived) { if (!currentlyArchived) {
Session.moveToArchived(entry); Session.moveToArchived(entry);
if (archiveItem != null) archiveItem.setText("Unarchive chat"); if (archiveItem != null) archiveItem.setText("Unarchive chat");
@@ -1361,7 +1357,7 @@ public class ChatPageController {
String rel = pathOrUrl.startsWith("/") ? pathOrUrl.substring(1) : pathOrUrl; String rel = pathOrUrl.startsWith("/") ? pathOrUrl.substring(1) : pathOrUrl;
java.nio.file.Path p = java.nio.file.Paths.get(UPLOADS_DIR, rel.replace("/", java.io.File.separator)); java.nio.file.Path p = java.nio.file.Paths.get(UPLOADS_DIR, rel.replace("/", java.io.File.separator));
java.net.URI uri = p.toUri(); // می‌شود file:///C:/Users/.../uploads/images/... java.net.URI uri = p.toUri();
return uri.toString(); return uri.toString();
@@ -1683,10 +1679,7 @@ public class ChatPageController {
} }
} }
} }
// imports لازم:
// import javafx.scene.media.Media;
// import javafx.scene.media.MediaPlayer;
// import javafx.util.Duration;
private Node buildAudioNodeLocal(String localPathOrUri, String caption) { private Node buildAudioNodeLocal(String localPathOrUri, String caption) {
@@ -1708,14 +1701,12 @@ public class ChatPageController {
} }
root.getChildren().add(right); root.getChildren().add(right);
// مسیر را به file URI تبدیل کن
String playable = toFileUri(localPathOrUri); String playable = toFileUri(localPathOrUri);
if (playable == null) { if (playable == null) {
btn.setDisable(true); seek.setDisable(true); time.setText("no audio"); btn.setDisable(true); seek.setDisable(true); time.setText("no audio");
return root; return root;
} }
// قبل از ساخت Media، وجود فایل را چک کن
try { try {
java.net.URI uri = java.net.URI.create(playable); java.net.URI uri = java.net.URI.create(playable);
java.nio.file.Path p = java.nio.file.Paths.get(uri); java.nio.file.Path p = java.nio.file.Paths.get(uri);
@@ -1724,7 +1715,7 @@ public class ChatPageController {
System.err.println("Audio file not found: " + p); System.err.println("Audio file not found: " + p);
return root; return root;
} }
} catch (Exception ignore) { /* اگر نشد، ادامه می‌دهیم */ } } catch (Exception ignore) { }
MediaPlayer[] holder = new MediaPlayer[1]; MediaPlayer[] holder = new MediaPlayer[1];
try { try {
@@ -1782,7 +1773,7 @@ public class ChatPageController {
if (pathOrUri.startsWith("file:/")) return pathOrUri; if (pathOrUri.startsWith("file:/")) return pathOrUri;
java.io.File f = new java.io.File(pathOrUri); java.io.File f = new java.io.File(pathOrUri);
return f.toURI().toString(); // -> file:///C:/... یا file:///home/... return f.toURI().toString();
} }
@@ -2057,7 +2048,7 @@ public class ChatPageController {
org.to.telegramfinalproject.Models.ContactEntry ce = org.to.telegramfinalproject.Models.ContactEntry ce =
new org.to.telegramfinalproject.Models.ContactEntry( new org.to.telegramfinalproject.Models.ContactEntry(
other, // contact_id (UUID) other, // contact_id (UUID)
currentChat.getDisplayId(), // contact_display_id / user_id دیدنی currentChat.getDisplayId(), // contact_display_id / user_id
currentChat.getDisplayId(), currentChat.getDisplayId(),
nz(chatTitle.getText()), nz(chatTitle.getText()),
currentChat.getImageUrl(), currentChat.getImageUrl(),
@@ -2139,8 +2130,8 @@ public class ChatPageController {
org.json.JSONObject req = new org.json.JSONObject() org.json.JSONObject req = new org.json.JSONObject()
.put("action", "toggle_block") .put("action", "toggle_block")
.put("user_id", viewerUuid) // internal_uuid خودت .put("user_id", viewerUuid)
.put("target_id", other.toString()); // internal_uuid طرف مقابل .put("target_id", other.toString());
org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req); org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
boolean ok = res != null && "success".equalsIgnoreCase(res.optString("status")); boolean ok = res != null && "success".equalsIgnoreCase(res.optString("status"));
@@ -2546,7 +2537,6 @@ public class ChatPageController {
org.json.JSONObject cu = org.to.telegramfinalproject.Client.Session.currentUser; org.json.JSONObject cu = org.to.telegramfinalproject.Client.Session.currentUser;
if (cu == null) return new java.util.ArrayList<>(); if (cu == null) return new java.util.ArrayList<>();
// دو منبع معمول در Session: chat_list و active_chat_list
org.json.JSONArray[] sources = new org.json.JSONArray[]{ org.json.JSONArray[] sources = new org.json.JSONArray[]{
cu.optJSONArray("chat_list"), cu.optJSONArray("chat_list"),
cu.optJSONArray("active_chat_list") cu.optJSONArray("active_chat_list")
@@ -2704,121 +2694,7 @@ public class ChatPageController {
return profileName + (userId != null && !userId.isBlank() ? " [" + userId + "]" : ""); return profileName + (userId != null && !userId.isBlank() ? " [" + userId + "]" : "");
} }
} }
// private void onLeaveGroupMenuClicked(ChatEntry entry) {
// if (entry == null || !"group".equalsIgnoreCase(entry.getType())) {
// alert(Alert.AlertType.INFORMATION, "This action is only available for groups.");
// return;
// }
//
// String myUuidStr = Session.getUserUUID();
// if (myUuidStr == null || myUuidStr.isBlank()) {
// alert(Alert.AlertType.ERROR, "Cannot determine your identity.");
// return;
// }
// if (entry.isOwner()) {
// // 1. گرفتن لیست ادمین‌ها
// JSONObject req = new JSONObject()
// .put("action", "view_group_admins")
// .put("group_id", entry.getId().toString());
//
// JSONObject res = ActionHandler.sendWithResponse(req);
// if (res == null || !"success".equalsIgnoreCase(res.optString("status"))) {
// alert(Alert.AlertType.ERROR, "Failed to fetch admins.");
// return;
// }
//
// JSONArray admins = res.getJSONObject("data").optJSONArray("admins");
// if (admins == null || admins.isEmpty()) {
// alert(Alert.AlertType.WARNING, "No other admins available. Promote someone first.");
// return;
// }
//
// String myUuid = Session.getUserUUID();
// List<JSONObject> candidates = new ArrayList<>();
// for (int i = 0; i < admins.length(); i++) {
// JSONObject a = admins.getJSONObject(i);
// String uuid = a.optString("internal_uuid");
// if (uuid != null && uuid.equals(myUuid)) continue; // حذف Owner
// candidates.add(a);
// }
//
// if (candidates.isEmpty()) {
// alert(Alert.AlertType.WARNING, "No other admins available.");
// return;
// }
//
// // 2. نمایش لیست در یک Dialog ساده
// Dialog<JSONObject> dialog = new Dialog<>();
// dialog.setTitle("Transfer Ownership");
// dialog.setHeaderText("Select a new owner before leaving");
// dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
//
// ListView<JSONObject> listView = new ListView<>();
// listView.getItems().addAll(candidates);
// listView.setCellFactory(v -> new ListCell<>() {
// @Override protected void updateItem(JSONObject item, boolean empty) {
// super.updateItem(item, empty);
// if (empty || item == null) {
// setText(null);
// } else {
// setText(item.optString("profile_name", "Unknown")
// + " (" + item.optString("user_id") + ")");
// }
// }
// });
//
// dialog.getDialogPane().setContent(listView);
// Node okBtn = dialog.getDialogPane().lookupButton(ButtonType.OK);
// okBtn.setDisable(true);
//
// listView.getSelectionModel().selectedItemProperty().addListener((obs, old, sel) -> {
// okBtn.setDisable(sel == null);
// });
//
// dialog.setResultConverter(bt ->
// bt == ButtonType.OK ? listView.getSelectionModel().getSelectedItem() : null);
//
// Optional<JSONObject> pick = dialog.showAndWait();
// if (pick.isEmpty()) return;
//
// JSONObject selected = pick.get();
// String newOwnerId = selected.optString("internal_uuid");
//
// JSONObject promoteReq = new JSONObject()
// .put("action", "transfer_group_ownership")
// .put("group_id", entry.getId().toString())
// .put("new_owner_user_id", newOwnerId);
//
// JSONObject promoteRes = ActionHandler.sendWithResponse(promoteReq);
// if (promoteRes == null || !"success".equalsIgnoreCase(promoteRes.optString("status"))) {
// alert(Alert.AlertType.ERROR, "Ownership transfer failed.");
// return;
// }
//
// // 4. بعد از انتقال، خروج از گروه
// JSONObject leaveReq = new JSONObject()
// .put("action", "leave_chat")
// .put("user_id", myUuid)
// .put("chat_id", entry.getId().toString())
// .put("chat_type", "group");
//
// JSONObject leaveRes = ActionHandler.sendWithResponse(leaveReq);
// if (leaveRes != null && "success".equalsIgnoreCase(leaveRes.optString("status"))) {
// alert(Alert.AlertType.INFORMATION, "Ownership transferred and you left the group.");
// MainController.getInstance().refreshChatListUI();
// AppRouter.showMain();
// } else {
// alert(Alert.AlertType.ERROR, "Leave failed.");
// }
// return;
// }
//
//
//
// if (confirm("Leave Group", "Are you sure you want to leave this group?")) {
// sendLeaveRequest(entry.getId(), myUuidStr);
// }
// }
private void onLeaveGroupMenuClicked(ChatEntry entry) { private void onLeaveGroupMenuClicked(ChatEntry entry) {
if (entry == null || !"group".equalsIgnoreCase(entry.getType())) { if (entry == null || !"group".equalsIgnoreCase(entry.getType())) {