Delete / leave / Archive in group
This commit is contained in:
@@ -1,359 +0,0 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class LeaveGroupFlow {
|
||||
|
||||
private LeaveGroupFlow() {}
|
||||
|
||||
// ==== Public API: call this from your controller when menu item clicked
|
||||
public static void start(ChatEntry entry) {
|
||||
if (entry == null || !"group".equalsIgnoreCase(entry.getType())) {
|
||||
info("This action is only available for groups.");
|
||||
return;
|
||||
}
|
||||
final String myUuidStr = Session.getUserUUID();
|
||||
if (myUuidStr == null || myUuidStr.isBlank()) {
|
||||
error("Cannot determine your identity (internal UUID missing).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.isOwner()) {
|
||||
// Non-owner: confirm and leave directly
|
||||
if (!confirm("Leave Group", "Are you sure you want to leave this group?")) return;
|
||||
leaveGroupAsync(entry.getId(), myUuidStr, () -> info("You left the group."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Owner: choose new owner (server returns list WITHOUT self)
|
||||
openOwnerPickerAndLeave(entry.getId(), myUuidStr);
|
||||
}
|
||||
|
||||
// ==== Owner flow ====
|
||||
private static void openOwnerPickerAndLeave(UUID groupId, String myUuidStr) {
|
||||
ProgressIndicator pi = showLoading("Loading admins...");
|
||||
|
||||
Task<List<AdminRow>> task = new Task<>() {
|
||||
@Override protected List<AdminRow> call() {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_group_admins")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("exclude_self", true) // 👈 سرور خودش Owner را حذف کند
|
||||
.put("viewer_id", myUuidStr); // 👈 برای تشخیص self
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res == null || !"success".equalsIgnoreCase(res.optString("status"))) {
|
||||
throw new RuntimeException(res != null ? res.optString("message","Failed to fetch admins.")
|
||||
: "null response");
|
||||
}
|
||||
|
||||
JSONArray arr = res.getJSONObject("data").optJSONArray("admins");
|
||||
List<AdminRow> list = new ArrayList<>();
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject a = arr.getJSONObject(i);
|
||||
list.add(new AdminRow(
|
||||
a.optString("internal_uuid",""),
|
||||
a.optString("user_id",""),
|
||||
a.optString("profile_name","Unknown"),
|
||||
a.optString("avatar_url","")
|
||||
));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(ev -> {
|
||||
hideLoading(pi);
|
||||
|
||||
List<AdminRow> candidates = task.getValue();
|
||||
if (candidates == null || candidates.isEmpty()) {
|
||||
warn("No other admins available. Promote an admin first, then try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
// اگر فقط یکی بود، سریعتر پیش برو
|
||||
if (candidates.size() == 1) {
|
||||
AdminRow target = candidates.get(0);
|
||||
if (!confirm("Confirm Ownership Transfer",
|
||||
"Transfer ownership to " + target.display() + "?\nYou will leave the group afterward.")) return;
|
||||
transferThenLeaveAtomic(groupId, target, myUuidStr);
|
||||
return;
|
||||
}
|
||||
|
||||
// دیالوگ شیک با سرچ + لیست
|
||||
Optional<AdminRow> pick = AdminPickerDialog.show("Transfer Ownership", "Select the new owner", candidates);
|
||||
if (pick.isEmpty()) return;
|
||||
AdminRow target = pick.get();
|
||||
|
||||
if (!confirm("Confirm Ownership Transfer",
|
||||
"Transfer ownership to " + target.display() + "?\nYou will leave the group afterward.")) return;
|
||||
|
||||
transferThenLeaveAtomic(groupId, target, myUuidStr);
|
||||
});
|
||||
|
||||
task.setOnFailed(ev -> {
|
||||
hideLoading(pi);
|
||||
error("Failed to load admins: " + safeMsg(task.getException()));
|
||||
});
|
||||
|
||||
new Thread(task, "load-admins").start();
|
||||
}
|
||||
|
||||
// ==== Prefer ATOMIC request; fallback to classic ====
|
||||
private static void transferThenLeaveAtomic(UUID groupId, AdminRow target, String myUuidStr) {
|
||||
ProgressIndicator pi = showLoading("Transferring & leaving...");
|
||||
|
||||
Task<Void> task = new Task<>() {
|
||||
@Override protected Void call() {
|
||||
// 1) Try atomic endpoint
|
||||
JSONObject atomic = new JSONObject()
|
||||
.put("action", "transfer_and_leave_group") // 👈 اکشن اتمیک
|
||||
.put("group_id", groupId.toString())
|
||||
.put("viewer_id", myUuidStr)
|
||||
.put("new_owner_id", target.requireUuid().toString()); // UUID ضروری
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(atomic);
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
return null; // done
|
||||
}
|
||||
|
||||
// 2) Fallback: classic two-step
|
||||
JSONObject tReq = new JSONObject()
|
||||
.put("action", "transfer_group_ownership")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("new_owner_id", target.requireUuid().toString());
|
||||
|
||||
JSONObject tRes = ActionHandler.sendWithResponse(tReq);
|
||||
if (tRes == null || !"success".equalsIgnoreCase(tRes.optString("status"))) {
|
||||
throw new RuntimeException(tRes != null ? tRes.optString("message","Ownership transfer failed.")
|
||||
: "null response");
|
||||
}
|
||||
|
||||
JSONObject lReq = new JSONObject()
|
||||
.put("action", "leave_chat")
|
||||
.put("user_id", myUuidStr)
|
||||
.put("chat_id", groupId.toString())
|
||||
.put("chat_type", "group");
|
||||
|
||||
JSONObject lRes = ActionHandler.sendWithResponse(lReq);
|
||||
if (lRes == null || !"success".equalsIgnoreCase(lRes.optString("status"))) {
|
||||
throw new RuntimeException(lRes != null ? lRes.optString("message","Leave failed.")
|
||||
: "null response");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(ev -> {
|
||||
hideLoading(pi);
|
||||
info("Done. You left the group.");
|
||||
// TODO: refresh chat list / close current view
|
||||
});
|
||||
|
||||
task.setOnFailed(ev -> {
|
||||
hideLoading(pi);
|
||||
error("Operation failed: " + safeMsg(task.getException()));
|
||||
});
|
||||
|
||||
new Thread(task, "transfer-leave").start();
|
||||
}
|
||||
|
||||
// ==== direct leave for non-owner ====
|
||||
private static void leaveGroupAsync(UUID groupId, String myUuidStr, Runnable onOk) {
|
||||
ProgressIndicator pi = showLoading("Leaving group...");
|
||||
|
||||
Task<Void> task = new Task<>() {
|
||||
@Override protected Void call() {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "leave_chat")
|
||||
.put("user_id", myUuidStr)
|
||||
.put("chat_id", groupId.toString())
|
||||
.put("chat_type", "group");
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res == null || !"success".equalsIgnoreCase(res.optString("status"))) {
|
||||
throw new RuntimeException(res != null ? res.optString("message","Leave failed.") : "null response");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
task.setOnSucceeded(ev -> {
|
||||
hideLoading(pi);
|
||||
if (onOk != null) onOk.run();
|
||||
});
|
||||
task.setOnFailed(ev -> {
|
||||
hideLoading(pi);
|
||||
error("Leave failed: " + safeMsg(task.getException()));
|
||||
});
|
||||
|
||||
new Thread(task, "leave-group").start();
|
||||
}
|
||||
|
||||
// ====== Admin Picker Dialog (with search + avatars) ======
|
||||
private static final class AdminPickerDialog {
|
||||
|
||||
static Optional<AdminRow> show(String title, String header, List<AdminRow> admins) {
|
||||
Dialog<AdminRow> dialog = new Dialog<>();
|
||||
dialog.setTitle(title);
|
||||
dialog.setHeaderText(header);
|
||||
|
||||
ButtonType okType = new ButtonType("OK", ButtonBar.ButtonData.OK_DONE);
|
||||
dialog.getDialogPane().getButtonTypes().addAll(okType, ButtonType.CANCEL);
|
||||
|
||||
TextField search = new TextField();
|
||||
search.setPromptText("Search admin by name or id...");
|
||||
search.setMinHeight(36);
|
||||
|
||||
ListView<AdminRow> list = new ListView<>();
|
||||
list.getItems().setAll(admins);
|
||||
list.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
list.setPrefWidth(480);
|
||||
list.setPrefHeight(360);
|
||||
|
||||
list.setCellFactory(v -> new ListCell<>() {
|
||||
private final HBox row = new HBox(10);
|
||||
private final ImageView avatar = new ImageView();
|
||||
private final Label name = new Label();
|
||||
private final Label id = new Label();
|
||||
{
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
name.getStyleClass().add("admin-name");
|
||||
id.getStyleClass().add("admin-id");
|
||||
HBox text = new HBox(8, name, id);
|
||||
row.getChildren().addAll(avatar, text);
|
||||
}
|
||||
@Override protected void updateItem(AdminRow it, boolean empty) {
|
||||
super.updateItem(it, empty);
|
||||
if (empty || it == null) {
|
||||
setGraphic(null); setText(null);
|
||||
} else {
|
||||
name.setText(it.name);
|
||||
id.setText("• " + it.userId);
|
||||
// آواتار (اختیاری): اگر URL هست نمایش بده
|
||||
if (it.avatarUrl != null && !it.avatarUrl.isBlank()) {
|
||||
try { avatar.setImage(new Image(it.avatarUrl, true)); }
|
||||
catch (Exception e) { avatar.setImage(null); }
|
||||
} else avatar.setImage(null);
|
||||
setGraphic(row); setText(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// جستجو
|
||||
search.textProperty().addListener((obs, o, n) -> {
|
||||
String q = (n == null) ? "" : n.trim().toLowerCase(Locale.ROOT);
|
||||
List<AdminRow> filtered = admins.stream()
|
||||
.filter(a -> a.name.toLowerCase(Locale.ROOT).contains(q)
|
||||
|| normalizeDisplayId(a.userId).contains(q))
|
||||
.collect(Collectors.toList());
|
||||
list.getItems().setAll(filtered);
|
||||
});
|
||||
|
||||
// دابل کلیک = OK
|
||||
list.setOnMouseClicked(e -> {
|
||||
if (e.getClickCount() == 2 && list.getSelectionModel().getSelectedItem() != null) {
|
||||
dialog.setResult(list.getSelectionModel().getSelectedItem());
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
Node okBtn = dialog.getDialogPane().lookupButton(okType);
|
||||
okBtn.setDisable(true);
|
||||
list.getSelectionModel().selectedItemProperty().addListener((obs, old, sel) -> okBtn.setDisable(sel == null));
|
||||
|
||||
VBox content = new VBox(12, search, list);
|
||||
content.setPadding(new Insets(10));
|
||||
dialog.getDialogPane().setContent(content);
|
||||
|
||||
dialog.setResultConverter(bt -> bt == okType ? list.getSelectionModel().getSelectedItem() : null);
|
||||
return dialog.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Models & helpers =====
|
||||
private static final class AdminRow {
|
||||
final UUID internalUuid;
|
||||
final String userId;
|
||||
final String name;
|
||||
final String avatarUrl;
|
||||
|
||||
AdminRow(String internalUuidStr, String userId, String name, String avatarUrl) {
|
||||
this.internalUuid = parseUuidOrNull(internalUuidStr);
|
||||
this.userId = userId == null ? "" : userId;
|
||||
this.name = name == null ? "Unknown" : name;
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
UUID requireUuid() {
|
||||
if (internalUuid == null)
|
||||
throw new IllegalStateException("Admin has no internal_uuid; server must provide it.");
|
||||
return internalUuid;
|
||||
}
|
||||
|
||||
String display() { return name + " (" + userId + ")"; }
|
||||
|
||||
@Override public String toString() {
|
||||
return "AdminRow{name='"+name+"', userId='"+userId+"', uuid="+internalUuid+"}";
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalizeDisplayId(String s) {
|
||||
if (s == null) return "";
|
||||
String t = s.trim();
|
||||
if (t.startsWith("@")) t = t.substring(1);
|
||||
return t.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static UUID parseUuidOrNull(String s) {
|
||||
try { return (s == null || s.isBlank()) ? null : UUID.fromString(s); }
|
||||
catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private static ProgressIndicator showLoading(String title) {
|
||||
Alert a = new Alert(Alert.AlertType.NONE);
|
||||
a.setTitle(title);
|
||||
ProgressIndicator pi = new ProgressIndicator();
|
||||
a.getDialogPane().setContent(pi);
|
||||
a.getButtonTypes().clear();
|
||||
a.show();
|
||||
pi.getProperties().put("alert", a);
|
||||
return pi;
|
||||
}
|
||||
private static void hideLoading(ProgressIndicator pi) {
|
||||
Object a = pi.getProperties().get("alert");
|
||||
if (a instanceof Alert) ((Alert) a).close();
|
||||
}
|
||||
static boolean confirm(String title, String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.CONFIRMATION, msg, ButtonType.OK, ButtonType.CANCEL);
|
||||
a.setTitle(title);
|
||||
Optional<ButtonType> r = a.showAndWait();
|
||||
return r.isPresent() && r.get() == ButtonType.OK;
|
||||
}
|
||||
private static void info(String msg) { new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK).show(); }
|
||||
private static void warn(String msg) { new Alert(Alert.AlertType.WARNING, msg, ButtonType.OK).show(); }
|
||||
private static void error(String msg) { new Alert(Alert.AlertType.ERROR, msg, ButtonType.OK).show(); }
|
||||
private static String safeMsg(Throwable t) { return (t == null || t.getMessage() == null) ? "Unknown error" : t.getMessage(); }
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.to.telegramfinalproject.UI.LeaveGroupFlow.confirm;
|
||||
|
||||
public class OwnerTransferDialogController {
|
||||
|
||||
@FXML private TextField searchField;
|
||||
@FXML private ListView<AdminRow> adminList;
|
||||
@FXML private Button okBtn;
|
||||
@FXML private Button cancelBtn;
|
||||
|
||||
private UUID groupId;
|
||||
private String viewerUuid; // internal UUID string
|
||||
private Runnable onSuccess; // callback برای refresh UI
|
||||
|
||||
private FilteredList<AdminRow> filtered;
|
||||
|
||||
// ===== API =====
|
||||
public void init(UUID groupId, String viewerUuid, Runnable onSuccess) {
|
||||
this.groupId = groupId;
|
||||
this.viewerUuid = viewerUuid;
|
||||
this.onSuccess = onSuccess;
|
||||
setupUI();
|
||||
loadAdmins();
|
||||
}
|
||||
|
||||
private void setupUI() {
|
||||
okBtn.setDisable(true);
|
||||
adminList.getSelectionModel().selectedItemProperty().addListener((obs, o, n) -> okBtn.setDisable(n == null));
|
||||
cancelBtn.setOnAction(e -> cancel());
|
||||
|
||||
// Double-click = OK
|
||||
adminList.setOnMouseClicked(e -> {
|
||||
if (e.getClickCount() == 2 && adminList.getSelectionModel().getSelectedItem() != null) {
|
||||
onOkClick();
|
||||
}
|
||||
});
|
||||
|
||||
// Cell factory (Avatar + name + id)
|
||||
adminList.setCellFactory(v -> new ListCell<>() {
|
||||
private final HBox row = new HBox(10);
|
||||
private final ImageView avatar = new ImageView();
|
||||
private final Label name = new Label();
|
||||
private final Label id = new Label();
|
||||
{
|
||||
avatar.setFitWidth(32); avatar.setFitHeight(32); avatar.setPreserveRatio(true);
|
||||
row.getChildren().addAll(avatar, name, id);
|
||||
}
|
||||
@Override protected void updateItem(AdminRow it, boolean empty) {
|
||||
super.updateItem(it, empty);
|
||||
if (empty || it == null) {
|
||||
setGraphic(null); setText(null);
|
||||
} else {
|
||||
name.setText(it.name);
|
||||
id.setText("• " + it.userId);
|
||||
if (it.avatarUrl != null && !it.avatarUrl.isBlank()) {
|
||||
try { avatar.setImage(new Image(it.avatarUrl, true)); } catch (Exception ex) { avatar.setImage(null); }
|
||||
} else avatar.setImage(null);
|
||||
setGraphic(row); setText(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// search
|
||||
searchField.textProperty().addListener((obs, o, n) -> {
|
||||
final String q = (n == null) ? "" : n.trim().toLowerCase(Locale.ROOT);
|
||||
filtered.setPredicate(makePredicate(q));
|
||||
});
|
||||
|
||||
okBtn.setOnAction(e -> onOkClick());
|
||||
}
|
||||
|
||||
private Predicate<AdminRow> makePredicate(String q) {
|
||||
if (q.isBlank()) return r -> true;
|
||||
return r -> r.name.toLowerCase(Locale.ROOT).contains(q)
|
||||
|| normalizeDisplayId(r.userId).contains(q);
|
||||
}
|
||||
|
||||
private void loadAdmins() {
|
||||
ProgressIndicator pi = modalSpinner("Loading admins...");
|
||||
|
||||
Task<List<AdminRow>> t = new Task<>() {
|
||||
@Override protected List<AdminRow> call() {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_group_admins")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("exclude_self", true)
|
||||
.put("viewer_id", viewerUuid);
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res == null || !"success".equalsIgnoreCase(res.optString("status"))) {
|
||||
throw new RuntimeException(res != null ? res.optString("message","Failed to fetch admins.")
|
||||
: "null response");
|
||||
}
|
||||
JSONArray arr = res.getJSONObject("data").optJSONArray("admins");
|
||||
List<AdminRow> list = new ArrayList<>();
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject a = arr.getJSONObject(i);
|
||||
list.add(new AdminRow(
|
||||
a.optString("internal_uuid",""),
|
||||
a.optString("user_id",""),
|
||||
a.optString("profile_name","Unknown"),
|
||||
a.optString("avatar_url","")
|
||||
));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
t.setOnSucceeded(ev -> {
|
||||
closeSpinner(pi);
|
||||
List<AdminRow> admins = t.getValue();
|
||||
if (admins == null) admins = List.of();
|
||||
if (admins.isEmpty()) {
|
||||
alert(Alert.AlertType.WARNING, "No other admins available. Promote an admin first, then try again.");
|
||||
}
|
||||
filtered = new FilteredList<>(FXCollections.observableArrayList(admins), r -> true);
|
||||
adminList.setItems(filtered);
|
||||
});
|
||||
|
||||
t.setOnFailed(ev -> {
|
||||
closeSpinner(pi);
|
||||
alert(Alert.AlertType.ERROR, "Failed to load admins: " + safeMsg(t.getException()));
|
||||
});
|
||||
|
||||
new Thread(t, "load-admins").start();
|
||||
}
|
||||
|
||||
private void onOkClick() {
|
||||
AdminRow sel = adminList.getSelectionModel().getSelectedItem();
|
||||
if (sel == null) return;
|
||||
|
||||
if (!confirm("Confirm Ownership Transfer",
|
||||
"Transfer ownership to " + sel.display() + "?\nYou will leave the group afterward.")) return;
|
||||
|
||||
ProgressIndicator pi = modalSpinner("Transferring & leaving...");
|
||||
|
||||
Task<Void> t = new Task<>() {
|
||||
@Override protected Void call() {
|
||||
// 1) تلاش برای اکشن اتمیک
|
||||
JSONObject atomic = new JSONObject()
|
||||
.put("action", "transfer_and_leave_group")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("viewer_id", viewerUuid)
|
||||
.put("new_owner_id", sel.requireUuid().toString());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(atomic);
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2) Fallback: دو مرحلهای
|
||||
JSONObject tReq = new JSONObject()
|
||||
.put("action", "transfer_group_ownership")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("new_owner_id", sel.requireUuid().toString());
|
||||
JSONObject tRes = ActionHandler.sendWithResponse(tReq);
|
||||
if (tRes == null || !"success".equalsIgnoreCase(tRes.optString("status"))) {
|
||||
throw new RuntimeException(tRes != null ? tRes.optString("message","Ownership transfer failed.")
|
||||
: "null response");
|
||||
}
|
||||
|
||||
JSONObject lReq = new JSONObject()
|
||||
.put("action", "leave_chat")
|
||||
.put("user_id", viewerUuid)
|
||||
.put("chat_id", groupId.toString())
|
||||
.put("chat_type", "group");
|
||||
JSONObject lRes = ActionHandler.sendWithResponse(lReq);
|
||||
if (lRes == null || !"success".equalsIgnoreCase(lRes.optString("status"))) {
|
||||
throw new RuntimeException(lRes != null ? lRes.optString("message","Leave failed.")
|
||||
: "null response");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
t.setOnSucceeded(ev -> {
|
||||
closeSpinner(pi);
|
||||
alert(Alert.AlertType.INFORMATION, "Done. You left the group.");
|
||||
closeDialog();
|
||||
if (onSuccess != null) onSuccess.run();
|
||||
});
|
||||
|
||||
t.setOnFailed(ev -> {
|
||||
closeSpinner(pi);
|
||||
alert(Alert.AlertType.ERROR, "Operation failed: " + safeMsg(t.getException()));
|
||||
});
|
||||
|
||||
new Thread(t, "transfer-leave").start();
|
||||
}
|
||||
|
||||
private void cancel() { closeDialog(); }
|
||||
|
||||
// ===== Utilities =====
|
||||
private ProgressIndicator modalSpinner(String title) {
|
||||
Alert a = new Alert(Alert.AlertType.NONE);
|
||||
a.setTitle(title);
|
||||
ProgressIndicator pi = new ProgressIndicator();
|
||||
a.getDialogPane().setContent(pi);
|
||||
a.getButtonTypes().clear();
|
||||
a.show();
|
||||
pi.getProperties().put("alert", a);
|
||||
return pi;
|
||||
}
|
||||
private void closeSpinner(ProgressIndicator pi) {
|
||||
Object a = pi.getProperties().get("alert");
|
||||
if (a instanceof Alert) ((Alert)a).close();
|
||||
}
|
||||
|
||||
private void closeDialog() {
|
||||
// بستن پنجره میزبان کنترلر (DialogStage):
|
||||
adminList.getScene().getWindow().hide();
|
||||
}
|
||||
|
||||
private static String normalizeDisplayId(String s) {
|
||||
if (s == null) return "";
|
||||
String t = s.trim();
|
||||
if (t.startsWith("@")) t = t.substring(1);
|
||||
return t.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
private static String safeMsg(Throwable t) { return (t == null || t.getMessage() == null) ? "Unknown error" : t.getMessage(); }
|
||||
|
||||
// ===== Row model =====
|
||||
public static final class AdminRow {
|
||||
public final UUID internalUuid;
|
||||
public final String userId;
|
||||
public final String name;
|
||||
public final String avatarUrl;
|
||||
|
||||
public AdminRow(String internalUuidStr, String userId, String name, String avatarUrl) {
|
||||
this.internalUuid = parseUuidOrNull(internalUuidStr);
|
||||
this.userId = userId == null ? "" : userId;
|
||||
this.name = name == null ? "Unknown" : name;
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
public UUID requireUuid() {
|
||||
if (internalUuid == null) throw new IllegalStateException("admin.internal_uuid missing");
|
||||
return internalUuid;
|
||||
}
|
||||
public String display() { return name + " (" + userId + ")"; }
|
||||
|
||||
private static UUID parseUuidOrNull(String s) {
|
||||
try { return (s == null || s.isBlank()) ? null : UUID.fromString(s); }
|
||||
catch (Exception e) { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
private static void alert(Alert.AlertType type, String msg) {
|
||||
new Alert(type, msg, ButtonType.OK).show();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<StackPane fx:id="root"
|
||||
xmlns="http://javafx.com/javafx/21"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="org.to.telegramfinalproject.UI.ChatPageController">
|
||||
|
||||
<VBox spacing="0.0">
|
||||
<MenuBar>
|
||||
<menus>
|
||||
<Menu text="Chat">
|
||||
<items>
|
||||
<MenuItem fx:id="leaveGroupItem" text="Leave Group"/>
|
||||
</items>
|
||||
</Menu>
|
||||
</menus>
|
||||
</MenuBar>
|
||||
|
||||
<BorderPane prefWidth="900.0" prefHeight="600.0">
|
||||
<center>
|
||||
<ListView fx:id="messagesList" />
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox spacing="8.0" padding="8.0">
|
||||
<TextField fx:id="messageField" HBox.hgrow="ALWAYS" promptText="Type a message..."/>
|
||||
<Button text="Send"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<padding>
|
||||
<Insets top="8" right="8" bottom="8" left="8"/>
|
||||
</padding>
|
||||
</BorderPane>
|
||||
</VBox>
|
||||
|
||||
<StackPane fx:id="overlay" visible="false" mouseTransparent="false"
|
||||
style="-fx-background-color: rgba(0,0,0,0.35);">
|
||||
<ProgressIndicator fx:id="loadingIndicator" maxWidth="80" maxHeight="80"/>
|
||||
</StackPane>
|
||||
|
||||
</StackPane>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Reference in New Issue
Block a user