Profile logic for UI

This commit is contained in:
2025-09-04 20:45:38 +03:30
parent 89e2033ac2
commit 9c61b27004
11 changed files with 674 additions and 115 deletions
@@ -1,5 +1,6 @@
package org.to.telegramfinalproject.Client;
import javafx.application.Platform;
import org.json.JSONArray;
import org.json.JSONObject;
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
@@ -9,10 +10,12 @@ import org.to.telegramfinalproject.Models.ContactEntry;
import org.to.telegramfinalproject.Models.SearchRequestModel;
import org.to.telegramfinalproject.Models.SearchResultModel;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
@@ -4227,4 +4230,401 @@ public class ActionHandler {
}
public void sendMessageInteractive(UUID receiverId, String receiverType) {
Scanner sc = new Scanner(System.in);
System.out.print("Type (TEXT / IMAGE / AUDIO): ");
String type = sc.nextLine().trim().toUpperCase();
while (!Set.of("TEXT","IMAGE","AUDIO").contains(type)) {
System.out.print("❌ Invalid. Try (TEXT / IMAGE / AUDIO): ");
type = sc.nextLine().trim().toUpperCase();
}
System.out.print("Text (optional for media; empty = no caption): ");
String text = sc.nextLine();
if ("TEXT".equals(type)) {
sendTextMessage(receiverId, receiverType, text);
} else {
System.out.print("File path: ");
String path = sc.nextLine().trim();
File f = new File(path);
if (!f.isFile()) {
System.out.println("❌ File not found");
return;
}
try {
sendMediaMessage(receiverId, receiverType, type, f, text);
} catch (Exception e) {
e.printStackTrace();
System.out.println("❌ Media send failed: " + e.getMessage());
}
}
}
private void sendTextMessage(UUID receiverId, String receiverType, String content) {
JSONObject req = new JSONObject()
.put("action", "send_message")
.put("receiver_type", receiverType)
.put("receiver_id", receiverId.toString())
.put("message_type", "TEXT")
.put("content", content == null ? "" : content);
JSONObject resp = sendWithResponse(req);
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
System.out.println("✅ Sent. id=" + resp.optJSONObject("data").optString("message_id",""));
} else {
System.out.println("❌ Failed: " + (resp != null ? resp.optString("message") : "no response"));
}
}
public void sendMediaMessage(UUID receiverId, String receiverType, String type /* IMAGE/AUDIO */, File file, String caption) {
if (file == null) {
System.out.println("❌ File is null");
return;
}
if (!file.exists()) {
System.out.println("❌ File not found: " + file.getAbsolutePath());
return;
}
if (file.isDirectory()) {
System.out.println("❌ Path is a directory, expected a file: " + file.getAbsolutePath());
return;
}
final UUID messageId = UUID.randomUUID();
try {
String mime = detectMime(file, type.toUpperCase());
if (mime == null) mime = type.equalsIgnoreCase("IMAGE") ? "image/*" : "audio/*";
JSONObject header = new JSONObject()
.put("message_id", messageId.toString())
.put("sender_id", TelegramClient.loggedInUserId.toString())
.put("receiver_type", receiverType) // private|group|channel
.put("receiver_id", receiverId.toString())
.put("message_type", type.toUpperCase()) // IMAGE | AUDIO
.put("file_name", file.getName())
.put("mime_type", mime)
.put("text", caption == null ? "" : caption);
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
long contentLen = file.length();
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
TelegramClient.pendingResponses.put(messageId.toString(), q);
try {
outBin.write("MEDIA\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
outBin.flush();
// 2) binary frame: magic + headerLen + header + contentLen + content
outBin.writeInt(0x4D444D31); // "MDM1"
outBin.writeInt(headerBytes.length); // headerLen (int)
outBin.write(headerBytes); // header
outBin.writeLong(contentLen); // contentLen (long)
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
byte[] buf = new byte[8192];
int n;
while ((n = fis.read(buf)) != -1) {
outBin.write(buf, 0, n);
}
}
outBin.flush();
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
if (ack == null) {
System.out.println("❌ Media ACK timeout for " + messageId);
return;
}
String status = ack.optString("status", "error");
if ("success".equalsIgnoreCase(status)) {
System.out.println("✅ Media sent. id=" + ack.optString("message_id") +
" url=" + ack.optString("file_url"));
} else {
System.out.println("❌ Media failed: " + ack.optString("message"));
}
} finally {
TelegramClient.pendingResponses.remove(messageId.toString());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("❌ sendMediaMessage error: " + e.getMessage());
}
}
public static String detectMime(File f, String typeUpper /* IMAGE or AUDIO */) {
try {
String m = java.nio.file.Files.probeContentType(f.toPath());
if (m != null) return m;
} catch (Exception ignored) {}
String name = f.getName().toLowerCase();
if (name.endsWith(".png")) return "image/png";
if (name.endsWith(".jpg") || name.endsWith(".jpeg")) return "image/jpeg";
if (name.endsWith(".gif")) return "image/gif";
if (name.endsWith(".mp3")) return "audio/mpeg";
if (name.endsWith(".wav")) return "audio/wav";
if (name.endsWith(".ogg")) return "audio/ogg";
return typeUpper.equals("IMAGE") ? "image/*" : "audio/*";
}
private static String safeName(String s) {
if (s == null) return "unknown";
s = s.replace("\\", "/");
if (s.contains("/")) s = s.substring(s.lastIndexOf('/') + 1);
s = s.replaceAll("[\\\\/:*?\"<>|]", "_").trim();
if (s.isEmpty() || s.equals(".") || s.equals("..")) s = "unknown";
return s;
}
private static String accountFolderName() {
JSONObject me = Session.currentUser;
String acc = me.optString("username",
me.optString("user_id",
me.optString("profile_name",
me.optString("internal_uuid", "me"))));
return safeName(acc);
}
private static String chatFolderName(ChatEntry chat) {
String name = chat.getName();
if (name == null || name.isBlank()) {
name = chat.getDisplayId() != null && !chat.getDisplayId().isBlank()
? chat.getDisplayId()
: String.valueOf(chat.getId());
}
return safeName(name);
}
public void uploadAvatar(File file) {
if (file == null || !file.exists() || !file.isFile()) {
System.out.println("❌ Invalid file");
return;
}
final UUID requestId = UUID.randomUUID();
try {
String mime = detectMime(file, "IMAGE");
if (mime == null) mime = "image/*";
// header مثل مدیا، ولی برای آواتار
JSONObject header = new JSONObject()
.put("message_id", requestId.toString()) // برای مچ ACK
.put("target_type", "user") // یا channel/group
// .put("target_id", "…") // اگر channel/group بود
.put("file_name", file.getName())
.put("mime_type", mime);
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
long contentLen = file.length();
// صف پاسخ (مثل sendMediaMessage)
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
TelegramClient.pendingResponses.put(requestId.toString(), q);
try {
// 🔹 سوئیچ به حالت آواتار (مثل "MEDIA\n")
outBin.write("AVATAR\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
outBin.flush();
// 🔹 فریم باینری: مجیک + headerLen + header + contentLen + content
outBin.writeInt(0x41565431); // "AVT1"
outBin.writeInt(headerBytes.length);
outBin.write(headerBytes);
outBin.writeLong(contentLen);
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
byte[] buf = new byte[8192]; int n;
while ((n = fis.read(buf)) != -1) outBin.write(buf, 0, n);
}
outBin.flush();
// 🔹 انتظار ACK (مثل مدیا)
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
if (ack == null) {
System.out.println("❌ Avatar ACK timeout for " + requestId);
return;
}
if ("success".equalsIgnoreCase(ack.optString("status"))) {
String url = ack.optString("display_url", null);
System.out.println("✅ Avatar uploaded. url=" + url);
// (اختیاری) رفرش UI + شکستن کش:
// MainController.getInstance().refreshMyAvatar(url + "?v=" + System.currentTimeMillis());
} else {
System.out.println("❌ Avatar failed: " + ack.optString("message"));
}
} finally {
TelegramClient.pendingResponses.remove(requestId.toString());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("❌ uploadAvatar error: " + e.getMessage());
}
}
private static final Path UPLOADS_ROOT =
Paths.get(System.getProperty("app.uploads.root", "uploads"))
.toAbsolutePath().normalize();
private String prepareAvatar(String input) {
if (input == null || input.isBlank()) return null;
// اگر URL کامل است، دست نزن
if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("file:")) {
return input;
}
try {
Path src = Paths.get(input).toAbsolutePath().normalize();
if (!Files.exists(src)) {
System.out.println("⚠️ Image file not found: " + src);
return null;
}
String ext = getExt(src.getFileName().toString());
String day = LocalDate.now().toString();
String fileName = java.util.UUID.randomUUID() + (ext.isEmpty() ? ".jpg" : ext);
Path destDir = UPLOADS_ROOT.resolve("avatars").resolve(day);
Files.createDirectories(destDir);
Path dest = destDir.resolve(fileName);
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
// مقدار نسبی که سرور/کلاینت‌های دیگر هم می‌فهمند
return "/avatars/" + day + "/" + fileName;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void uploadAvatarFor(String targetType, UUID targetId, File file) {
if (file == null || !file.exists() || !file.isFile()) {
System.out.println("❌ Invalid file");
return;
}
final UUID reqId = UUID.randomUUID();
String lastStatus;
String lastMessage;
try {
String mime = detectMime(file, "IMAGE");
if (mime == null) mime = "image/*";
JSONObject header = new JSONObject()
.put("request_id", reqId.toString()) // ← هماهنگ با pendingResponses
.put("target_type", targetType.toLowerCase()); // user | group | channel
if (targetId != null) header.put("target_id", targetId.toString());
header.put("file_name", file.getName())
.put("mime_type", mime);
byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
long contentLen = file.length();
BlockingQueue<JSONObject> q = new LinkedBlockingQueue<>(1);
// رجیستر با هر دو کلید تا هر نوع ACK روت شود
TelegramClient.pendingResponses.put(reqId.toString(), q);
TelegramClient.pendingResponses.put(header.optString("message_id", reqId.toString()), q);
try {
TelegramClient.mediaBusy.set(true); // ← حین باینری JSON نخونیم
// سوئیچ پروتکل
TelegramClient.getInstance().getOutBin()
.write("AVATAR\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
DataOutputStream outBin = TelegramClient.getInstance().getOutBin();
outBin.flush();
// فریم: MAGIC + headerLen + header + contentLen + content
outBin.writeInt(0x41565431); // "AVT1"
outBin.writeInt(headerBytes.length);
outBin.write(headerBytes);
outBin.writeLong(contentLen);
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
byte[] buf = new byte[8192];
int n;
while ((n = fis.read(buf)) != -1) outBin.write(buf, 0, n);
}
outBin.flush();
} finally {
TelegramClient.mediaBusy.set(false);
}
// انتظار ACK (هر کدوم از کلیدها بیاد، Listener می‌فرسته به صف q)
JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS);
if (ack == null) {
System.out.println("❌ Avatar ACK timeout for " + reqId);
return;
}
if ("success".equalsIgnoreCase(ack.optString("status"))) {
String url = ack.optString("display_url", null);
lastStatus = "success";
lastMessage = url != null ? url : "";
if (url != null && targetId != null) {
String busted = url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
// به‌روزرسانی State و UI: چت‌لیست + هدر
Platform.runLater(() -> {
var mc = org.to.telegramfinalproject.UI.MainController.getInstance();
});
}
} else {
System.out.println("❌ Avatar failed: " + ack.optString("message"));
lastStatus = "error";
lastMessage = ack.optString("message", "failed");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("❌ uploadAvatarFor error: " + e.getMessage());
lastStatus = "error";
lastMessage = e.getMessage();
} finally {
TelegramClient.pendingResponses.remove(reqId.toString());
}
}
private static String getExt(String name) {
int i = name.lastIndexOf('.');
return (i >= 0) ? name.substring(i) : "";
}
private void updateChatAvatarLocally(String targetType, UUID chatId, String newUrl) {
try {
java.util.function.Consumer<java.util.List<org.to.telegramfinalproject.Models.ChatEntry>> upd =
list -> list.stream()
.filter(c -> c.getId().equals(chatId))
.findFirst()
.ifPresent(c -> c.setImageUrl(newUrl));
upd.accept(Session.chatList);
upd.accept(Session.activeChats);
upd.accept(Session.archivedChats);
} catch (Exception ignore) {}
}
}
@@ -36,34 +36,43 @@ public class IncomingMessageListener implements Runnable {
try {
System.out.println("👂 Real-Time Listener started.");
String line;
while ((line = in.readLine()) != null) {
while (true) {
if (TelegramClient.mediaBusy.get()) {
try { Thread.sleep(15); } catch (InterruptedException ignored) {}
continue;
}
String line = in.readLine();
if (line == null) break;
if (line.isBlank()) continue;
final JSONObject response;
try {
response = new JSONObject(line);
} catch (Exception badJson) {
System.out.println("⚠️ [Listener] Non-JSON line ignored: " + line);
continue;
}
JSONObject response = new JSONObject(line);
System.out.println("📥 Received raw line: " + line);
//if it has reqID answer
// 1) اول message_id را روت کن (برای ACK نهایی مدیا)
String mid = response.optString("message_id", "");
if (!mid.isEmpty()) {
BlockingQueue<JSONObject> q = TelegramClient.pendingResponses.get(mid);
if (q != null) {
q.put(response);
continue; // این پیام مصرف شد
continue; // مصرف شد
}
}
// 2) بعد request_id را روت کن (برای INIT و بقیه درخواست‌ها)
if (response.has("request_id")) {
String requestId = response.getString("request_id");
System.out.println("📬 Response with request_id: " + requestId);
System.out.println("📬 Full response: " + response.toString(2));
BlockingQueue<JSONObject> queue = TelegramClient.pendingResponses.get(requestId);
if (queue != null) {
queue.put(response);
@@ -71,13 +80,10 @@ public class IncomingMessageListener implements Runnable {
System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue...");
TelegramClient.responseQueue.put(response);
}
continue;
}
//if it has action check it
// 3) رویدادهای real-time
if (response.has("action")) {
String action = response.getString("action");
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
@@ -88,9 +94,12 @@ public class IncomingMessageListener implements Runnable {
} else {
TelegramClient.responseQueue.put(response);
}
continue;
}
} else if (response.has("status") && response.has("message")) {
TelegramClient.responseQueue.put(response); // general answer
// 4) سایر پاسخ‌های عمومی
if (response.has("status") && response.has("message")) {
TelegramClient.responseQueue.put(response);
} else {
TelegramClient.responseQueue.put(response); // fallback
}
@@ -222,9 +222,6 @@ public class TelegramClient {
if (socket != null && socket.isConnected() && !socket.isClosed()) return;
socket = new Socket(SERVER_HOST, SERVER_PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
out = new PrintWriter(socket.getOutputStream(), true);
InputStream rawIn = socket.getInputStream();
OutputStream rawOut = socket.getOutputStream();
@@ -256,14 +253,24 @@ public class TelegramClient {
// }
// private void startListenerOnce(IncomingMessageListener.UIMode mode) {
// if (listenerStarted) return;
// listenerStarted = true;
//
// Thread listenerThread = new Thread(
// new IncomingMessageListener(in, mode),
// "socket-listener"
// );
// listenerThread.setDaemon(true);
// listenerThread.start();
// }
private void startListenerOnce(IncomingMessageListener.UIMode mode) {
if (listenerStarted) return;
listenerStarted = true;
Thread listenerThread = new Thread(
new IncomingMessageListener(in, mode),
"socket-listener"
);
listener = new IncomingMessageListener(in, mode);
Thread listenerThread = new Thread(listener, "socket-listener");
listenerThread.setDaemon(true);
listenerThread.start();
}
@@ -223,16 +223,58 @@ public class ChannelDatabase {
}
public static boolean insertChannel(UUID internalUUID, String channelId, String channelName, UUID creatorId, String imageUrl, LocalDateTime createdAt) {
String sql = "INSERT INTO channels (internal_uuid, channel_id, channel_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)";
// public static boolean insertChannel(UUID internalUUID, String channelId, String channelName, UUID creatorId, String imageUrl, LocalDateTime createdAt) {
// String sql = "INSERT INTO channels (internal_uuid, channel_id, channel_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)";
//
// try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
// stmt.setObject(1, internalUUID);
// stmt.setString(2, channelId);
// stmt.setString(3, channelName);
// stmt.setObject(4, creatorId);
// stmt.setString(5, imageUrl);
// stmt.setObject(6, createdAt);
// stmt.executeUpdate();
// return true;
// } catch (SQLException e) {
// e.printStackTrace();
// return false;
// }
// }
public static boolean insertChannel(UUID internalUUID,
String channelId,
String channelName,
UUID creatorId,
String imageUrl,
String description,
LocalDateTime createdAt) {
String sql = "INSERT INTO channels " +
"(internal_uuid, channel_id, channel_name, creator_id, image_url, description, created_at) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) {
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, internalUUID);
stmt.setString(2, channelId);
stmt.setString(3, channelName);
stmt.setObject(4, creatorId);
if (imageUrl != null && !imageUrl.isBlank()) {
stmt.setString(5, imageUrl);
stmt.setObject(6, createdAt);
} else {
stmt.setNull(5, java.sql.Types.VARCHAR);
}
if (description != null && !description.isBlank()) {
// اگر می‌خوای مطمئن بشی 255 نشکنه:
stmt.setString(6, description.length() > 255 ? description.substring(0, 255) : description);
} else {
stmt.setNull(6, java.sql.Types.VARCHAR);
}
stmt.setObject(7, createdAt);
stmt.executeUpdate();
return true;
} catch (SQLException e) {
@@ -7,11 +7,11 @@ import java.time.LocalDateTime;
import java.util.UUID;
public class ChannelService {
public static boolean createChannel(String channelId, String channelName, UUID creatorUUID, String imageUrl) {
public static boolean createChannel(String channelId, String channelName, UUID creatorUUID, String imageUrl,String description) {
UUID internalUUID = UUID.randomUUID();
LocalDateTime now = LocalDateTime.now();
boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, now);
boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, description,now);
if (inserted) {
ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner");
@@ -970,8 +970,8 @@ public class ClientHandler implements Runnable {
UUID creatorUUID = UUID.fromString(userIdStr);
// اگر سرویس‌ات ورودی توضیح را می‌پذیرد، از متد اورلودشده استفاده کن:
// boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
//boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl);
if (created) {
Channel createdChannel = ChannelDatabase.findByChannelId(channelId);
@@ -3319,6 +3319,10 @@ public class ClientHandler implements Runnable {
}
}
private boolean hasManagePermission(UUID currentUserId, String targetType, UUID targetId) {
return true;
}
private static String err(String m) { return new JSONObject().put("status","error").put("message",m).toString(); }
private static boolean isAllowedImageMime(String mime) {
@@ -3344,8 +3348,8 @@ public class ClientHandler implements Runnable {
private boolean updateProfileImageUrl(String targetType, UUID targetId, String url) {
String sql = switch (targetType) {
case "user" -> "UPDATE users SET image_url=? WHERE internal_uuid=?";
case "channel" -> "UPDATE channels SET image_url=? WHERE id=?";
case "group" -> "UPDATE groups SET image_url=? WHERE id=?";
case "channel" -> "UPDATE channels SET image_url=? WHERE internal_uuid=?";
case "group" -> "UPDATE groups SET image_url=? WHERE internal_uuid=?";
default -> null;
};
if (sql == null) return false;
@@ -117,4 +117,28 @@ public class ChatItemController {
unreadCount.setManaged(show);
if (show) unreadCount.setText(String.valueOf(unread));
}
// ChatItemController.java
@FXML private ImageView avatarImage;
// اگر نوع چت نیاز داری برای دیفالت‌ها (private|group|channel)
private String chatType; // اگر داری از setChatData بگیرش و نگه دار
public void updateAvatar(String url) {
try {
if (url == null || url.isBlank()) {
String def = switch (chatType == null ? "" : chatType.toLowerCase()) {
case "group" -> "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
case "channel" -> "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
default -> "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
};
avatarImage.setImage(new Image(Objects.requireNonNull(
getClass().getResourceAsStream(def))));
} else {
avatarImage.setImage(new Image(url, true)); // true = لود غیرهمزمان
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -30,6 +30,8 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import static org.to.telegramfinalproject.Client.Session.currentChatId;
public class ChatPageController {
// ===== messages area =====
@@ -2354,6 +2356,39 @@ private void addBubble(
}
}
// ChatPageController.java
public void onChatAvatarUpdated(UUID chatId, String newUrl) {
// اگه چت فعلی چیز دیگریه، کاری نکن
if (currentChat == null || chatId == null || newUrl == null || newUrl.isBlank()) return;
if (!currentChat.getId().equals(chatId)) return;
// اگر از نخ غیر JavaFX صدا زده شد، امنش کنیم
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> onChatAvatarUpdated(chatId, newUrl));
return;
}
// State داخلی entry را هم آپدیت کن
currentChat.setImageUrl(newUrl);
// آواتار هدر را ست کن (با resolver خودت)
try {
Image im = org.to.telegramfinalproject.Client.AvatarLocalResolver.load(newUrl);
if (im != null) {
userAvatar.setImage(im);
} else {
// fallback اگر لود نشد
setDefaultHeaderAvatarByType(currentChat.getType());
}
} catch (Exception ignore) {
setDefaultHeaderAvatarByType(currentChat.getType());
}
// مطمئن شو همچنان دایره‌ایه
try { AvatarFX.circleClip(userAvatar, 36); } catch (Throwable ignored) {}
}
private static final class ForwardTarget {
@@ -437,6 +437,9 @@ public class MainController {
ChatPageController controller = loader.getController();
controller.showChat(chat);
@@ -118,28 +118,34 @@ public class NewChannelController {
return;
}
// TODO: اگر آپلود تصویر داری، فایل را آپلود کن و URL نهایی را اینجا بفرست
final String imageUrl = null;
// در مرحله ساخت، image_url را خالی بفرست؛ بعداً آپلود می‌کنیم
JSONObject req = new JSONObject()
.put("action", "create_channel")
.put("channel_id", dispId) // آیدی نمایشی/عمومی
.put("channel_id", dispId)
.put("channel_name", name)
.put("user_id", me) // internal_uuid سازنده
.put("image_url", imageUrl) // اختیاری
.put("description", description); // اختیاری (سرور اگر ساپورت نکند نادیده می‌گیرد)
.put("user_id", me)
.put("image_url", (Object) null)
.put("description", description);
createButton.setDisable(true);
new Thread(() -> {
try {
JSONObject resp = ActionHandler.sendWithResponse(req);
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
Platform.runLater(() -> showToast("Create failed: " +
(resp == null ? "no response" : resp.optString("message",""))));
Platform.runLater(() -> {
createButton.setDisable(false);
showToast("Create failed: " + (resp == null ? "no response" : resp.optString("message","")));
});
return;
}
JSONObject data = resp.optJSONObject("data");
if (data == null) {
Platform.runLater(() -> showToast("Create failed: empty data."));
Platform.runLater(() -> {
createButton.setDisable(false);
showToast("Create failed: empty data.");
});
return;
}
@@ -148,27 +154,34 @@ public class NewChannelController {
String returnedDisp = data.optString("id", dispId);
String returnedImg = data.optString("image_url", "");
// 1) چت کانال تازه‌ساخته را به سایدبار اضافه کن و انتخاب کن
// اگر کاربر عکس انتخاب کرده بود: الان آپلود کن (target_type=channel)
if (channelImageFile != null) {
ActionHandler.instance.uploadAvatarFor("channel", internalId, channelImageFile);
if (ActionHandler.instance.wasSuccess()) {
String url = ActionHandler.instance.getLastMessage(); // display_url
if (url != null && !url.isBlank()) {
returnedImg = url;
}
} else {
// اختیاری: پیام خطا را نشان بده
System.out.println("Channel avatar upload failed: " + ActionHandler.instance.getLastMessage());
}
}
String finalImg = returnedImg;
Platform.runLater(() -> {
// 1) کانال را به سایدبار اضافه و انتخاب کن
ChatEntry entry = ChatEntry.fromServer(
internalId,
"channel",
returnedName,
returnedDisp,
returnedImg,
/*isOwner*/ true,
/*isAdmin*/ true
internalId, "channel", returnedName, returnedDisp, finalImg,
/*isOwner*/ true, /*isAdmin*/ true
);
MainController.getInstance().addChatAndSelect(entry);
});
// 2) اُورلی افزودن سابسکرایبر را باز کن و internal_id را پاس بده
Platform.runLater(() -> {
// 2) باز کردن Overlay افزودن سابسکرایبر و بستن این Overlay
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
StackPane addSubsOverlay = loader.load();
AddSubscriberController controller = loader.getController();
controller.setChannelInfo(internalId, returnedName, returnedDisp, channelImageFile, description);
@@ -178,10 +191,20 @@ public class NewChannelController {
ex.printStackTrace();
showToast("Failed to open Add Subscribers.");
}
createButton.setDisable(false);
});
} catch (Exception ex) {
Platform.runLater(() -> {
createButton.setDisable(false);
showToast("Create failed: " + ex.getMessage());
});
}
}).start();
}
private void showToast(String msg) {
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
a.initOwner(overlayRoot.getScene().getWindow());
@@ -14,12 +14,16 @@ import javafx.scene.layout.Pane;
import org.json.JSONObject;
import org.to.telegramfinalproject.Client.ActionHandler;
import org.to.telegramfinalproject.Client.Session;
import org.to.telegramfinalproject.Client.TelegramClient;
import org.to.telegramfinalproject.Models.ChatEntry;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static org.to.telegramfinalproject.Client.ActionHandler.detectMime;
public class NewGroupController {
@@ -83,34 +87,25 @@ public class NewGroupController {
String groupId = groupIdField.getText() == null ? "" : groupIdField.getText().trim();
boolean ok = true;
if (groupName.isEmpty()) {
groupNameField.getStyleClass().add("error");
groupNameLabel.getStyleClass().add("error");
ok = false;
}
if (groupId.isEmpty()) {
groupIdField.getStyleClass().add("error");
groupIdLabel.getStyleClass().add("error");
ok = false;
}
if (groupName.isEmpty()) { groupNameField.getStyleClass().add("error"); groupNameLabel.getStyleClass().add("error"); ok = false; }
if (groupId.isEmpty()) { groupIdField.getStyleClass().add("error"); groupIdLabel.getStyleClass().add("error"); ok = false; }
if (!ok) return;
// ساخت گروه روی سرور
final String me = Session.getUserUUID(); // internal_uuid
final String me = Session.getUserUUID();
if (me == null || me.isBlank()) {
showToast("Cannot create group: current user UUID missing.");
return;
}
// اگر آپلود تصویر داری، اینجا عکس رو آپلود کن و imageUrl واقعی بفرست (TODO)
// در مرحلهٔ ساخت، image_url را خالی بفرست (بعداً آپلود می‌کنیم)
final String imageUrl = null;
JSONObject req = new JSONObject()
.put("action", "create_group")
.put("group_id", groupId)
.put("group_name", groupName)
.put("user_id", me) // internal_uuid سازنده
.put("image_url", imageUrl); // اختیاری
.put("user_id", me)
.put("image_url", imageUrl);
new Thread(() -> {
JSONObject resp = ActionHandler.sendWithResponse(req);
@@ -131,21 +126,37 @@ public class NewGroupController {
String returnedDisp = data.optString("id", groupId);
String returnedImg = data.optString("image_url", "");
// 1) به‌صورت لوکال یک ChatEntry بساز و به سایدبار اضافه و سِلکت کن
// اگر عکس انتخاب شده، حالا آپلود کن (target_type=group, target_id=internalId)
String finalImageUrl = returnedImg;
if (groupImageFile != null) {
ActionHandler.instance.uploadAvatarFor("group", internalId, groupImageFile);
if (ActionHandler.instance.wasSuccess()) {
String url = ActionHandler.instance.getLastMessage(); // display_url
if (url != null && !url.isBlank()) {
finalImageUrl = url;
}
}
}
// 1) به سایدبار اضافه و انتخاب کن
String imageUrlToUse = finalImageUrl; // برای استفاده داخل lambda
Platform.runLater(() -> {
ChatEntry entry = ChatEntry.fromServer(
internalId,
"group",
returnedName,
returnedDisp,
returnedImg,
imageUrlToUse,
/*isOwner*/ true,
/*isAdmin*/ true
);
MainController.getInstance().addChatAndSelect(entry);
// اگر URL جدید داریم، می‌تونی یک bust-cache بزنی:
// MainController.getInstance().refreshChatAvatar(internalId, imageUrlToUse + "?v=" + System.currentTimeMillis());
});
// 2) پنجره‌ی Add Members را باز کن و internal_id گروه را پاس بده
// 2) پنجره Add Members
Platform.runLater(() -> {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
@@ -154,10 +165,7 @@ public class NewGroupController {
AddMembersController controller = loader.getController();
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
// این اُورلی را روی UI نشان بده
MainController.getInstance().showOverlay(addMembersOverlay);
// این اُورلی NewGroup را ببند
MainController.getInstance().closeOverlay(overlayRoot);
} catch (IOException ex) {
ex.printStackTrace();
@@ -167,10 +175,14 @@ public class NewGroupController {
}).start();
}
private void showToast(String msg) {
// جایگزینش کن با سیستم نوتی شما
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
a.initOwner(overlayRoot.getScene().getWindow());
a.show();
}
}