From 9d16258d0cb0552d33e9a1cd91e19fce09a365d4 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Sun, 31 Aug 2025 19:29:14 +0330 Subject: [PATCH] Show profile on UI --- .../Client/AvatarLocalResolver.java | 47 ++++++++++++++++ .../to/telegramfinalproject/UI/AvatarFX.java | 21 ++++++++ .../UI/ChatItemController.java | 23 +++++++- .../UI/ChatPageController.java | 53 ++++++++++++------- .../UI/SidebarMenuController.java | 27 +++++++++- 5 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Client/AvatarLocalResolver.java create mode 100644 src/main/java/org/to/telegramfinalproject/UI/AvatarFX.java diff --git a/src/main/java/org/to/telegramfinalproject/Client/AvatarLocalResolver.java b/src/main/java/org/to/telegramfinalproject/Client/AvatarLocalResolver.java new file mode 100644 index 0000000..2458d99 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/AvatarLocalResolver.java @@ -0,0 +1,47 @@ +package org.to.telegramfinalproject.Client; + +import javafx.scene.image.Image; +import java.nio.file.*; + +public final class AvatarLocalResolver { + private static final Path UPLOADS_ROOT = Paths.get( + System.getProperty("app.uploads.root", "uploads") + ).toAbsolutePath().normalize(); + + private static boolean isHttp(String s) { + return s.startsWith("http://") || s.startsWith("https://"); + } + + public static String resolve(String serverValue) { + if (serverValue == null || serverValue.isBlank()) return null; + if (isHttp(serverValue) || serverValue.startsWith("file:")) return serverValue; + + String rel = serverValue.startsWith("/") ? serverValue.substring(1) : serverValue; // "avatars/..." + Path p = UPLOADS_ROOT.resolve(rel).normalize(); + if (!p.startsWith(UPLOADS_ROOT)) return null; // امنیت مسیر + if (!Files.exists(p)) { // مهم: واقعاً وجود دارد؟ + System.err.println("Avatar resolve: NOT FOUND -> " + p); + return null; + } + String url = p.toUri().toString(); // file:///.../uploads/avatars/.... + System.out.println("Avatar resolve: " + serverValue + " -> " + url); + return url; + } + + public static Image load(String serverValue) { + String url = resolve(serverValue); + if (url == null) return null; + + // فقط برای http/https کش‌بریکر + if (isHttp(url)) { + url += (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis(); + } + + Image img = new Image(url, false); // sync load تا خطا را همان‌جا بفهمیم + if (img.isError()) { + System.err.println("Avatar load failed: " + url + " -> " + img.getException()); + return null; + } + return img; + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/AvatarFX.java b/src/main/java/org/to/telegramfinalproject/UI/AvatarFX.java new file mode 100644 index 0000000..c26d0f4 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/AvatarFX.java @@ -0,0 +1,21 @@ +package org.to.telegramfinalproject.UI; + +import javafx.beans.binding.Bindings; +import javafx.scene.image.ImageView; +import javafx.scene.shape.Circle; + +public final class AvatarFX { + + public static void circleClip(ImageView iv, double sizePx) { + iv.setFitWidth(sizePx); + iv.setFitHeight(sizePx); + iv.setPreserveRatio(true); + iv.setSmooth(true); + + Circle c = new Circle(); + c.radiusProperty().bind(Bindings.min(iv.fitWidthProperty(), iv.fitHeightProperty()).divide(2)); + c.centerXProperty().bind(iv.fitWidthProperty().divide(2)); + c.centerYProperty().bind(iv.fitHeightProperty().divide(2)); + iv.setClip(c); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatItemController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatItemController.java index 9031af8..4b61f3a 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatItemController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatItemController.java @@ -6,6 +6,7 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import javafx.scene.shape.Circle; +import org.to.telegramfinalproject.Client.AvatarLocalResolver; import java.util.Objects; @@ -68,7 +69,27 @@ public class ChatItemController { systemAvatar.setManaged(true); } else if (imageUrl != null && !imageUrl.isEmpty()) { - profileImageUser.setImage(new Image(imageUrl, true)); +// profileImageUser.setImage(new Image(imageUrl, true)); +// profileImageUser.setVisible(true); +// profileImageUser.setManaged(true); + + Image img = AvatarLocalResolver.load(imageUrl); + if (img != null) { + profileImageUser.setImage(img); + AvatarFX.circleClip(profileImageUser, 40); + } else { + String path; + if ("group".equalsIgnoreCase(chatType)) { + path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png"; + } else if ("channel".equalsIgnoreCase(chatType)) { + path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png"; + } else { + path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png"; + } + profileImageUser.setImage(new Image( + java.util.Objects.requireNonNull(getClass().getResourceAsStream(path)) + )); + } profileImageUser.setVisible(true); profileImageUser.setManaged(true); diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index 750c776..45a0000 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -13,6 +13,7 @@ import javafx.stage.FileChooser; import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Client.ActionHandler; +import org.to.telegramfinalproject.Client.AvatarLocalResolver; import org.to.telegramfinalproject.Client.Session; import org.to.telegramfinalproject.Models.ChatEntry; import org.json.JSONArray; @@ -538,25 +539,22 @@ public class ChatPageController { chatTitle.setText(entry.getName()); // آواتار پیش‌فرض بر اساس نوع + // ChatPageController.showChat(...) if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { - userAvatar.setImage(new Image(entry.getImageUrl())); - } else if ("channel".equals(entry.getType())) { - userAvatar.setImage(new Image( - Objects.requireNonNull(getClass().getResourceAsStream( - "/org/to/telegramfinalproject/Avatars/default_channel_profile.png")) - )); - } else if ("group".equals(entry.getType())) { - userAvatar.setImage(new Image( - Objects.requireNonNull(getClass().getResourceAsStream( - "/org/to/telegramfinalproject/Avatars/default_group_profile.png")) - )); + Image img = AvatarLocalResolver.load(entry.getImageUrl()); + if (img != null) { + userAvatar.setImage(img); + } else { + // ⬇️ فال‌بک بر اساس نوع + setDefaultHeaderAvatarByType(entry.getType()); + } } else { - userAvatar.setImage(new Image( - Objects.requireNonNull(getClass().getResourceAsStream( - "/org/to/telegramfinalproject/Avatars/default_user_profile.png")) - )); + setDefaultHeaderAvatarByType(entry.getType()); } - userAvatar.setClip(new Circle(20, 20, 20)); +// userAvatar.setClip(new Circle(20, 20, 20)); + AvatarFX.circleClip(userAvatar, 36); + + fetchAndRenderHeader(entry); @@ -1016,10 +1014,12 @@ public class ChatPageController { String img = data.optString("image_url", ""); if (hasVal(img)) { try { - userAvatar.setImage(new Image(img, true)); + Image im = AvatarLocalResolver.load(img); // ⬅️ + if (im != null) userAvatar.setImage(im); userAvatar.setClip(new Circle(20, 20, 20)); } catch (Exception ignore) {} } + chatStatus.setText(userStatusText( data.optBoolean("online", false), data.optString("last_seen", null) @@ -1033,11 +1033,13 @@ public class ChatPageController { String img = data.optString("image_url", ""); if (hasVal(img)) { try { - userAvatar.setImage(new Image(img, true)); + Image im = AvatarLocalResolver.load(img); // ⬅️ + if (im != null) userAvatar.setImage(im); userAvatar.setClip(new Circle(20, 20, 20)); } catch (Exception ignore) {} } + int members = data.optInt("member_count", 0); int online = data.optInt("online_count", -1); chatStatus.setText(online >= 0 ? (members + " members, " + online + " online") @@ -1050,11 +1052,13 @@ public class ChatPageController { String img = data.optString("image_url", ""); if (hasVal(img)) { try { - userAvatar.setImage(new Image(img, true)); + Image im = AvatarLocalResolver.load(img); // ⬅️ + if (im != null) userAvatar.setImage(im); userAvatar.setClip(new Circle(20, 20, 20)); } catch (Exception ignore) {} } + int subs = data.optInt("member_count", 0); chatStatus.setText(subs + " subscribers"); } @@ -1097,4 +1101,15 @@ public class ChatPageController { } return "recently"; } + + private void setDefaultHeaderAvatarByType(String type){ + String path = switch (type == null ? "" : type.toLowerCase()) { + case "channel" -> "/org/to/telegramfinalproject/Avatars/default_channel_profile.png"; + case "group" -> "/org/to/telegramfinalproject/Avatars/default_group_profile.png"; + default -> "/org/to/telegramfinalproject/Avatars/default_user_profile.png"; + }; + userAvatar.setImage(new Image( + java.util.Objects.requireNonNull(getClass().getResourceAsStream(path)) + )); + } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index d1810ee..b40133a 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -12,6 +12,7 @@ import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.util.Duration; import org.json.JSONObject; +import org.to.telegramfinalproject.Client.AvatarLocalResolver; import java.net.URL; @@ -49,6 +50,8 @@ public class SidebarMenuController { // Load default profile image Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png"); if (profile != null) profileImage.setImage(profile); + AvatarFX.circleClip(profileImage, 56); + setupButtonActions(); setupToggleAction(); @@ -202,17 +205,37 @@ public class SidebarMenuController { } // کمک‌کننده: هم URL و هم ریسورس کلاس‌پث را امتحان می‌کند +// private Image tryLoadImage(String src) { +// if (src == null || src.isBlank()) return null; +// try { +// // اگر ریسورس داخل پروژه است (با / شروع شود یا در resources موجود باشد) +// var res = getClass().getResource(src); +// if (res != null) return new Image(res.toExternalForm(), true); +// // در غیر این صورت فرض کن URL است (http/https/file) +// return new Image(src, true); +// } catch (Exception ignored) { +// return null; +// } +// } + + private Image tryLoadImage(String src) { if (src == null || src.isBlank()) return null; try { - // اگر ریسورس داخل پروژه است (با / شروع شود یا در resources موجود باشد) + // اگر مسیر داخل resources است var res = getClass().getResource(src); if (res != null) return new Image(res.toExternalForm(), true); - // در غیر این صورت فرض کن URL است (http/https/file) + + // اگر مسیر نسبی سرور (مثل /avatars/...) است + String fileUri = AvatarLocalResolver.resolve(src); + if (fileUri != null) return new Image(fileUri, true); + + // در غیر این صورت، فرض URL کامل return new Image(src, true); } catch (Exception ignored) { return null; } } + }