Merge remote-tracking branch 'origin/Main-UI' into Main-UI
@@ -1,27 +1,32 @@
|
||||
module org.to.telegramfinalproject {
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires javafx.graphics; // بهتره اضافه باشه
|
||||
requires javafx.media;
|
||||
requires javafx.web;
|
||||
|
||||
requires org.json;
|
||||
requires java.sql;
|
||||
requires java.desktop;
|
||||
|
||||
// لایبرریهای جانبی — اگر واقعاً در مسیر ماژول هستند
|
||||
requires org.controlsfx.controls;
|
||||
requires com.dlsc.formsfx;
|
||||
requires net.synedra.validatorfx;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires org.kordamp.bootstrapfx.core;
|
||||
requires eu.hansolo.tilesfx;
|
||||
requires org.json;
|
||||
requires java.sql;
|
||||
requires java.desktop;
|
||||
requires spark.core;
|
||||
requires javax.servlet.api;
|
||||
requires mp3agic;
|
||||
requires spark.core; // فقط اگر واقعاً استفاده میکنی
|
||||
requires javax.servlet.api; // فقط اگر واقعاً استفاده میکنی
|
||||
|
||||
// FXML کنترلرها در این پکیجاند:
|
||||
// کنترلرها/کلاسهایی که FXML به آنها دسترسی بازتابی دارد
|
||||
opens org.to.telegramfinalproject.UI to javafx.fxml;
|
||||
// اگر FXML از کلاسهای Client هم استفاده میکند:
|
||||
opens org.to.telegramfinalproject.Client to javafx.fxml;
|
||||
opens org.to.telegramfinalproject.Models to javafx.fxml; // اگر مدلها داخل FXML مصرف میشوند
|
||||
|
||||
// فقط اگر لازم است از بیرون به این APIها کامپایل شود:
|
||||
// اگر پکیجهایی را میخواهی خارج از ماژول در دسترس قرار دهی
|
||||
exports org.to.telegramfinalproject.UI;
|
||||
exports org.to.telegramfinalproject.Client;
|
||||
// ⚠️ عمداً NOT exporting/opens ریشهی پکیج، چون کلاس ندارد.
|
||||
exports org.to.telegramfinalproject.Models;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ public class AddMembersController {
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button createButton;
|
||||
|
||||
// گروه هدف
|
||||
private UUID groupInternalId;
|
||||
private String groupName;
|
||||
private String groupDisplayId;
|
||||
@@ -42,9 +41,7 @@ public class AddMembersController {
|
||||
|
||||
private Mode mode = Mode.CREATE; // default
|
||||
|
||||
// انتخابها
|
||||
private final Set<Contact> selectedContacts = new HashSet<>();
|
||||
// همهی کانتکتها
|
||||
private final List<Contact> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
@@ -61,7 +58,6 @@ public class AddMembersController {
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// آیکن سرچ با تم
|
||||
Platform.runLater(() -> {
|
||||
if (addMembersCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||
@@ -70,19 +66,15 @@ public class AddMembersController {
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||
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);
|
||||
@@ -132,7 +124,6 @@ public class AddMembersController {
|
||||
allContacts.add(new Contact(id, name, status, imageUrl));
|
||||
}
|
||||
}
|
||||
// اگر خالی بود، چیزی نشون نده (یا میتونی دمو بسازی)
|
||||
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
|
||||
@@ -206,11 +197,9 @@ public class AddMembersController {
|
||||
|
||||
private Image loadAvatar(String path) {
|
||||
try {
|
||||
// 1) اگر ریسورس داخلی باشد
|
||||
var in = getClass().getResourceAsStream(path);
|
||||
if (in != null) return new Image(in);
|
||||
|
||||
// 2) اگر URL کامل یا file URI
|
||||
return new Image(path, true);
|
||||
} catch (Exception e) {
|
||||
return new Image(
|
||||
@@ -233,7 +222,6 @@ public class AddMembersController {
|
||||
|
||||
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())
|
||||
@@ -248,11 +236,10 @@ public class AddMembersController {
|
||||
return;
|
||||
}
|
||||
|
||||
// fallback: تکبهتک
|
||||
boolean allOk = true;
|
||||
for (String uid : ids) {
|
||||
JSONObject single = new JSONObject()
|
||||
.put("action", "add_member_to_group")
|
||||
.put("action", "add_members_to_group")
|
||||
.put("group_id", groupInternalId.toString())
|
||||
.put("user_id", uid);
|
||||
JSONObject r = ActionHandler.sendWithResponse(single);
|
||||
@@ -299,9 +286,8 @@ public class AddMembersController {
|
||||
a.show();
|
||||
}
|
||||
|
||||
// ================== مدل Contact ==================
|
||||
public static class Contact {
|
||||
private final String id; // internal_uuid کاربر
|
||||
private final String id; // internal_uuid
|
||||
private final String name;
|
||||
private final String status;
|
||||
private final String imageUrl;
|
||||
|
||||
@@ -29,20 +29,17 @@ public class AddSubscriberController {
|
||||
@FXML private Button skipButton;
|
||||
@FXML private Button addButton;
|
||||
|
||||
// اطلاعات کانال
|
||||
private UUID channelInternalId;
|
||||
private String channelName;
|
||||
private String channelDisplayId;
|
||||
private File channelImageFile;
|
||||
private String description;
|
||||
|
||||
// انتخابها و منبع داده
|
||||
private final Set<Contact> selected = new HashSet<>();
|
||||
private final List<Contact> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// اسکرول نرم
|
||||
contactsScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
@@ -54,7 +51,6 @@ public class AddSubscriberController {
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// تم آیکن
|
||||
Platform.runLater(() -> {
|
||||
if (addMembersCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||
@@ -63,22 +59,18 @@ public class AddSubscriberController {
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||
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);
|
||||
}
|
||||
|
||||
/** NewChannelController این را بعد از ساخت کانال صدا بزند */
|
||||
public void setChannelInfo(UUID internalId, String name, String displayId, File imageFile, String description) {
|
||||
this.channelInternalId = internalId;
|
||||
this.channelName = name;
|
||||
@@ -87,7 +79,6 @@ public class AddSubscriberController {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
// برای سازگاری با امضای قدیمی (اختیاری)
|
||||
public void setChannelInfo(String name, String id, String description, File image) {
|
||||
this.channelName = name;
|
||||
this.channelDisplayId = id;
|
||||
@@ -226,7 +217,6 @@ public class AddSubscriberController {
|
||||
return;
|
||||
}
|
||||
|
||||
// fallback: تکبهتک
|
||||
boolean allOk = true;
|
||||
for (String uid : ids) {
|
||||
JSONObject single = new JSONObject()
|
||||
@@ -277,9 +267,8 @@ public class AddSubscriberController {
|
||||
a.show();
|
||||
}
|
||||
|
||||
// ===== مدل Contact =====
|
||||
public static class Contact {
|
||||
private final String id; // internal_uuid کاربر
|
||||
private final String id; // internal_uuid
|
||||
private final String name;
|
||||
private final String status;
|
||||
private final String imageUrl;
|
||||
|
||||
@@ -27,14 +27,12 @@ public class BlockedUsersController {
|
||||
@FXML private ListView<HBox> blockedList;
|
||||
@FXML private VBox blockedCard;
|
||||
|
||||
// نگاشت برای حذف سریع ردیف بعد از Unblock
|
||||
private final Map<UUID, HBox> rowByUserId = new HashMap<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
backButton.setGraphic(makeIcon("/org/to/telegramfinalproject/Icons/back_button_dark.png"));
|
||||
|
||||
// بارگذاری واقعی از سرور
|
||||
loadBlockedUsers();
|
||||
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayBackground.getParent()));
|
||||
@@ -72,7 +70,6 @@ public class BlockedUsersController {
|
||||
/* ===================== Networking ===================== */
|
||||
|
||||
private void loadBlockedUsers() {
|
||||
// درخواست به سرور
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject().put("action", "get_blocked_users");
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
@@ -102,8 +99,8 @@ public class BlockedUsersController {
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "toggle_block")
|
||||
.put("user_id", me) // UUID من
|
||||
.put("target_id", targetId.toString()); // UUID طرف
|
||||
.put("user_id", me) // UUID
|
||||
.put("target_id", targetId.toString()); // UUID
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
boolean ok = resp != null && "success".equalsIgnoreCase(resp.optString("status"));
|
||||
@@ -127,7 +124,6 @@ public class BlockedUsersController {
|
||||
blockedList.getItems().clear();
|
||||
rowByUserId.clear();
|
||||
|
||||
// انتظار میره هر آبجکت حداقل: internal_uuid, profile_name, user_id/username/display_id, image_url داشته باشه
|
||||
IntStream.range(0, list.length()).forEach(i -> {
|
||||
JSONObject o = list.optJSONObject(i);
|
||||
if (o == null) return;
|
||||
@@ -229,7 +225,6 @@ public class BlockedUsersController {
|
||||
}
|
||||
|
||||
private void showToast(String msg) {
|
||||
// ساده: از Alert استفاده کن؛ اگر Toast اختصاصی داری جایگزینش کن
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
if (a.getDialogPane().getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(a.getDialogPane().getScene());
|
||||
|
||||
@@ -34,7 +34,6 @@ public class ChangeCredentialsController {
|
||||
private boolean passwordVisible = false;
|
||||
private boolean confirmVisible = false;
|
||||
|
||||
// ↓↓↓ متدی که از CheckPasswordController مقدار میگیرد
|
||||
private String currentPassword;
|
||||
public void setCurrentPassword(String p) { this.currentPassword = p; }
|
||||
|
||||
@@ -44,7 +43,6 @@ public class ChangeCredentialsController {
|
||||
togglePasswordBtn.setOnAction(e -> togglePasswordVisibility());
|
||||
toggleConfirmBtn.setOnAction(e -> toggleConfirmVisibility());
|
||||
|
||||
// «ذخیره و بستن» مثل ادیت پروفایل
|
||||
saveButton.setOnAction(e -> saveAndClose());
|
||||
closeButton.setOnAction(e -> saveAndClose());
|
||||
cancelButton.setOnAction(e -> saveAndClose());
|
||||
@@ -78,7 +76,6 @@ public class ChangeCredentialsController {
|
||||
Platform.runLater(() -> usernameField.requestFocus());
|
||||
}
|
||||
|
||||
// ← از CheckPasswordController بلافاصله بعد از load صدا بزن
|
||||
public void prefillFromSession() {
|
||||
var u = Session.currentUser;
|
||||
if (u == null) return;
|
||||
@@ -112,12 +109,10 @@ public class ChangeCredentialsController {
|
||||
toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
// ارسال تغییرات (در صورت پر بودن/تغییر) و بستن
|
||||
private void saveAndClose() {
|
||||
boolean anyError = false;
|
||||
boolean sentSomething = false;
|
||||
|
||||
// 1) یوزرنیم
|
||||
var cu = Session.currentUser;
|
||||
String oldUsername = cu != null ? cu.optString("user_id",
|
||||
cu.optString("username",
|
||||
@@ -129,7 +124,7 @@ public class ChangeCredentialsController {
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "update_username")
|
||||
.put("current_password", currentPassword) // با اینکه سرور فعلاً چک نمیکند، میفرستیم
|
||||
.put("current_password", currentPassword)
|
||||
.put("new_username", newUsername);
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
@@ -150,7 +145,7 @@ public class ChangeCredentialsController {
|
||||
if (confirm.isEmpty() || !confirm.equals(newPwd)) {
|
||||
addError(confirmPasswordField, confirmPasswordLabel);
|
||||
addError(visibleConfirmPasswordField, confirmPasswordLabel);
|
||||
return; // نذار بسته شه تا کاربر درست کنه
|
||||
return;
|
||||
}
|
||||
|
||||
sentSomething = true;
|
||||
@@ -168,7 +163,6 @@ public class ChangeCredentialsController {
|
||||
}
|
||||
}
|
||||
|
||||
// اگر چیزی نفرستادیم یا همهچیز OK بود → ببند و رفرش Settings
|
||||
if (!sentSomething || !anyError) {
|
||||
var sc = SettingsController.getInstance();
|
||||
if (sc != null) sc.populateFromSession();
|
||||
|
||||
@@ -7,6 +7,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -120,8 +121,7 @@ public class ChatItemController {
|
||||
|
||||
// ChatItemController.java
|
||||
@FXML private ImageView avatarImage;
|
||||
// اگر نوع چت نیاز داری برای دیفالتها (private|group|channel)
|
||||
private String chatType; // اگر داری از setChatData بگیرش و نگه دار
|
||||
private String chatType;
|
||||
|
||||
public void updateAvatar(String url) {
|
||||
try {
|
||||
@@ -141,4 +141,10 @@ public class ChatItemController {
|
||||
}
|
||||
}
|
||||
|
||||
private String safeTitle(ChatEntry e) {
|
||||
if (e.getName() != null && !e.getName().isBlank()) return e.getName();
|
||||
if (e.getDisplayId() != null && !e.getDisplayId().isBlank()) return e.getDisplayId();
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ public class CheckPasswordController {
|
||||
private void validatePassword() {
|
||||
String entered = (passwordVisible ? visiblePasswordField.getText() : passwordField.getText()).trim();
|
||||
if (entered.isEmpty()) {
|
||||
// استایل خطا مثل قبل
|
||||
if (!passwordField.getStyleClass().contains("error"))
|
||||
passwordField.getStyleClass().add("error");
|
||||
if (!visiblePasswordField.getStyleClass().contains("error"))
|
||||
@@ -110,7 +109,6 @@ public class CheckPasswordController {
|
||||
return;
|
||||
}
|
||||
|
||||
// تماس با سرور
|
||||
org.json.JSONObject req = new org.json.JSONObject()
|
||||
.put("action", "verify_password")
|
||||
.put("current_password", entered);
|
||||
@@ -119,12 +117,11 @@ public class CheckPasswordController {
|
||||
boolean ok = (resp != null && "success".equalsIgnoreCase(resp.optString("status")));
|
||||
|
||||
if (ok) {
|
||||
// پاک کردن خطاها
|
||||
passwordField.getStyleClass().remove("error");
|
||||
visiblePasswordField.getStyleClass().remove("error");
|
||||
passwordLabel.getStyleClass().remove("error");
|
||||
|
||||
openChangeCredentials(entered); // ← پسورد فعلی را پاس بده
|
||||
openChangeCredentials(entered);
|
||||
} else {
|
||||
if (!passwordField.getStyleClass().contains("error"))
|
||||
passwordField.getStyleClass().add("error");
|
||||
@@ -165,8 +162,8 @@ public class CheckPasswordController {
|
||||
Node overlay = loader.load();
|
||||
|
||||
ChangeCredentialsController c = loader.getController();
|
||||
c.setCurrentPassword(currentPassword); // ← مهم
|
||||
c.prefillFromSession(); // ← پیشپر کردن
|
||||
c.setCurrentPassword(currentPassword);
|
||||
c.prefillFromSession();
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException ex) {
|
||||
|
||||
@@ -30,15 +30,12 @@ public class ContactsController {
|
||||
@FXML private ScrollPane contactsScroll;
|
||||
@FXML private Button searchIcon;
|
||||
|
||||
/** منبع داده UI — با کانتکتهای واقعی پر میشود */
|
||||
private final List<ContactVM> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// 1) لود اولیهی کانتکتها از سشن/سرور
|
||||
loadContactsAndRender();
|
||||
|
||||
// 2) سرچ محلی روی لیست
|
||||
searchField.textProperty().addListener((obs, ov, nv) -> {
|
||||
String f = nv == null ? "" : nv.trim().toLowerCase();
|
||||
List<ContactVM> filtered = allContacts.stream()
|
||||
@@ -49,14 +46,11 @@ public class ContactsController {
|
||||
renderContacts(filtered);
|
||||
});
|
||||
|
||||
// 3) فوکوس خودکار روی سرچ
|
||||
Platform.runLater(() -> searchField.requestFocus());
|
||||
|
||||
// 4) بستن اورلی
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(contactsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(contactsCard.getParent()));
|
||||
|
||||
// 5) اسکرول نرم
|
||||
contactsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
contactsScroll.setPannable(true);
|
||||
contactsScroll.setFitToWidth(true);
|
||||
@@ -66,23 +60,18 @@ public class ContactsController {
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
// 6) ثبت صحنه برای ThemeManager
|
||||
Platform.runLater(() -> {
|
||||
if (contactsCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(contactsCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// 7) واکنش به تغییر تم
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal));
|
||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// لود داده و رندر لیست
|
||||
// -----------------------
|
||||
|
||||
private void loadContactsAndRender() {
|
||||
// اگر Session.contactEntries از قبل لود شده، از همان استفاده میکنیم.
|
||||
CompletableFuture
|
||||
.supplyAsync(() -> {
|
||||
try {
|
||||
@@ -90,10 +79,9 @@ public class ContactsController {
|
||||
return new ArrayList<>(Session.contactEntries);
|
||||
}
|
||||
|
||||
// در غیر این صورت از سرور میگیریم (اختیاری)
|
||||
// اگر API «view_contacts» داری، اینجا بفرست:
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_contacts") // 🔧 اگر نام اکشنات فرق دارد، تغییر بده
|
||||
.put("action", "view_contacts")
|
||||
.put("user_id", Session.getUserUUID());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
@@ -122,7 +110,6 @@ public class ContactsController {
|
||||
|
||||
fetched.add(new ContactEntry(contactId, userId, contactDisplay, profileName, imageUrl, isBlocked, lastSeen));
|
||||
}
|
||||
// اگر میخواهی تو سشن هم نگه داری:
|
||||
if (Session.contactEntries == null) Session.contactEntries = new ArrayList<>();
|
||||
Session.contactEntries.clear();
|
||||
Session.contactEntries.addAll(fetched);
|
||||
@@ -161,7 +148,6 @@ public class ContactsController {
|
||||
item.getStyleClass().add("contact-item");
|
||||
item.setCursor(Cursor.HAND);
|
||||
|
||||
// آواتار
|
||||
ImageView avatar = new ImageView(loadAvatarSafe(c.imageUrl));
|
||||
avatar.setFitWidth(58);
|
||||
avatar.setFitHeight(58);
|
||||
@@ -183,13 +169,20 @@ public class ContactsController {
|
||||
|
||||
private Image loadAvatarSafe(String urlOrResource) {
|
||||
try {
|
||||
if (urlOrResource != null && urlOrResource.startsWith("/")) {
|
||||
return new Image(Objects.requireNonNull(getClass().getResourceAsStream(urlOrResource)));
|
||||
if (urlOrResource != null) {
|
||||
if (urlOrResource.startsWith("/")) {
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream(urlOrResource)));
|
||||
}
|
||||
if (urlOrResource.startsWith("http://") ||
|
||||
urlOrResource.startsWith("https://") ||
|
||||
urlOrResource.startsWith("file:")) {
|
||||
return new Image(urlOrResource, true); // true = لود async
|
||||
}
|
||||
}
|
||||
// اگر URL وب هم داری، میتونی مستقیم Image(url) بسازی
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
|
||||
} catch (Exception ignore) {
|
||||
} catch (Exception e) {
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
|
||||
}
|
||||
@@ -283,12 +276,10 @@ public class ContactsController {
|
||||
}
|
||||
|
||||
|
||||
// -----------------------
|
||||
// ViewModel سادهی کانتکت
|
||||
// -----------------------
|
||||
|
||||
private static class ContactVM {
|
||||
final UUID contactId; // internal UUID (واقعی)
|
||||
final String userId; // @id نمایش (اختیاری)
|
||||
final UUID contactId; // internal UUID
|
||||
final String userId; // @id
|
||||
final String profileName;
|
||||
final String imageUrl;
|
||||
|
||||
|
||||
@@ -137,8 +137,8 @@ public class EditProfileController {
|
||||
File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow());
|
||||
|
||||
if (file != null) {
|
||||
pickedAvatarFile = file; // ← ذخیره کن برای ارسال هنگام خروج
|
||||
profileImageView.setImage(new Image(file.toURI().toString())); // پیشنمایش
|
||||
pickedAvatarFile = file;
|
||||
profileImageView.setImage(new Image(file.toURI().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,16 +192,13 @@ public class EditProfileController {
|
||||
if (ActionHandler.instance.wasSuccess()) {
|
||||
String url = ActionHandler.instance.getLastMessage();
|
||||
if (url != null && !url.isBlank()) {
|
||||
// قبلاً اینجا مستقیم bust میزدیم → مشکل ایجاد میکرد
|
||||
// currentUser.put("image_url", url);
|
||||
|
||||
// ✔️ نسخهی امن:
|
||||
String extracted = extractImageUrl(url); // از helper پایین
|
||||
|
||||
String extracted = extractImageUrl(url);
|
||||
if (isLikelyImageUrl(extracted)) {
|
||||
String finalUrl = addCacheBusterIfHttp(extracted); // فقط برای http/https
|
||||
String finalUrl = addCacheBusterIfHttp(extracted);
|
||||
Session.currentUser.put("image_url", finalUrl);
|
||||
} else {
|
||||
// اگر سرور چیز عجیبی مثل "Welcome Farid" برگردوند، نادیده بگیر
|
||||
System.out.println("Upload avatar returned a non-image value: " + url);
|
||||
}
|
||||
}
|
||||
@@ -267,15 +264,12 @@ public class EditProfileController {
|
||||
);
|
||||
}
|
||||
|
||||
// 2) خودِ کارت Settings را فوراً از Session ریفرش کن
|
||||
if (parentSettings != null) {
|
||||
parentSettings.populateFromSession();
|
||||
}
|
||||
|
||||
// 3) سایدبار بالایی (اسم/آواتار خود کاربر) اگر داری
|
||||
MainController.getInstance().refreshSidebarUserFromSession();
|
||||
|
||||
// 4) بستن اورلی ادیت
|
||||
// MainController.getInstance().goBack(overlayBackground);
|
||||
MainController.getInstance().closeOverlay(editCard.getParent());
|
||||
|
||||
@@ -302,7 +296,6 @@ public class EditProfileController {
|
||||
|
||||
private boolean isLikelyImageUrl(String s) {
|
||||
if (s == null || s.isBlank()) return false;
|
||||
// URL کامل، مسیر نسبی سرور، یا file: و پسوند تصویر
|
||||
return s.startsWith("http://") || s.startsWith("https://")
|
||||
|| s.startsWith("file:")
|
||||
|| s.startsWith("/")
|
||||
@@ -314,19 +307,15 @@ public class EditProfileController {
|
||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||
return url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||
}
|
||||
// برای مسیر لوکال/نسبی اصلاً ?v اضافه نکن (روی ویندوز مشکل میدهد)
|
||||
return url;
|
||||
}
|
||||
|
||||
/** اگر سرور بهجای URL، JSON یا متن دیگری داد، اینجا URL را دربیار. */
|
||||
private String extractImageUrl(String raw) {
|
||||
if (raw == null) return null;
|
||||
String s = raw.trim();
|
||||
// اگر برگشتی JSON است (بعضی سرویسها data.display_url برمیگردانند)
|
||||
if (s.startsWith("{") && s.endsWith("}")) {
|
||||
try {
|
||||
org.json.JSONObject j = new org.json.JSONObject(s);
|
||||
// هر کلیدی که سرویسات استفاده میکند را امتحان کن
|
||||
if (j.has("display_url")) return j.optString("display_url", null);
|
||||
if (j.has("url")) return j.optString("url", null);
|
||||
if (j.has("data")) {
|
||||
@@ -338,7 +327,7 @@ public class EditProfileController {
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
return s; // در غیر این صورت همان raw
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -291,10 +291,45 @@ public class GroupInfoController {
|
||||
}
|
||||
|
||||
private void handleDeleteGroup() {
|
||||
System.out.println("Deleting group...");
|
||||
// TODO: implement backend call
|
||||
ChatEntry entry = Session.currentChatEntry;
|
||||
|
||||
// if (entry == null || !"group".equalsIgnoreCase(entry.getType())) {
|
||||
// alert(Alert.AlertType.INFORMATION, "This action is only available for groups.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (!confirm("Delete Group",
|
||||
"Are you sure you want to DELETE this group?\nThis action cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "delete_group")
|
||||
.put("group_id", entry.getId().toString());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
alert(Alert.AlertType.INFORMATION, "✅ Group deleted successfully.");
|
||||
MainController.getInstance().refreshChatListUI();
|
||||
AppRouter.showMain();
|
||||
} else {
|
||||
String msg = (res != null) ? res.optString("message", "Failed to delete group.") : "null response";
|
||||
alert(Alert.AlertType.ERROR, "❌ " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean confirm(String title, String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.CONFIRMATION, msg, ButtonType.OK, ButtonType.CANCEL);
|
||||
a.setTitle(title);
|
||||
return a.showAndWait().filter(btn -> btn == ButtonType.OK).isPresent();
|
||||
}
|
||||
private void alert(Alert.AlertType type, String msg) {
|
||||
new Alert(type, msg, ButtonType.OK).show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void updateIcons(boolean dark) {
|
||||
String suffix = dark ? "_light.png" : "_dark.png";
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ public class IntroController {
|
||||
|
||||
@FXML
|
||||
private void handleStartMessaging(ActionEvent event) {
|
||||
AppRouter.showLogin(); // همون Scene میمونه، فقط Root عوض میشه
|
||||
AppRouter.showLogin();
|
||||
}
|
||||
|
||||
@FXML private void goRegister() { AppRouter.showRegister(); }
|
||||
|
||||
@@ -151,8 +151,8 @@ public class LoginController {
|
||||
return;
|
||||
}
|
||||
|
||||
// موفق: Session از داخل send پر شده
|
||||
goMain(); // بدون Alert → مستقیم به main.fxml
|
||||
|
||||
goMain();
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
@@ -200,7 +200,7 @@ public class LoginController {
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||
pause.setOnFinished(event -> {
|
||||
errorLabel.setText(message);
|
||||
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||
errorLabel.setStyle("-fx-text-fill: red;");
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
pause.play();
|
||||
|
||||
@@ -13,6 +13,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
@@ -96,8 +97,12 @@ public class MainController {
|
||||
// Keep track of the scene user comes from
|
||||
private final Deque<Node> navigationStack = new ArrayDeque<>();
|
||||
private final Map<UUID, ChatItemController> itemControllers = new HashMap<>();
|
||||
private final java.util.Set<UUID> enrichInFlight = java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>());
|
||||
|
||||
|
||||
private static final String SAVED_TITLE = "Saved Messages";
|
||||
private static final String SAVED_AVATAR = "/org/to/telegramfinalproject/Avatars/saved_messages.png";
|
||||
|
||||
//For realtime handling
|
||||
public void onChatUpdated(UUID chatId, String chatType, LocalDateTime lastTs,
|
||||
boolean isIncoming, String lastPreview) {
|
||||
@@ -351,10 +356,8 @@ public class MainController {
|
||||
}
|
||||
}
|
||||
|
||||
// اگر “Archived Chats” یا هدر دیگری داری، قبلش اضافه کن (اختیاری)
|
||||
// addArchivedHeaderIfYouHaveOne();
|
||||
// addArchivedHeaderIfYouHaveOne();
|
||||
|
||||
// 1) همیشه Saved اول بیاد (اگر وجود داشت)
|
||||
if (saved != null) {
|
||||
addChatNode(saved);
|
||||
}
|
||||
@@ -408,33 +411,98 @@ public class MainController {
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private void addChatNode(ChatEntry chat) {
|
||||
try {
|
||||
FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||
Node item = fx.load();
|
||||
ChatItemController cc = fx.getController();
|
||||
|
||||
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||
// --- saved detection
|
||||
boolean saved = isSaved(chat);
|
||||
|
||||
String timeText = chat.getLastMessageTime() == null
|
||||
? ""
|
||||
: formatChatTime(chat.getLastMessageTime());
|
||||
String title = safeTitle(chat);
|
||||
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||
String timeText = chat.getLastMessageTime() == null ? "" : formatChatTime(chat.getLastMessageTime());
|
||||
String imageUrl = safeImage(chat.getImageUrl());
|
||||
|
||||
// If chat has a profile picture, pass it; otherwise null
|
||||
String imageUrl = (chat.getImageUrl() != null && !chat.getImageUrl().isEmpty())
|
||||
? chat.getImageUrl()
|
||||
: null;
|
||||
// --- force Saved Messages title & avatar
|
||||
if (saved) {
|
||||
title = SAVED_TITLE;
|
||||
imageUrl = SAVED_AVATAR;
|
||||
}
|
||||
|
||||
cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), imageUrl, chat.getType());
|
||||
cc.setChatData(title, preview, timeText, chat.getUnreadCount(), imageUrl, chat.getType());
|
||||
item.setOnMouseClicked(e -> openChat(chat));
|
||||
chatListContainer.getChildren().add(item);
|
||||
|
||||
itemControllers.put(chat.getId(), cc);
|
||||
|
||||
// --- no enrich for Saved
|
||||
boolean needName = (chat.getName() == null || chat.getName().isBlank());
|
||||
boolean needImage = (chat.getImageUrl() == null || chat.getImageUrl().isBlank());
|
||||
if (!saved && (needName || needImage)) {
|
||||
enrichChatEntryAsync(chat);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
private boolean isSaved(ChatEntry e) {
|
||||
if (e == null) return false;
|
||||
if (e.isSavedMessages()) return true;
|
||||
return "saved".equalsIgnoreCase(e.getType()); // اگر type اختصاصی داری
|
||||
}
|
||||
|
||||
|
||||
public void updateSingleChatCell(ChatEntry entry) {
|
||||
ChatItemController cc = itemControllers.get(entry.getId());
|
||||
if (cc != null) {
|
||||
Platform.runLater(() -> cc.setChatData(
|
||||
safeTitle(entry),
|
||||
entry.getLastMessagePreview() == null ? "" : entry.getLastMessagePreview(),
|
||||
entry.getLastMessageTime() == null ? "" : formatChatTime(entry.getLastMessageTime()),
|
||||
entry.getUnreadCount(),
|
||||
safeImage(entry.getImageUrl()),
|
||||
entry.getType()
|
||||
));
|
||||
} else {
|
||||
refreshChatListUI(); // اگر پیدا نشد، کل لیست را رفرش کن
|
||||
}
|
||||
}
|
||||
|
||||
public void enrichChatEntryAsync(ChatEntry entry) {
|
||||
if (entry == null) return;
|
||||
// اگر در حال دریافت هستیم، تکراری نفرست
|
||||
if (!enrichInFlight.add(entry.getId())) return;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "get_header_info")
|
||||
.put("receiver_id", entry.getId().toString())
|
||||
.put("receiver_type", entry.getType())
|
||||
.put("viewer_id", Session.getUserUUID());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
JSONObject d = res.optJSONObject("data");
|
||||
if (d != null) {
|
||||
String name = d.optString("name", "");
|
||||
String image = d.optString("image_url", "");
|
||||
Platform.runLater(() -> {
|
||||
if (!name.isBlank()) entry.setName(name);
|
||||
if (!image.isBlank()) entry.setImageUrl(image);
|
||||
updateSingleChatCell(entry); // فقط همان آیتم را نوسازی کن
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} finally {
|
||||
enrichInFlight.remove(entry.getId());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private String mapTypeToLabel(String t) {
|
||||
switch (t.toUpperCase()) {
|
||||
@@ -992,7 +1060,6 @@ public class MainController {
|
||||
private void openSearchResult(SearchResult r) {
|
||||
switch (r.type) {
|
||||
case USER: {
|
||||
// 1) اگه قبلاً چت پرایوت با این یوزر داری، از همون استفاده کن
|
||||
UUID existingChatId = findExistingPrivateChatId(r.uuid);
|
||||
|
||||
ChatEntry ce = new ChatEntry();
|
||||
@@ -1006,8 +1073,7 @@ public class MainController {
|
||||
ce.setId(existingChatId.toString());
|
||||
mode = ChatViewMode.NORMAL;
|
||||
} else {
|
||||
// هنوز چتی وجود ندارد → Preview (بدون ساخت چت)
|
||||
// برای Preview از uuid خودِ طرف مقابل بهعنوان id موقت استفاده میکنیم
|
||||
|
||||
ce.setId(r.uuid.toString());
|
||||
mode = isContact(r.uuid) ? ChatViewMode.NORMAL : ChatViewMode.NEEDS_ADD_CONTACT;
|
||||
}
|
||||
@@ -1120,19 +1186,16 @@ public class MainController {
|
||||
private boolean isSavedMessages(ChatEntry c) {
|
||||
if (c == null) return false;
|
||||
|
||||
// اگر تایپ اختصاصی داری
|
||||
if ("saved".equalsIgnoreCase(c.getType())) return true;
|
||||
|
||||
// اگر با نام مشخص ذخیره میکنی
|
||||
String n = c.getName();
|
||||
if (n != null && n.equalsIgnoreCase("Saved Messages")) return true;
|
||||
|
||||
// حالت پرایوت با خودِ کاربر
|
||||
String me = (org.to.telegramfinalproject.Client.Session.currentUser != null)
|
||||
? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "")
|
||||
: "";
|
||||
try {
|
||||
UUID other = c.getOtherUserId(); // اگر این فیلد را داری
|
||||
UUID other = c.getOtherUserId();
|
||||
if ("private".equalsIgnoreCase(c.getType()) &&
|
||||
other != null && other.toString().equalsIgnoreCase(me)) {
|
||||
return true;
|
||||
@@ -1201,13 +1264,11 @@ public class MainController {
|
||||
if (Session.chatList == null) Session.chatList = new ArrayList<>();
|
||||
Session.chatList.add(ce);
|
||||
}
|
||||
// اگر activeChats استفاده میکنی:
|
||||
if (Session.activeChats != null && Session.activeChats.stream().noneMatch(c -> id.equals(c.getId()))) {
|
||||
Session.activeChats.add(ce);
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
// (اختیاری) مرتبسازی بر اساس زمان آخرین پیام
|
||||
Comparator<ChatEntry> byTimeDesc = (a,b) -> {
|
||||
LocalDateTime t1 = a.getLastMessageTime(), t2 = b.getLastMessageTime();
|
||||
if (t1 == null && t2 == null) return 0;
|
||||
@@ -1223,18 +1284,15 @@ 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(); // اگر نداری، این خط را حذف کن
|
||||
this.refreshChatListUI();
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
// نمایش پیج چت
|
||||
if (getChatPageController() != null) {
|
||||
getChatPageController().showChat(entry);
|
||||
}
|
||||
@@ -1255,13 +1313,11 @@ public class MainController {
|
||||
public void ensureArchivedHeaderRow() {
|
||||
boolean hasArchived = Session.archivedChats != null && !Session.archivedChats.isEmpty();
|
||||
|
||||
// اگر آرشیو نداریم، هدر قبلی را حذف کن و برگرد
|
||||
if (!hasArchived) {
|
||||
chatListContainer.getChildren().removeIf(n -> ARCHIVED_ROW_KEY.equals(n.getUserData()));
|
||||
return;
|
||||
}
|
||||
|
||||
// اگر قبلاً هست، دوباره نساز
|
||||
boolean exists = chatListContainer.getChildren().stream()
|
||||
.anyMatch(n -> ARCHIVED_ROW_KEY.equals(n.getUserData()));
|
||||
if (exists) return;
|
||||
@@ -1278,7 +1334,6 @@ public class MainController {
|
||||
headerRow.getChildren().addAll(title, spacer, openBtn);
|
||||
headerRow.setMinHeight(36); headerRow.setPrefHeight(36);
|
||||
|
||||
// در ابتدای همان لیست اصلیِ چتها
|
||||
chatListContainer.getChildren().add(0, headerRow);
|
||||
}
|
||||
|
||||
@@ -1290,7 +1345,6 @@ public class MainController {
|
||||
public void refreshArchivedListUI() {
|
||||
chatListContainer.getChildren().clear();
|
||||
|
||||
// Back row (بماند)
|
||||
HBox backRow = new HBox();
|
||||
Label back = new Label("← Back to Active");
|
||||
back.getStyleClass().add("archived-back");
|
||||
@@ -1302,10 +1356,9 @@ public class MainController {
|
||||
backRow.setStyle("-fx-padding: 6 10 8 10;");
|
||||
chatListContainer.getChildren().add(backRow);
|
||||
|
||||
// ✅ از همین سلساز FXML استفاده کن تا دقیقا همشکل اکتیو باشد
|
||||
if (Session.archivedChats != null && !Session.archivedChats.isEmpty()) {
|
||||
for (ChatEntry e : Session.archivedChats) {
|
||||
addChatNode(e); // ⬅️ همونی که در اکتیو استفاده میکنی
|
||||
addChatNode(e);
|
||||
}
|
||||
} else {
|
||||
Label empty = new Label("No archived chats");
|
||||
@@ -1316,29 +1369,40 @@ public class MainController {
|
||||
|
||||
|
||||
|
||||
// ساخت یک سل آیتم (برای اکتیو/آرشیو)
|
||||
private HBox buildChatCell(ChatEntry e, boolean archivedView) {
|
||||
HBox row = new HBox(8);
|
||||
ImageView avatar = new ImageView(); avatar.setFitWidth(36); avatar.setFitHeight(36);
|
||||
// ... لود تصویر از e.getImageUrl() اگر داری
|
||||
Label name = new Label(e.getName());
|
||||
Region spacer = new Region(); HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS);
|
||||
Label last = new Label(safeLastLine(e)); // آخرین پیام/زمان (اختیاری)
|
||||
Label last = new Label(safeLastLine(e));
|
||||
|
||||
row.getChildren().addAll(avatar, name, spacer, last);
|
||||
row.getStyleClass().add("chat-row");
|
||||
|
||||
// کلیک روی آیتم → openChat
|
||||
row.setOnMouseClicked(ev -> openChat(e));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private String safeLastLine(ChatEntry e) {
|
||||
String s = e.getLastMessagePreview(); // اگر داری
|
||||
String s = e.getLastMessagePreview();
|
||||
return s != null ? s : "";
|
||||
}
|
||||
|
||||
|
||||
private String safeTitle(ChatEntry e) {
|
||||
if (e.getName() != null && !e.getName().isBlank()) return e.getName();
|
||||
if (e.getDisplayId() != null && !e.getDisplayId().isBlank()) return e.getDisplayId();
|
||||
if ("saved".equalsIgnoreCase(e.getType()) || e.isSavedMessages()) return "Saved Messages";
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
private String safeImage(String img) {
|
||||
return (img != null && !img.isBlank())
|
||||
? img
|
||||
: "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -116,13 +118,27 @@ public class ManageGroupController {
|
||||
groupIdField.setText(originalGroupId);
|
||||
|
||||
if (!originalImageUrl.isBlank()) {
|
||||
groupImage.setImage(new Image(originalImageUrl, true));
|
||||
String uri = resolveLocalUri(originalImageUrl);
|
||||
if (uri != null) {
|
||||
groupImage.setImage(new Image(uri, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
adminCount.setText(String.valueOf(countRole(data, "admin") + 1));
|
||||
memberCount.setText(String.valueOf(countRole(data, "member") + Integer.parseInt(adminCount.getText())));
|
||||
}
|
||||
|
||||
private String resolveLocalUri(String raw) {
|
||||
if (raw == null || raw.isBlank()) return null;
|
||||
if (raw.startsWith("file:/") || raw.startsWith("http")) return raw;
|
||||
|
||||
Path base = Paths.get(System.getProperty("user.dir"), "uploads");
|
||||
Path resolved = base.resolve(raw.startsWith("/") ? raw.substring(1) : raw)
|
||||
.normalize();
|
||||
return resolved.toUri().toString(); // file:///C:/.../uploads/avatars/2025-09-05/...
|
||||
}
|
||||
|
||||
private int countRole(JSONObject groupData, String role) {
|
||||
var arr = groupData.optJSONArray("members");
|
||||
if (arr == null) return 0;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class MediaPathResolver {
|
||||
private final Path uploadsRoot;
|
||||
|
||||
public MediaPathResolver(Path uploadsRoot) {
|
||||
this.uploadsRoot = uploadsRoot;
|
||||
}
|
||||
|
||||
public String toFileUri(String raw) {
|
||||
if (raw == null || raw.isBlank()) return null;
|
||||
if (raw.startsWith("file:/")) return raw;
|
||||
|
||||
Path p;
|
||||
if (raw.startsWith("/")) {
|
||||
String sub = raw.substring(1).replace("/", File.separator);
|
||||
p = uploadsRoot.resolve(sub).normalize();
|
||||
} else {
|
||||
p = Paths.get(raw).normalize();
|
||||
}
|
||||
return p.toUri().toString();
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |