This commit is contained in:
2025-09-08 11:39:59 +03:30
parent fa1c9636c1
commit d2b57157d6
12 changed files with 23 additions and 71 deletions
@@ -100,7 +100,6 @@ public class CheckPasswordController {
private void validatePassword() { private void validatePassword() {
String entered = (passwordVisible ? visiblePasswordField.getText() : passwordField.getText()).trim(); String entered = (passwordVisible ? visiblePasswordField.getText() : passwordField.getText()).trim();
if (entered.isEmpty()) { if (entered.isEmpty()) {
// استایل خطا مثل قبل
if (!passwordField.getStyleClass().contains("error")) if (!passwordField.getStyleClass().contains("error"))
passwordField.getStyleClass().add("error"); passwordField.getStyleClass().add("error");
if (!visiblePasswordField.getStyleClass().contains("error")) if (!visiblePasswordField.getStyleClass().contains("error"))
@@ -110,7 +109,6 @@ public class CheckPasswordController {
return; return;
} }
// تماس با سرور
org.json.JSONObject req = new org.json.JSONObject() org.json.JSONObject req = new org.json.JSONObject()
.put("action", "verify_password") .put("action", "verify_password")
.put("current_password", entered); .put("current_password", entered);
@@ -119,12 +117,11 @@ public class CheckPasswordController {
boolean ok = (resp != null && "success".equalsIgnoreCase(resp.optString("status"))); boolean ok = (resp != null && "success".equalsIgnoreCase(resp.optString("status")));
if (ok) { if (ok) {
// پاک کردن خطاها
passwordField.getStyleClass().remove("error"); passwordField.getStyleClass().remove("error");
visiblePasswordField.getStyleClass().remove("error"); visiblePasswordField.getStyleClass().remove("error");
passwordLabel.getStyleClass().remove("error"); passwordLabel.getStyleClass().remove("error");
openChangeCredentials(entered); // ← پسورد فعلی را پاس بده openChangeCredentials(entered);
} else { } else {
if (!passwordField.getStyleClass().contains("error")) if (!passwordField.getStyleClass().contains("error"))
passwordField.getStyleClass().add("error"); passwordField.getStyleClass().add("error");
@@ -165,8 +162,8 @@ public class CheckPasswordController {
Node overlay = loader.load(); Node overlay = loader.load();
ChangeCredentialsController c = loader.getController(); ChangeCredentialsController c = loader.getController();
c.setCurrentPassword(currentPassword); // ← مهم c.setCurrentPassword(currentPassword);
c.prefillFromSession(); // ← پیش‌پر کردن c.prefillFromSession();
MainController.getInstance().showOverlay(overlay); MainController.getInstance().showOverlay(overlay);
} catch (IOException ex) { } catch (IOException ex) {
@@ -30,15 +30,12 @@ public class ContactsController {
@FXML private ScrollPane contactsScroll; @FXML private ScrollPane contactsScroll;
@FXML private Button searchIcon; @FXML private Button searchIcon;
/** منبع داده UI — با کانتکت‌های واقعی پر می‌شود */
private final List<ContactVM> allContacts = new ArrayList<>(); private final List<ContactVM> allContacts = new ArrayList<>();
@FXML @FXML
public void initialize() { public void initialize() {
// 1) لود اولیه‌ی کانتکت‌ها از سشن/سرور
loadContactsAndRender(); loadContactsAndRender();
// 2) سرچ محلی روی لیست
searchField.textProperty().addListener((obs, ov, nv) -> { searchField.textProperty().addListener((obs, ov, nv) -> {
String f = nv == null ? "" : nv.trim().toLowerCase(); String f = nv == null ? "" : nv.trim().toLowerCase();
List<ContactVM> filtered = allContacts.stream() List<ContactVM> filtered = allContacts.stream()
@@ -49,14 +46,11 @@ public class ContactsController {
renderContacts(filtered); renderContacts(filtered);
}); });
// 3) فوکوس خودکار روی سرچ
Platform.runLater(() -> searchField.requestFocus()); Platform.runLater(() -> searchField.requestFocus());
// 4) بستن اورلی
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(contactsCard.getParent())); closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(contactsCard.getParent()));
overlayBackground.setOnMouseClicked(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.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
contactsScroll.setPannable(true); contactsScroll.setPannable(true);
contactsScroll.setFitToWidth(true); contactsScroll.setFitToWidth(true);
@@ -66,23 +60,18 @@ public class ContactsController {
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY); contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
}); });
// 6) ثبت صحنه برای ThemeManager
Platform.runLater(() -> { Platform.runLater(() -> {
if (contactsCard.getScene() != null) { if (contactsCard.getScene() != null) {
ThemeManager.getInstance().registerScene(contactsCard.getScene()); ThemeManager.getInstance().registerScene(contactsCard.getScene());
} }
}); });
// 7) واکنش به تغییر تم
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal)); ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal));
updateSearchIcon(ThemeManager.getInstance().isDarkMode()); updateSearchIcon(ThemeManager.getInstance().isDarkMode());
} }
// -----------------------
// لود داده و رندر لیست
// -----------------------
private void loadContactsAndRender() { private void loadContactsAndRender() {
// اگر Session.contactEntries از قبل لود شده، از همان استفاده می‌کنیم.
CompletableFuture CompletableFuture
.supplyAsync(() -> { .supplyAsync(() -> {
try { try {
@@ -181,23 +170,19 @@ public class ContactsController {
private Image loadAvatarSafe(String urlOrResource) { private Image loadAvatarSafe(String urlOrResource) {
try { try {
if (urlOrResource != null) { if (urlOrResource != null) {
// حالت Resource داخلی
if (urlOrResource.startsWith("/")) { if (urlOrResource.startsWith("/")) {
return new Image(Objects.requireNonNull( return new Image(Objects.requireNonNull(
getClass().getResourceAsStream(urlOrResource))); getClass().getResourceAsStream(urlOrResource)));
} }
// حالت URL وب یا مسیر فایل
if (urlOrResource.startsWith("http://") || if (urlOrResource.startsWith("http://") ||
urlOrResource.startsWith("https://") || urlOrResource.startsWith("https://") ||
urlOrResource.startsWith("file:")) { urlOrResource.startsWith("file:")) {
return new Image(urlOrResource, true); // true = لود async return new Image(urlOrResource, true); // true = لود async
} }
} }
// fallback به پیش‌فرض
return new Image(Objects.requireNonNull( return new Image(Objects.requireNonNull(
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png"))); getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
} catch (Exception e) { } catch (Exception e) {
// هر مشکلی → پیش‌فرض
return new Image(Objects.requireNonNull( return new Image(Objects.requireNonNull(
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png"))); getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
} }
@@ -291,12 +276,10 @@ public class ContactsController {
} }
// -----------------------
// ViewModel ساده‌ی کانتکت
// -----------------------
private static class ContactVM { private static class ContactVM {
final UUID contactId; // internal UUID (واقعی) final UUID contactId; // internal UUID
final String userId; // @id نمایش (اختیاری) final String userId; // @id
final String profileName; final String profileName;
final String imageUrl; final String imageUrl;
@@ -137,8 +137,8 @@ public class EditProfileController {
File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow()); File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow());
if (file != null) { if (file != null) {
pickedAvatarFile = file; // ← ذخیره کن برای ارسال هنگام خروج pickedAvatarFile = file;
profileImageView.setImage(new Image(file.toURI().toString())); // پیش‌نمایش profileImageView.setImage(new Image(file.toURI().toString()));
} }
} }
@@ -192,16 +192,13 @@ public class EditProfileController {
if (ActionHandler.instance.wasSuccess()) { if (ActionHandler.instance.wasSuccess()) {
String url = ActionHandler.instance.getLastMessage(); String url = ActionHandler.instance.getLastMessage();
if (url != null && !url.isBlank()) { if (url != null && !url.isBlank()) {
// قبلاً اینجا مستقیم bust می‌زدیم → مشکل ایجاد می‌کرد
// currentUser.put("image_url", url);
// ✔️ نسخه‌ی امن:
String extracted = extractImageUrl(url); // از helper پایین String extracted = extractImageUrl(url);
if (isLikelyImageUrl(extracted)) { if (isLikelyImageUrl(extracted)) {
String finalUrl = addCacheBusterIfHttp(extracted); // فقط برای http/https String finalUrl = addCacheBusterIfHttp(extracted);
Session.currentUser.put("image_url", finalUrl); Session.currentUser.put("image_url", finalUrl);
} else { } else {
// اگر سرور چیز عجیبی مثل "Welcome Farid" برگردوند، نادیده بگیر
System.out.println("Upload avatar returned a non-image value: " + url); System.out.println("Upload avatar returned a non-image value: " + url);
} }
} }
@@ -267,15 +264,12 @@ public class EditProfileController {
); );
} }
// 2) خودِ کارت Settings را فوراً از Session ریفرش کن
if (parentSettings != null) { if (parentSettings != null) {
parentSettings.populateFromSession(); parentSettings.populateFromSession();
} }
// 3) سایدبار بالایی (اسم/آواتار خود کاربر) اگر داری
MainController.getInstance().refreshSidebarUserFromSession(); MainController.getInstance().refreshSidebarUserFromSession();
// 4) بستن اورلی ادیت
// MainController.getInstance().goBack(overlayBackground); // MainController.getInstance().goBack(overlayBackground);
MainController.getInstance().closeOverlay(editCard.getParent()); MainController.getInstance().closeOverlay(editCard.getParent());
@@ -302,7 +296,6 @@ public class EditProfileController {
private boolean isLikelyImageUrl(String s) { private boolean isLikelyImageUrl(String s) {
if (s == null || s.isBlank()) return false; if (s == null || s.isBlank()) return false;
// URL کامل، مسیر نسبی سرور، یا file: و پسوند تصویر
return s.startsWith("http://") || s.startsWith("https://") return s.startsWith("http://") || s.startsWith("https://")
|| s.startsWith("file:") || s.startsWith("file:")
|| s.startsWith("/") || s.startsWith("/")
@@ -314,19 +307,15 @@ public class EditProfileController {
if (url.startsWith("http://") || url.startsWith("https://")) { if (url.startsWith("http://") || url.startsWith("https://")) {
return url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis(); return url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
} }
// برای مسیر لوکال/نسبی اصلاً ?v اضافه نکن (روی ویندوز مشکل می‌دهد)
return url; return url;
} }
/** اگر سرور به‌جای URL، JSON یا متن دیگری داد، اینجا URL را دربیار. */
private String extractImageUrl(String raw) { private String extractImageUrl(String raw) {
if (raw == null) return null; if (raw == null) return null;
String s = raw.trim(); String s = raw.trim();
// اگر برگشتی JSON است (بعضی سرویس‌ها data.display_url برمی‌گردانند)
if (s.startsWith("{") && s.endsWith("}")) { if (s.startsWith("{") && s.endsWith("}")) {
try { try {
org.json.JSONObject j = new org.json.JSONObject(s); org.json.JSONObject j = new org.json.JSONObject(s);
// هر کلیدی که سرویس‌ات استفاده می‌کند را امتحان کن
if (j.has("display_url")) return j.optString("display_url", null); if (j.has("display_url")) return j.optString("display_url", null);
if (j.has("url")) return j.optString("url", null); if (j.has("url")) return j.optString("url", null);
if (j.has("data")) { if (j.has("data")) {
@@ -338,7 +327,7 @@ public class EditProfileController {
} }
} catch (Exception ignore) {} } catch (Exception ignore) {}
} }
return s; // در غیر این صورت همان raw return s;
} }
} }
@@ -142,7 +142,7 @@ public class IntroController {
@FXML @FXML
private void handleStartMessaging(ActionEvent event) { private void handleStartMessaging(ActionEvent event) {
AppRouter.showLogin(); // همون Scene می‌مونه، فقط Root عوض میشه AppRouter.showLogin();
} }
@FXML private void goRegister() { AppRouter.showRegister(); } @FXML private void goRegister() { AppRouter.showRegister(); }
@@ -151,8 +151,8 @@ public class LoginController {
return; return;
} }
// موفق: Session از داخل send پر شده
goMain(); // بدون Alert → مستقیم به main.fxml goMain();
}); });
} catch (Exception ex) { } catch (Exception ex) {
@@ -200,7 +200,7 @@ public class LoginController {
PauseTransition pause = new PauseTransition(Duration.millis(50)); PauseTransition pause = new PauseTransition(Duration.millis(50));
pause.setOnFinished(event -> { pause.setOnFinished(event -> {
errorLabel.setText(message); errorLabel.setText(message);
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red errorLabel.setStyle("-fx-text-fill: red;");
errorLabel.setVisible(true); errorLabel.setVisible(true);
}); });
pause.play(); pause.play();
@@ -1060,7 +1060,6 @@ public class MainController {
private void openSearchResult(SearchResult r) { private void openSearchResult(SearchResult r) {
switch (r.type) { switch (r.type) {
case USER: { case USER: {
// 1) اگه قبلاً چت پرایوت با این یوزر داری، از همون استفاده کن
UUID existingChatId = findExistingPrivateChatId(r.uuid); UUID existingChatId = findExistingPrivateChatId(r.uuid);
ChatEntry ce = new ChatEntry(); ChatEntry ce = new ChatEntry();
@@ -1074,8 +1073,7 @@ public class MainController {
ce.setId(existingChatId.toString()); ce.setId(existingChatId.toString());
mode = ChatViewMode.NORMAL; mode = ChatViewMode.NORMAL;
} else { } else {
// هنوز چتی وجود ندارد → Preview (بدون ساخت چت)
// برای Preview از uuid خودِ طرف مقابل به‌عنوان id موقت استفاده می‌کنیم
ce.setId(r.uuid.toString()); ce.setId(r.uuid.toString());
mode = isContact(r.uuid) ? ChatViewMode.NORMAL : ChatViewMode.NEEDS_ADD_CONTACT; mode = isContact(r.uuid) ? ChatViewMode.NORMAL : ChatViewMode.NEEDS_ADD_CONTACT;
} }
@@ -1188,19 +1186,16 @@ public class MainController {
private boolean isSavedMessages(ChatEntry c) { private boolean isSavedMessages(ChatEntry c) {
if (c == null) return false; if (c == null) return false;
// اگر تایپ اختصاصی داری
if ("saved".equalsIgnoreCase(c.getType())) return true; if ("saved".equalsIgnoreCase(c.getType())) return true;
// اگر با نام مشخص ذخیره می‌کنی
String n = c.getName(); String n = c.getName();
if (n != null && n.equalsIgnoreCase("Saved Messages")) return true; if (n != null && n.equalsIgnoreCase("Saved Messages")) return true;
// حالت پرایوت با خودِ کاربر
String me = (org.to.telegramfinalproject.Client.Session.currentUser != null) String me = (org.to.telegramfinalproject.Client.Session.currentUser != null)
? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "") ? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "")
: ""; : "";
try { try {
UUID other = c.getOtherUserId(); // اگر این فیلد را داری UUID other = c.getOtherUserId();
if ("private".equalsIgnoreCase(c.getType()) && if ("private".equalsIgnoreCase(c.getType()) &&
other != null && other.toString().equalsIgnoreCase(me)) { other != null && other.toString().equalsIgnoreCase(me)) {
return true; return true;
@@ -1294,12 +1289,10 @@ public class MainController {
org.to.telegramfinalproject.Client.Session.chatList.add(0, entry); org.to.telegramfinalproject.Client.Session.chatList.add(0, entry);
org.to.telegramfinalproject.Client.Session.activeChats.add(0, entry); org.to.telegramfinalproject.Client.Session.activeChats.add(0, entry);
} }
// سایدبارت اگر متدی برای رفرش دارد صداش بزن (اسمش را با کلاس خودت هماهنگ کن)
try { try {
this.refreshChatListUI(); // اگر نداری، این خط را حذف کن this.refreshChatListUI();
} catch (Exception ignore) {} } catch (Exception ignore) {}
// نمایش پیج چت
if (getChatPageController() != null) { if (getChatPageController() != null) {
getChatPageController().showChat(entry); getChatPageController().showChat(entry);
} }
@@ -1320,13 +1313,11 @@ public class MainController {
public void ensureArchivedHeaderRow() { public void ensureArchivedHeaderRow() {
boolean hasArchived = Session.archivedChats != null && !Session.archivedChats.isEmpty(); boolean hasArchived = Session.archivedChats != null && !Session.archivedChats.isEmpty();
// اگر آرشیو نداریم، هدر قبلی را حذف کن و برگرد
if (!hasArchived) { if (!hasArchived) {
chatListContainer.getChildren().removeIf(n -> ARCHIVED_ROW_KEY.equals(n.getUserData())); chatListContainer.getChildren().removeIf(n -> ARCHIVED_ROW_KEY.equals(n.getUserData()));
return; return;
} }
// اگر قبلاً هست، دوباره نساز
boolean exists = chatListContainer.getChildren().stream() boolean exists = chatListContainer.getChildren().stream()
.anyMatch(n -> ARCHIVED_ROW_KEY.equals(n.getUserData())); .anyMatch(n -> ARCHIVED_ROW_KEY.equals(n.getUserData()));
if (exists) return; if (exists) return;
@@ -1343,7 +1334,6 @@ public class MainController {
headerRow.getChildren().addAll(title, spacer, openBtn); headerRow.getChildren().addAll(title, spacer, openBtn);
headerRow.setMinHeight(36); headerRow.setPrefHeight(36); headerRow.setMinHeight(36); headerRow.setPrefHeight(36);
// در ابتدای همان لیست اصلیِ چت‌ها
chatListContainer.getChildren().add(0, headerRow); chatListContainer.getChildren().add(0, headerRow);
} }
@@ -1355,7 +1345,6 @@ public class MainController {
public void refreshArchivedListUI() { public void refreshArchivedListUI() {
chatListContainer.getChildren().clear(); chatListContainer.getChildren().clear();
// Back row (بماند)
HBox backRow = new HBox(); HBox backRow = new HBox();
Label back = new Label("← Back to Active"); Label back = new Label("← Back to Active");
back.getStyleClass().add("archived-back"); back.getStyleClass().add("archived-back");
@@ -1367,10 +1356,9 @@ public class MainController {
backRow.setStyle("-fx-padding: 6 10 8 10;"); backRow.setStyle("-fx-padding: 6 10 8 10;");
chatListContainer.getChildren().add(backRow); chatListContainer.getChildren().add(backRow);
// ✅ از همین سل‌ساز FXML استفاده کن تا دقیقا هم‌شکل اکتیو باشد
if (Session.archivedChats != null && !Session.archivedChats.isEmpty()) { if (Session.archivedChats != null && !Session.archivedChats.isEmpty()) {
for (ChatEntry e : Session.archivedChats) { for (ChatEntry e : Session.archivedChats) {
addChatNode(e); // ⬅️ همونی که در اکتیو استفاده می‌کنی addChatNode(e);
} }
} else { } else {
Label empty = new Label("No archived chats"); Label empty = new Label("No archived chats");
@@ -1381,31 +1369,27 @@ public class MainController {
// ساخت یک سل آیتم (برای اکتیو/آرشیو)
private HBox buildChatCell(ChatEntry e, boolean archivedView) { private HBox buildChatCell(ChatEntry e, boolean archivedView) {
HBox row = new HBox(8); HBox row = new HBox(8);
ImageView avatar = new ImageView(); avatar.setFitWidth(36); avatar.setFitHeight(36); ImageView avatar = new ImageView(); avatar.setFitWidth(36); avatar.setFitHeight(36);
// ... لود تصویر از e.getImageUrl() اگر داری
Label name = new Label(e.getName()); Label name = new Label(e.getName());
Region spacer = new Region(); HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); 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.getChildren().addAll(avatar, name, spacer, last);
row.getStyleClass().add("chat-row"); row.getStyleClass().add("chat-row");
// کلیک روی آیتم → openChat
row.setOnMouseClicked(ev -> openChat(e)); row.setOnMouseClicked(ev -> openChat(e));
return row; return row;
} }
private String safeLastLine(ChatEntry e) { private String safeLastLine(ChatEntry e) {
String s = e.getLastMessagePreview(); // اگر داری String s = e.getLastMessagePreview();
return s != null ? s : ""; return s != null ? s : "";
} }
// title fallback: اول name بعد displayId، در نهایت پیش‌فرض
private String safeTitle(ChatEntry e) { private String safeTitle(ChatEntry e) {
if (e.getName() != null && !e.getName().isBlank()) return e.getName(); if (e.getName() != null && !e.getName().isBlank()) return e.getName();
if (e.getDisplayId() != null && !e.getDisplayId().isBlank()) return e.getDisplayId(); if (e.getDisplayId() != null && !e.getDisplayId().isBlank()) return e.getDisplayId();
@@ -1413,7 +1397,6 @@ public class MainController {
return "Unknown"; return "Unknown";
} }
// image fallback: اگر خالی بود، آواتار پیش‌فرض
private String safeImage(String img) { private String safeImage(String img) {
return (img != null && !img.isBlank()) return (img != null && !img.isBlank())
? img ? img
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB