Show profile on UI

This commit is contained in:
2025-08-31 19:29:14 +03:30
parent 626a1335e6
commit 9d16258d0c
5 changed files with 149 additions and 22 deletions
@@ -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;
}
}
@@ -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);
}
}
@@ -6,6 +6,7 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle; import javafx.scene.shape.Circle;
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
import java.util.Objects; import java.util.Objects;
@@ -68,7 +69,27 @@ public class ChatItemController {
systemAvatar.setManaged(true); systemAvatar.setManaged(true);
} else if (imageUrl != null && !imageUrl.isEmpty()) { } 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.setVisible(true);
profileImageUser.setManaged(true); profileImageUser.setManaged(true);
@@ -13,6 +13,7 @@ import javafx.stage.FileChooser;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Client.ActionHandler; import org.to.telegramfinalproject.Client.ActionHandler;
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
import org.to.telegramfinalproject.Client.Session; import org.to.telegramfinalproject.Client.Session;
import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ChatEntry;
import org.json.JSONArray; import org.json.JSONArray;
@@ -538,25 +539,22 @@ public class ChatPageController {
chatTitle.setText(entry.getName()); chatTitle.setText(entry.getName());
// آواتار پیش‌فرض بر اساس نوع // آواتار پیش‌فرض بر اساس نوع
// ChatPageController.showChat(...)
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
userAvatar.setImage(new Image(entry.getImageUrl())); Image img = AvatarLocalResolver.load(entry.getImageUrl());
} else if ("channel".equals(entry.getType())) { if (img != null) {
userAvatar.setImage(new Image( userAvatar.setImage(img);
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"))
));
} else { } else {
userAvatar.setImage(new Image( // ⬇️ فال‌بک بر اساس نوع
Objects.requireNonNull(getClass().getResourceAsStream( setDefaultHeaderAvatarByType(entry.getType());
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))
));
} }
userAvatar.setClip(new Circle(20, 20, 20)); } else {
setDefaultHeaderAvatarByType(entry.getType());
}
// userAvatar.setClip(new Circle(20, 20, 20));
AvatarFX.circleClip(userAvatar, 36);
fetchAndRenderHeader(entry); fetchAndRenderHeader(entry);
@@ -1016,10 +1014,12 @@ public class ChatPageController {
String img = data.optString("image_url", ""); String img = data.optString("image_url", "");
if (hasVal(img)) { if (hasVal(img)) {
try { 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)); userAvatar.setClip(new Circle(20, 20, 20));
} catch (Exception ignore) {} } catch (Exception ignore) {}
} }
chatStatus.setText(userStatusText( chatStatus.setText(userStatusText(
data.optBoolean("online", false), data.optBoolean("online", false),
data.optString("last_seen", null) data.optString("last_seen", null)
@@ -1033,11 +1033,13 @@ public class ChatPageController {
String img = data.optString("image_url", ""); String img = data.optString("image_url", "");
if (hasVal(img)) { if (hasVal(img)) {
try { 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)); userAvatar.setClip(new Circle(20, 20, 20));
} catch (Exception ignore) {} } catch (Exception ignore) {}
} }
int members = data.optInt("member_count", 0); int members = data.optInt("member_count", 0);
int online = data.optInt("online_count", -1); int online = data.optInt("online_count", -1);
chatStatus.setText(online >= 0 ? (members + " members, " + online + " online") chatStatus.setText(online >= 0 ? (members + " members, " + online + " online")
@@ -1050,11 +1052,13 @@ public class ChatPageController {
String img = data.optString("image_url", ""); String img = data.optString("image_url", "");
if (hasVal(img)) { if (hasVal(img)) {
try { 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)); userAvatar.setClip(new Circle(20, 20, 20));
} catch (Exception ignore) {} } catch (Exception ignore) {}
} }
int subs = data.optInt("member_count", 0); int subs = data.optInt("member_count", 0);
chatStatus.setText(subs + " subscribers"); chatStatus.setText(subs + " subscribers");
} }
@@ -1097,4 +1101,15 @@ public class ChatPageController {
} }
return "recently"; 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))
));
}
} }
@@ -12,6 +12,7 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Duration; import javafx.util.Duration;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
import java.net.URL; import java.net.URL;
@@ -49,6 +50,8 @@ public class SidebarMenuController {
// Load default profile image // Load default profile image
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png"); Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
if (profile != null) profileImage.setImage(profile); if (profile != null) profileImage.setImage(profile);
AvatarFX.circleClip(profileImage, 56);
setupButtonActions(); setupButtonActions();
setupToggleAction(); setupToggleAction();
@@ -202,17 +205,37 @@ public class SidebarMenuController {
} }
// کمک‌کننده: هم URL و هم ریسورس کلاس‌پث را امتحان می‌کند // کمک‌کننده: هم 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) { private Image tryLoadImage(String src) {
if (src == null || src.isBlank()) return null; if (src == null || src.isBlank()) return null;
try { try {
// اگر ریسورس داخل پروژه است (با / شروع شود یا در resources موجود باشد) // اگر مسیر داخل resources است
var res = getClass().getResource(src); var res = getClass().getResource(src);
if (res != null) return new Image(res.toExternalForm(), true); 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); return new Image(src, true);
} catch (Exception ignored) { } catch (Exception ignored) {
return null; return null;
} }
} }
} }