diff --git a/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java b/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java index fd7b036..849eafa 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/CheckPasswordController.java @@ -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) { diff --git a/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java b/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java index 316db81..2c6101a 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ContactsController.java @@ -30,15 +30,12 @@ public class ContactsController { @FXML private ScrollPane contactsScroll; @FXML private Button searchIcon; - /** منبع داده UI — با کانتکت‌های واقعی پر می‌شود */ private final List allContacts = new ArrayList<>(); @FXML public void initialize() { - // 1) لود اولیه‌ی کانتکت‌ها از سشن/سرور loadContactsAndRender(); - // 2) سرچ محلی روی لیست searchField.textProperty().addListener((obs, ov, nv) -> { String f = nv == null ? "" : nv.trim().toLowerCase(); List 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 { @@ -181,23 +170,19 @@ public class ContactsController { private Image loadAvatarSafe(String urlOrResource) { try { if (urlOrResource != null) { - // حالت Resource داخلی if (urlOrResource.startsWith("/")) { return new Image(Objects.requireNonNull( getClass().getResourceAsStream(urlOrResource))); } - // حالت URL وب یا مسیر فایل if (urlOrResource.startsWith("http://") || urlOrResource.startsWith("https://") || urlOrResource.startsWith("file:")) { return new Image(urlOrResource, true); // true = لود async } } - // fallback به پیش‌فرض return new Image(Objects.requireNonNull( getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png"))); } catch (Exception e) { - // هر مشکلی → پیش‌فرض return new Image(Objects.requireNonNull( getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png"))); } @@ -291,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; diff --git a/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java b/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java index 4b41213..043054a 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/EditProfileController.java @@ -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; } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/IntroController.java b/src/main/java/org/to/telegramfinalproject/UI/IntroController.java index 3d25621..7aee43e 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/IntroController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/IntroController.java @@ -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(); } diff --git a/src/main/java/org/to/telegramfinalproject/UI/LoginController.java b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java index 2e16767..446e245 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/LoginController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java @@ -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(); diff --git a/src/main/java/org/to/telegramfinalproject/UI/MainController.java b/src/main/java/org/to/telegramfinalproject/UI/MainController.java index 932d8a2..6d2c8f9 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/MainController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/MainController.java @@ -1060,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(); @@ -1074,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; } @@ -1188,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; @@ -1294,12 +1289,10 @@ public class MainController { 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); } @@ -1320,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; @@ -1343,7 +1334,6 @@ public class MainController { headerRow.getChildren().addAll(title, spacer, openBtn); headerRow.setMinHeight(36); headerRow.setPrefHeight(36); - // در ابتدای همان لیست اصلیِ چت‌ها chatListContainer.getChildren().add(0, headerRow); } @@ -1355,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"); @@ -1367,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"); @@ -1381,31 +1369,27 @@ 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 : ""; } - // title fallback: اول name بعد displayId، در نهایت پیش‌فرض 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(); @@ -1413,7 +1397,6 @@ public class MainController { return "Unknown"; } - // image fallback: اگر خالی بود، آواتار پیش‌فرض private String safeImage(String img) { return (img != null && !img.isBlank()) ? img diff --git a/uploads/audios/2025-09-08/f2e4fa82-e4d6-42f4-bc30-2beb48f38697.mp3 b/uploads/audios/2025-09-08/f2e4fa82-e4d6-42f4-bc30-2beb48f38697.mp3 new file mode 100644 index 0000000..70cae69 Binary files /dev/null and b/uploads/audios/2025-09-08/f2e4fa82-e4d6-42f4-bc30-2beb48f38697.mp3 differ diff --git a/uploads/avatars/2025-09-08/049e91ab-8337-49d1-8b54-9418741f5e07.jpg b/uploads/avatars/2025-09-08/049e91ab-8337-49d1-8b54-9418741f5e07.jpg new file mode 100644 index 0000000..3c74d30 Binary files /dev/null and b/uploads/avatars/2025-09-08/049e91ab-8337-49d1-8b54-9418741f5e07.jpg differ diff --git a/uploads/avatars/2025-09-08/12b6441a-e7a7-402d-ac52-bf81460f135a.jpg b/uploads/avatars/2025-09-08/12b6441a-e7a7-402d-ac52-bf81460f135a.jpg new file mode 100644 index 0000000..d28a894 Binary files /dev/null and b/uploads/avatars/2025-09-08/12b6441a-e7a7-402d-ac52-bf81460f135a.jpg differ diff --git a/uploads/avatars/2025-09-08/9369c195-1426-4a95-a64c-3687de82da72.jpg b/uploads/avatars/2025-09-08/9369c195-1426-4a95-a64c-3687de82da72.jpg new file mode 100644 index 0000000..5a37195 Binary files /dev/null and b/uploads/avatars/2025-09-08/9369c195-1426-4a95-a64c-3687de82da72.jpg differ diff --git a/uploads/images/2025-09-08/0f42c3e1-56d7-4a80-8d03-ce4ee63cafa1.jpg b/uploads/images/2025-09-08/0f42c3e1-56d7-4a80-8d03-ce4ee63cafa1.jpg new file mode 100644 index 0000000..ee48cbb Binary files /dev/null and b/uploads/images/2025-09-08/0f42c3e1-56d7-4a80-8d03-ce4ee63cafa1.jpg differ diff --git a/uploads/images/2025-09-08/ecbc6432-a82f-4559-b2d0-1c6c7ae5b801.jpg b/uploads/images/2025-09-08/ecbc6432-a82f-4559-b2d0-1c6c7ae5b801.jpg new file mode 100644 index 0000000..6436ef0 Binary files /dev/null and b/uploads/images/2025-09-08/ecbc6432-a82f-4559-b2d0-1c6c7ae5b801.jpg differ