From c45a8f22be4ff0e10672a53d2aae407657041764 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Sun, 7 Sep 2025 18:15:25 +0330 Subject: [PATCH] Sending image --- .../Client/ActionHandler.java | 220 +++++++---- .../UI/ChatPageController.java | 345 +++++++++++++----- .../org/to/telegramfinalproject/CSS/chat.css | 18 +- .../242a76f9-f540-4d3a-bd0a-6507099b164e.jpeg | Bin 0 -> 3860 bytes .../7b6d3072-1c72-40d8-bee7-d6dbfda54dd2.jpg | Bin 0 -> 3237 bytes .../a93c7324-22fa-4268-9f1e-34a9448f5f6e.jpg | Bin 0 -> 2664 bytes .../d9208502-a2a5-41d2-b912-29d622b582bf.jpg | Bin 0 -> 6920 bytes .../dcb9b568-abea-43f4-bc39-acd71dc2438c.jpg | Bin 0 -> 4467 bytes .../e85885e6-4502-45fe-a2fe-71dd1f8595ba.jpg | Bin 0 -> 7851 bytes .../f87ee3b0-0a90-4c54-be85-a91f04d517df.jpg | Bin 0 -> 3627 bytes 10 files changed, 422 insertions(+), 161 deletions(-) create mode 100644 uploads/images/2025-09-07/242a76f9-f540-4d3a-bd0a-6507099b164e.jpeg create mode 100644 uploads/images/2025-09-07/7b6d3072-1c72-40d8-bee7-d6dbfda54dd2.jpg create mode 100644 uploads/images/2025-09-07/a93c7324-22fa-4268-9f1e-34a9448f5f6e.jpg create mode 100644 uploads/images/2025-09-07/d9208502-a2a5-41d2-b912-29d622b582bf.jpg create mode 100644 uploads/images/2025-09-07/dcb9b568-abea-43f4-bc39-acd71dc2438c.jpg create mode 100644 uploads/images/2025-09-07/e85885e6-4502-45fe-a2fe-71dd1f8595ba.jpg create mode 100644 uploads/images/2025-09-07/f87ee3b0-0a90-4c54-be85-a91f04d517df.jpg diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 06b5dd0..7a84301 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1,6 +1,9 @@ package org.to.telegramfinalproject.Client; import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Database.PrivateChatDatabase; @@ -9,6 +12,7 @@ import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.SearchRequestModel; import org.to.telegramfinalproject.Models.SearchResultModel; +import org.to.telegramfinalproject.UI.ChatPageController; import java.io.*; import java.nio.file.Files; @@ -4233,38 +4237,38 @@ 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()); - } - } - } +// +// 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() @@ -4282,36 +4286,117 @@ public class ActionHandler { } } +// +// 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", Session.getUserUUID()) +// .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 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 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(); + + // اورلود پیشنهادی: messageId از بیرون دریافت می‌شود + public void sendMediaMessage( + UUID messageId, // 👈 از بیرون می‌آید (برای Pending) + UUID receiverId, + String receiverType, // "private" | "group" | "channel" + String type, // "IMAGE" | "AUDIO" + File file, + String caption + ) { + if (file == null || !file.exists() || file.isDirectory()) { + Platform.runLater(() -> ChatPageController.get().updatePendingStatus( + messageId.toString(), "Failed (file)")); + return; + } 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", Session.getUserUUID()) - .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); + .put("message_id", messageId.toString()) + .put("sender_id", Session.getUserUUID()) // همون UUID کاربر + .put("receiver_type",receiverType) + .put("receiver_id", receiverId.toString()) + .put("message_type", type.toUpperCase()) + .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(); @@ -4320,15 +4405,13 @@ public class ActionHandler { 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) + outBin.writeInt(0x4D444D31); // "MDM1" + outBin.writeInt(headerBytes.length); + outBin.write(headerBytes); + outBin.writeLong(contentLen); try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) { byte[] buf = new byte[8192]; @@ -4342,15 +4425,23 @@ public class ActionHandler { JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS); if (ack == null) { System.out.println("❌ Media ACK timeout for " + messageId); + Platform.runLater(() -> ChatPageController.get().updatePendingStatus( + messageId.toString(), "Failed (timeout)")); 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")); + // سرور شما فیلد نمایش را با نام display_path می‌فرستد (نه file_url) + String url = ack.optString("display_path", ack.optString("file_url", "")); + System.out.println("✅ Media sent. id=" + ack.optString("message_id") + " url=" + url); + + // بابل Pending را بردار (بلافاصله؛ پیام واقعی هم بعداً با real-time می‌آید) + Platform.runLater(() -> ChatPageController.get().removePendingBubble(messageId.toString())); } else { System.out.println("❌ Media failed: " + ack.optString("message")); + Platform.runLater(() -> ChatPageController.get().updatePendingStatus( + messageId.toString(), "Failed to send")); } } finally { @@ -4360,6 +4451,8 @@ public class ActionHandler { } catch (Exception e) { e.printStackTrace(); System.out.println("❌ sendMediaMessage error: " + e.getMessage()); + Platform.runLater(() -> ChatPageController.get().updatePendingStatus( + messageId.toString(), "Error")); } } @@ -4631,4 +4724,7 @@ public class ActionHandler { + + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index 9f2af55..38ed2dd 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -124,6 +124,7 @@ public class ChatPageController { private final Map messageNodes = new HashMap<>(); private final Deque pendingBubbles = new ArrayDeque<>(); + private final Map pendingById = new HashMap<>(); // جایی عمومی (مثلا بالای کلاس) @@ -169,6 +170,12 @@ public class ChatPageController { } + + + public static ChatPageController get() { + return instance; + } + private void initCurrentUserId() { try { String meStr = org.to.telegramfinalproject.Client.Session @@ -707,52 +714,39 @@ public class ChatPageController { }).start(); } - private void openFileChooser() { FileChooser fc = new FileChooser(); fc.setTitle("Select image or audio"); - - // فقط عکس/صدا fc.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.webp"), - new FileChooser.ExtensionFilter("Audio", "*.mp3", "*.wav", "*.m4a", "*.ogg", "*.aac") + new FileChooser.ExtensionFilter("Images", "*.png","*.jpg","*.jpeg","*.gif","*.bmp","*.webp"), + new FileChooser.ExtensionFilter("Audio", "*.mp3","*.wav","*.m4a","*.ogg","*.aac") ); File file = fc.showOpenDialog(attachmentButton.getScene().getWindow()); if (file == null) return; - // نوع (IMAGE/AUDIO) را از اسم/محتوا حدس بزنیم - String type = guessType(file); - if (type == null) { - toast("Only Image and Aduio"); - return; - } - currentChatId = String.valueOf(currentChat.getId()); - Session.currentChatType = currentChat.getType(); + String type = guessType(file); // برگرداندن "IMAGE" یا "AUDIO" + if (type == null) { toast("Only image or audio"); return; } - // مقصد چت از Session - if (Session.currentChatId == null || Session.currentChatType == null) { - toast("Not available chat"); - return; - } - - UUID receiverId = UUID.fromString(Session.currentChatId); - String receiverType = Session.currentChatType; // "private" | "group" | "channel" + if (currentChat == null) { toast("Not available chat"); return; } + UUID receiverId = currentChat.getId(); // همون chat_id + String receiverType = currentChat.getType(); // "private" | "group" | "channel" String caption = (messageInput != null) ? messageInput.getText().trim() : ""; if (messageInput != null) messageInput.clear(); - // 1) حباب Pending فوری در UI - HBox pending = addPendingMediaBubble(file, type, caption); + // 1) message_id را همین‌جا بساز تا Pending به همین ID وصل شود + UUID messageId = UUID.randomUUID(); - // 2) ارسال واقعی در بک‌گراند با ActionHandler + // 2) حباب Pending (نسخه‌ای که messageId می‌گیرد) + addPendingMediaBubble(messageId.toString(), file, type, caption); + + // 3) ارسال واقعی با همین messageId new Thread(() -> { ActionHandler ah = ActionHandler.getInstance(); - ah.sendMediaMessage(receiverId, receiverType, type, file, caption); - - // نکته: پیام واقعی بعد از ذخیره روی سرور، از طریق Listener برمی‌گرده. - // وقتی رسید، در Listener این متد را صدا بزنید تا یک حباب Pending حذف شود: - // Platform.runLater(() -> removeOnePendingBubble()); + ah.sendMediaMessage(messageId, receiverId, receiverType, type, file, caption); + // اگر ACK success برگشت، خود ActionHandler می‌تونه removePendingBubble(messageId) صدا بزنه + // وگرنه در onRealTimeNewMessage که پیام واقعی آمد، پاک می‌کنیم (کد آن را قبلاً دادم). }, "Media-Uploader").start(); } @@ -765,62 +759,120 @@ public class ChatPageController { return null; } - /** ساخت یک حباب «درحال ارسال…» */ - private HBox addPendingMediaBubble(File file, String type, String caption) { - HBox root = new HBox(8); - root.getStyleClass().add("bubble-outgoing"); // استایل دلخواهت - root.setFillHeight(true); +// /** ساخت یک حباب «درحال ارسال…» */ +// private HBox addPendingMediaBubble(File file, String type, String caption) { +// HBox root = new HBox(8); +// root.getStyleClass().add("bubble-outgoing"); // استایل دلخواهت +// root.setFillHeight(true); +// +// ImageView iv = null; +// if ("IMAGE".equalsIgnoreCase(type)) { +// iv = new ImageView(new Image(file.toURI().toString(), 360, 360, true, true, true)); +// iv.setPreserveRatio(true); +// iv.setFitWidth(240); // سایز معقول برای Pending +// iv.setFitHeight(240); +// root.getChildren().add(iv); +// } else if ("AUDIO".equalsIgnoreCase(type)) { +// // برای صدا یک آیکون ساده و نام فایل +// ImageView icon = new ImageView(); // اگر آیکون داری اینجا بگذار +// icon.setFitWidth(24); icon.setFitHeight(24); +// Label name = new Label(file.getName()); +// HBox audioBox = new HBox(6, icon, name); +// root.getChildren().add(audioBox); +// } +// +// VBox right = new VBox(4); +// if (caption != null && !caption.isBlank()) { +// Label cap = new Label(caption); +// cap.getStyleClass().add("msg-caption"); +// cap.setWrapText(true); +// right.getChildren().add(cap); +// } +// +// HBox statusRow = new HBox(6); +// ProgressIndicator spinner = new ProgressIndicator(); +// spinner.setPrefSize(16, 16); +// Label status = new Label("Sending..."); +// status.getStyleClass().add("msg-status"); +// Region spacer = new Region(); +// HBox.setHgrow(spacer, Priority.ALWAYS); +// statusRow.getChildren().addAll(spinner, status, spacer); +// +// right.getChildren().add(statusRow); +// root.getChildren().add(right); +// +// messageContainer.getChildren().add(root); +// pendingBubbles.addLast(root); +// +// return root; +// } +// +// /** وقتی پیام واقعی (از خودِ کاربر) برای همین چت رسید، یکی از Pendingها را حذف کن. */ +// public void removeOnePendingBubble() { +// HBox node = pendingBubbles.pollFirst(); +// if (node != null) { +// messageContainer.getChildren().remove(node); +// } +// } - ImageView iv = null; + + private HBox addPendingMediaBubble(String messageId, File file, String type, String caption) { + HBox root = new HBox(8); + root.setFillHeight(true); + root.setAlignment(Pos.CENTER_RIGHT); // چون outgoing است + + // preview if ("IMAGE".equalsIgnoreCase(type)) { - iv = new ImageView(new Image(file.toURI().toString(), 360, 360, true, true, true)); + ImageView iv = new ImageView(new Image(file.toURI().toString(), 240, 240, true, true, true)); iv.setPreserveRatio(true); - iv.setFitWidth(240); // سایز معقول برای Pending + iv.setFitWidth(240); iv.setFitHeight(240); root.getChildren().add(iv); } else if ("AUDIO".equalsIgnoreCase(type)) { - // برای صدا یک آیکون ساده و نام فایل - ImageView icon = new ImageView(); // اگر آیکون داری اینجا بگذار - icon.setFitWidth(24); icon.setFitHeight(24); Label name = new Label(file.getName()); - HBox audioBox = new HBox(6, icon, name); - root.getChildren().add(audioBox); + root.getChildren().add(new HBox(6, new Label("🎵"), name)); } VBox right = new VBox(4); if (caption != null && !caption.isBlank()) { Label cap = new Label(caption); - cap.getStyleClass().add("msg-caption"); cap.setWrapText(true); right.getChildren().add(cap); } HBox statusRow = new HBox(6); ProgressIndicator spinner = new ProgressIndicator(); - spinner.setPrefSize(16, 16); + spinner.setPrefSize(14, 14); Label status = new Label("Sending..."); - status.getStyleClass().add("msg-status"); - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - statusRow.getChildren().addAll(spinner, status, spacer); - + status.getProperties().put("role", "statusLabel"); // برای آپدیت بعدی + statusRow.getChildren().addAll(spinner, status); right.getChildren().add(statusRow); + root.getChildren().add(right); + // برچسب messageId روی نود + if (messageId != null) root.getProperties().put("messageId", messageId); + messageContainer.getChildren().add(root); + + // ثبت در مپ/صف + if (messageId != null) pendingById.put(messageId, root); pendingBubbles.addLast(root); return root; } - /** وقتی پیام واقعی (از خودِ کاربر) برای همین چت رسید، یکی از Pendingها را حذف کن. */ - public void removeOnePendingBubble() { - HBox node = pendingBubbles.pollFirst(); + + public void removePendingBubble(String messageId) { + if (messageId == null) return; + HBox node = pendingById.remove(messageId); if (node != null) { + pendingBubbles.remove(node); messageContainer.getChildren().remove(node); } } + private void toast(String msg) { // هر جور که خودت نوتیف/Toast داری System.out.println("ℹ️ " + msg); @@ -2091,32 +2143,26 @@ public class ChatPageController { } - private javafx.scene.Node buildReactionsBarFromJson(org.json.JSONArray arr, boolean dark) { - javafx.scene.layout.HBox bar = new javafx.scene.layout.HBox(6); - bar.setAlignment(javafx.geometry.Pos.CENTER_LEFT); - for (int i = 0; i < arr.length(); i++) { - org.json.JSONObject ro = arr.optJSONObject(i); - if (ro == null) continue; - String emoji = ro.optString("emoji", "👍"); - int count = ro.optInt("count", 1); - boolean byMe = ro.optBoolean("by_me", false); + private Node buildReactionsBarFromJson(org.json.JSONArray reactions, boolean dark) { + HBox bar = new HBox(6); + for (int i = 0; i < reactions.length(); i++) { + var r = reactions.getJSONObject(i); + String emo = r.optString("emoji", "👍"); // 👈 باید کاراکتر واقعی باشه + int cnt = r.optInt("count", 1); + + Label chip = new Label(emo + " " + cnt); + chip.getStyleClass().add("emoji-label"); // 👈 کلاس CSS برای ایموجی + chip.setStyle("-fx-background-color:" + (dark ? "#39424a" : "#e9eef3") + + "; -fx-padding:3 8; -fx-background-radius:12;"); - String chipBg = byMe ? (dark ? "#215a9f" : "#d1e8ff") - : (dark ? "#2f3942" : "#eef3f7"); - String text = emoji + (count > 0 ? (" " + count) : ""); - Label chip = new Label(text); - chip.setStyle( - "-fx-font-size: 12;" + - "-fx-background-radius: 12;" + - "-fx-padding: 2 8;" + - "-fx-background-color: " + chipBg + ";" - ); bar.getChildren().add(chip); } return bar; } + + private static String ellipsize(String s, int max) { return s.length() > max ? s.substring(0, max) + "…" : s; } public boolean isSameChat(UUID chatId, String type) { @@ -2224,67 +2270,150 @@ public class ChatPageController { // } - public void onRealTimeNewMessage(JSONObject m) { +// public void onRealTimeNewMessage(JSONObject m) { +// try { +// String chatIdStr = str(m,"receiver_id"); +// String chatType = str(m,"receiver_type"); +// if (chatIdStr.isEmpty() || chatType.isEmpty()) return; +// +// UUID chatId = UUID.fromString(chatIdStr); +// boolean isCurrent = isSameChat(chatId, chatType); +// +// // id → message_id fallback +// if (!m.has("message_id") && m.has("id")) { +// m.put("message_id", m.getString("id")); +// } +// String msgId = str(m,"message_id"); +// if (!hasVal(msgId)) return; +// +// // اگر قبلاً تو UI هست، دوباره نساز +// if (messageNodes.containsKey(msgId)) return; +// +// String senderId = str(m,"sender_id"); +// String senderName = hasVal(str(m,"sender_name")) ? str(m,"sender_name") +// : (hasVal(senderId) ? shortId(senderId) : "Unknown"); +// +// String type = hasVal(str(m,"message_type")) ? str(m,"message_type") : "TEXT"; +// String content = str(m,"content"); // برای IMAGE = کپشن +// String whenIso = str(m,"send_at"); +// +// // 👇 جدید: URL ها برای عکس/صدا +// String fileUrl = str(m,"file_url"); +// String thumbUrl = str(m,"thumb_url"); +// +// String fwdFrom = str(m,"forwarded_from"); +// String fwdBy = str(m,"forwarded_by"); +// String replyTo = str(m,"reply_to_id"); +// boolean edited = bool(m,"is_edited"); +// JSONArray reacts = arr(m,"reactions"); +// +// LocalDateTime ts = parseWhen(whenIso); +// if (ts == null) ts = LocalDateTime.now(); +// +// // اندیس پیام برای ریپلای/ادیت +// msgIndex.put(msgId, m); +// +// // تشخیص خروجی/ورودی +// String myId = (Session.currentUser != null && Session.currentUser.has("internal_uuid")) +// ? Session.currentUser.getString("internal_uuid") : ""; +// boolean outgoing = hasVal(senderId) && senderId.equalsIgnoreCase(myId); +// +// // آپدیت لیست چت‌ها (پریویوِ کوتاه) +// String previewText = switch (type.toUpperCase()) { +// case "IMAGE" -> (hasVal(content) ? "🖼️ Photo — " + content : "🖼️ Photo"); +// case "AUDIO" -> "🎵 Audio"; +// default -> content; +// }; +// updateChatListPreview(chatId, chatType, !outgoing, previewText, type); +// +// if (!isCurrent) return; +// +// // 👇 امضای جدیدِ addBubble (با fileUrl/thumbUrl) +// addBubble(outgoing, senderName, type, content, ts, msgId, +// fwdFrom, fwdBy, replyTo, edited, reacts, fileUrl, thumbUrl); +// +// if (currentChat != null) markAsRead(currentChat); +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + + + public void onRealTimeNewMessage(org.json.JSONObject m) { try { - String chatIdStr = str(m,"receiver_id"); - String chatType = str(m,"receiver_type"); + // 1) chat id/type با fallback + String chatIdStr = nz(m.optString("receiver_id", m.optString("chat_id",""))); + String chatType = nz(m.optString("receiver_type", m.optString("chat_type",""))); if (chatIdStr.isEmpty() || chatType.isEmpty()) return; UUID chatId = UUID.fromString(chatIdStr); boolean isCurrent = isSameChat(chatId, chatType); - // id → message_id fallback - if (!m.has("message_id") && m.has("id")) { - m.put("message_id", m.getString("id")); - } + // 2) message_id با fallback از id + if (!m.has("message_id") && m.has("id")) m.put("message_id", m.getString("id")); String msgId = str(m,"message_id"); if (!hasVal(msgId)) return; - // اگر قبلاً تو UI هست، دوباره نساز + // تکراری نساز if (messageNodes.containsKey(msgId)) return; + // 3) sender/name String senderId = str(m,"sender_id"); String senderName = hasVal(str(m,"sender_name")) ? str(m,"sender_name") : (hasVal(senderId) ? shortId(senderId) : "Unknown"); - String type = hasVal(str(m,"message_type")) ? str(m,"message_type") : "TEXT"; - String content = str(m,"content"); // برای IMAGE = کپشن - String whenIso = str(m,"send_at"); + // 4) نوع پیام (سرور ممکنه lowercase بده) + String tRaw = nz(m.optString("message_type","TEXT")); + String type = tRaw.trim().toUpperCase(java.util.Locale.ROOT); - // 👇 جدید: URL ها برای عکس/صدا - String fileUrl = str(m,"file_url"); - String thumbUrl = str(m,"thumb_url"); + // 5) متن/کپشن (content یا text) + String content = nz(m.optString("content", m.optString("text",""))); + // 6) زمان + String whenIso = nz(m.optString("send_at", m.optString("created_at",""))); + java.time.LocalDateTime ts = parseWhen(whenIso); + if (ts == null) ts = java.time.LocalDateTime.now(); + + // 7) فایل: هم فرمت قدیم (file_url/thumb_url) هم جدید (media.url/thumbnail_url) + String fileUrl = nz(m.optString("file_url","")); + String thumbUrl = nz(m.optString("thumb_url","")); + org.json.JSONObject media = m.optJSONObject("media"); + if (media != null) { + if (!hasVal(fileUrl)) fileUrl = nz(media.optString("url","")); + if (!hasVal(thumbUrl)) thumbUrl = nz(media.optString("thumbnail_url","")); + // اگر width/height لازم شد، از media.optInt("width"), media.optInt("height") بخوان + } + + // 8) فوروارد/ریپلای/ادیت/ری‌اکشن String fwdFrom = str(m,"forwarded_from"); String fwdBy = str(m,"forwarded_by"); String replyTo = str(m,"reply_to_id"); boolean edited = bool(m,"is_edited"); - JSONArray reacts = arr(m,"reactions"); + org.json.JSONArray reacts = arr(m,"reactions"); - LocalDateTime ts = parseWhen(whenIso); - if (ts == null) ts = LocalDateTime.now(); - - // اندیس پیام برای ریپلای/ادیت - msgIndex.put(msgId, m); - - // تشخیص خروجی/ورودی + // 9) outgoing String myId = (Session.currentUser != null && Session.currentUser.has("internal_uuid")) ? Session.currentUser.getString("internal_uuid") : ""; boolean outgoing = hasVal(senderId) && senderId.equalsIgnoreCase(myId); - // آپدیت لیست چت‌ها (پریویوِ کوتاه) - String previewText = switch (type.toUpperCase()) { + // 10) ایندکس برای ریپلای/ادیت‌های بعدی + msgIndex.put(msgId, m); + + // 11) آپدیت پریویو لیست چت‌ها + String previewText = switch (type) { case "IMAGE" -> (hasVal(content) ? "🖼️ Photo — " + content : "🖼️ Photo"); case "AUDIO" -> "🎵 Audio"; - default -> content; + default -> content; }; updateChatListPreview(chatId, chatType, !outgoing, previewText, type); + // 12) اگر چت جاری است، حباب بساز if (!isCurrent) return; - // 👇 امضای جدیدِ addBubble (با fileUrl/thumbUrl) addBubble(outgoing, senderName, type, content, ts, msgId, - fwdFrom, fwdBy, replyTo, edited, reacts, fileUrl, thumbUrl); + fwdFrom, fwdBy, replyTo, edited, reacts, + fileUrl, thumbUrl); if (currentChat != null) markAsRead(currentChat); @@ -2294,6 +2423,7 @@ public class ChatPageController { } + private void updateChatListPreview(UUID chatId, String type, boolean incoming, String content, String messageType) { var mc = MainController.getInstance(); if (mc == null) return; @@ -3523,4 +3653,23 @@ public class ChatPageController { } } + + public void updatePendingStatus(String messageId, String text) { + HBox node = pendingById.get(messageId); + if (node == null) return; + // پیدا کردن لیبل وضعیت + if (node.getChildren().size() >= 2 && node.getChildren().get(1) instanceof VBox v) { + for (Node n : v.getChildren()) { + if (n instanceof HBox row) { + for (Node c : row.getChildren()) { + if (c instanceof Label l && "statusLabel".equals(l.getProperties().get("role"))) { + l.setText(text); return; + } + } + } + } + } + } + + } \ No newline at end of file diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css index bc3a46b..3a71f2b 100644 --- a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css +++ b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css @@ -77,4 +77,20 @@ /* نسخه‌ی قرمز برای UNBLOCK */ .button.footer-link-btn.danger { -fx-text-fill: #D32F2F; -} \ No newline at end of file +} + + +/* فونت‌های ایموجی روی همهٔ سیستم‌ها */ +.emoji-label { + -fx-font-family: "Segoe UI Emoji", "Segoe UI Symbol", + "Apple Color Emoji", "Noto Color Emoji", + "Arial Unicode MS"; + -fx-font-size: 13px; +} + +/* اگر بخواهی فقط داخل صفحهٔ چت اعمال شود */ +.chat-root .emoji-label { + -fx-font-family: "Segoe UI Emoji", "Segoe UI Symbol", + "Apple Color Emoji", "Noto Color Emoji", + "Arial Unicode MS"; +} diff --git a/uploads/images/2025-09-07/242a76f9-f540-4d3a-bd0a-6507099b164e.jpeg b/uploads/images/2025-09-07/242a76f9-f540-4d3a-bd0a-6507099b164e.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9515e2c4c9119804848acba0ac8f1552c9e67784 GIT binary patch literal 3860 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 R=KoOiKid9(Ey3+~{{mJc+nN9X literal 0 HcmV?d00001 diff --git a/uploads/images/2025-09-07/7b6d3072-1c72-40d8-bee7-d6dbfda54dd2.jpg b/uploads/images/2025-09-07/7b6d3072-1c72-40d8-bee7-d6dbfda54dd2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ba8aaef67363acd01fbc4554c8e58250640b41f GIT binary patch literal 3237 zcmYLK2|U!>7r(#X?2FlKV=2tonj|7yma()bX)HZU(S(XYSt=zoM3#_BN%7v0-BXBO zNwSB~hNdJ#com7VR8Raz^?&bse!qLqz4!diIiK@A_w!p4F7<;JfJC8iXxuXJSR58l zAmGI>5l;{cQ49%*<&j7xk;OV$3<(mIN+nS#6sn|Hs1gtojmG1|*2|v}i3Bo*Mx&5v z6tNYBwvwSpr7AL3%JSqT<$1F5N@TKXuV8stwEaF+L z<1ov>;c?V_+-=05l9CVZ{FrAsB%~p>^2GfI_87$w*3bf~Ww3VGJ6DK>o6V098d7)?r&Z z>}$aRi-OD_TTXS{C<{{{W2K^xhCpg0+Jkg)q9GcOzf z%W2K1ukTGJ>he1(>tZ_ru%5p9>SIW4LT}Wmk&|2Z0iYXowC`%*3^`HpkK9NA)R zmfO<5^|&}r@&UTIz4ja4Yg?EAATGaCCJnLx7?FRBxIQ5Qq%~N`GygUp7VFSFsF_CFx|)f%09f&+Z>`SOz2(FIktj02K=#h6{uuzIo*i~g zo3EacV*@m16(8JrJm0~G6k4a)=<#PIK;~HO7T}V1hzoV=D+BGhb{z!yv^#+A%u)~= z#{@w3M3)Go(#Hm?pBn_>r-bzYvtByj`)@{{9qL+aOzZy+Y?n?HJsQa+$J z#5_tNA7{4epgQ?2pWrdJ7Y7v*DKlM9KJs}4%!gD}k02N#w@TDM0`GqODF1Vk>^h*U z`NJ_{*=OeR;9_p(=9)$r@PuFcWUTrG-@8GeZGEH!&cC&Oq%#GdJVFYNg~P%~feG-u zO$XyJ1=YSmSWyM+y7n~l&q5!MUX|*t<`}L?kYIszHQU)OV?7G!w@`Pv(l3%B7%Txi z6?*38a?2>Qj#aG2P0C(XRdALpO;&C6^QV^iy>z_%dsdQfk6dbc;}Nw3L;SZ2H)rei zG1Teq8MaSW-nr;-AVIb&lkuVIZ>hOa3AOY~+p#llVICU9lM>4MA4qn(&($xIEHr72af7iH@ ztD3_-2Xz0+h_9b3hqrHI_uB?+YEBxKkMSc|w;wW(`1)z1+za8>cV>|qda?0|mA+fr z%ubv~hpl^P+qREX8uB8LcKn&6?wsNEo#t_SCpY+)Nu^r`{H8WzUGnscb+M$MQ`duE zY>I2KNMAEJve$zBTS>U-uE)A7N_|X@#;n@AR>@9vBtUh6u2Uik5(W=rH=O6ahWU&(=Xe4!mnq%D=w_CVKz7x z*-1a0KBtYJ{!(X-l=1ys`)8_ZuYKqQs}noykIfF9A^bMb%H3`xjDl(9wW4cZyEjk! zyvpes4<Aj=d$JVg{OFjx_%DA?E`g`ngP-8fjv6#) zt)M-Hvzl4>Pm%8X<5rL9IoIWcZ|v@}xMuSt7yVbsj@hU(&9A;YcCB8ktKqjmmX@hp zFVkAuX4%+I(UCr6oZ{`^Ok!s@Lj&+c64V4G2-C76StlnNRQqZbHJPxZdB-vxD@lLa*t zyQu*oC7LTcWf!I9iA)Dq)~W^7yVUMDQ<_z2W%4H}7?upX(RWiz!#J~i^jnL!msgl; z7WT6C6+%<*q^0)(-4*W#J^CI%oq;F+WfE|lhQf(@tjB@I-2Gxsktb&}UDD*oF8 zVgteH<~-Kz1QT58*s!{&{j$j#k(e$P({!Ff}wlO<8H=BdBrh8eAJ*y#d z!4@neQYXW$_7tQ|8gz;otlI0#Y}%KS z_`Wl)%JP^=+;?obAHMTesOSdmxE3Z!meR-Ko@VS+t+z>M>W;_C%})PF9TImEWf2b{BGc&CIUeh9FOjR$fs^GksI~R0$ray} zHpPm~Eo_l(l47I!wj++)oR43^O=-50#`E3?1a@9;b>VaPtCp9qhrL>FFBBA5dS-of zeIb%+!L(d-5WLSPyH+7GRS58WZ&ruMby*r#a>g|7?HefV2ayYnNkztR@ zHqD3Xk*aBs-tZPHM?SGv{9y1)(RYWBQPrw(kU8s-{l{G(3=}l|0&^r)OJO70Kf-eoy?Y8 zq}~SMo+lh!LE}VwntZOgiCz2#z0pClx;M2N)=czSB`dj*Y_{jCdnvMx6Uc^#O?_rE zZhVd6%)zgIyG4ZCM#T!3j-U?r$3|T3!B3YcHpOC4Y2j|C5|shN4(!0_LLaB4{+V19 zghl4U*ex*+&c_Ti#l4bRB`d&OA6GQ3&*Wpz#VPANr#bbDQ8B4`Ror-`-`7J?hey7zjWjgRqQY%Bfe1 zaMay#NO8Lze>jmis+!ea-aLc7AgrzEwlB(;a< zT`gZIX`;E(uk#TPSOm6Zc8C`0(D- zf>YI5>u*=vUP+m4K18vS9-!$Wlq-BTJ|h zTHbh7N`)v(d`1e9omY!|XGXp6@B8Ol?sNXQ&UHV}{d>;!T=%`yxzrC}QzNz!fFKB% zAP-pj4CvH*sU z79)fe!eFsNSYctT2oA>KL`89gl@em`YJ!Z+YJ#*hQ9*@7lv9?MmL{u{mDj0K)u^&0 z4Vngprb3}o_(dRLVPTvIP6~&Uq7bEtl>a-H>H%H^jDayUqy$iS2#tr9{s3|S0w^@{ zpMM8R2#vu)!boy8BEY|;06;PVh(@q$t4*~RLsi1 zmJ#eE*xp;bst$m}%Cnu%V+9n%KH+G}YXLw-p}x~cKmldN?Mc(QB{D52!*MzJN91J| zpVHD^YZ-sSurBZ@c9{Zrl@F&AH6}ZXT|ax;bPZmut4A9mFY9doWFBj`=n^*1S>_#BYcN{YKt`3Hio-XA(DE#N?9<@MVi zMftj`!h9*wXbhs{azH3Fz%cQWG$AQ6K~YH?)-v}-q!vLg01`qi0h7M_%`V)sQD(%K zjGgfYjy`4Ao?SSZYEo>rQTNQVQ8wMR{q`6qVK$~Fj!{S7P{{o)HJN0ZE$3lm>mV!V zLh>6@Q?D$!g@3vEaza9eo$j7#UgY;z#<$0>M>)uHst(=hi9KCWNZeJQZ_`k_fBoK$ z$*1l5X!5Od;bl4MwmWpO|tA&S3t0q)!727M+nFPW~(*xxs|DMFwAB5PIlx})NDQL%PP z-C>Dm-QDBm8Do7DZk&l+r1SRuja!N8pH_kep3ztTGs77}w{S zd{N72j7&vWMG=Yj#Up>@#hvNv;ngm3+L0d%ak;zQ|H3Tg5lH z+;t7(hxMTQpH>awnHB`e_J5qMihUd(_vG^V#EQIHKbf3tvJd!dMC&!;IRyKJB6?9_ z#H`5tKy3X>FFa7BX)#$A&OYHVIq%$~)+uxE%3r=VQ2g<9q-66{^N|MHmbVk0YuCIg zP0G596LLLnwK6rhbx)JBZ_`5CbUCgmbJ8k!Vl;!*sPSNw!7T`IS*e(Lcl*K4fy>s| zk(|wR@%Zp<&GIkwhWou9Q1@m`)ClWu4KVJ|X(?5qWN(|9Svn~fw zsGUlY8xk8c50%C-!aa(bC%qr)4HtHc)~}~YX?x%sgV@}GIA;H59ZPj`J;TD68@e@S z>Xr}ZW>kB$PL|hUoW+M9w<5oY)2A5yl*lrXTz$tkN2e^dcMbKT+{(E#Tplb{rnZ>x zbUb@e!J9L;@RbFJs`u!>dfq)(yFXU$VVUyANA`uUIVt*YTBZxigSr?=RJZ9{2WD;+ zyXIDthNUCblfnjSf-NnJ9qhA08b$5pUHmUU(P>V-JFl5vs1xZ!BoP!URrAAIxvC8I zf(B`SMnkH9Yv;6cP3=WQVZo3F{EI;5^#2qzaP{Z9vF*`?KcN_{fxr}C2rWi?du5qs zHi&60vjDU-C@hlzut3UB2uUUob}wUDxIv8ep3MJ{MXDh$f*>*?j<^a1+6 z^gF!V??@9O@ylXJEr6$KkriQr!pWNo|arlSqhs?8LN*Ur3srISwmt@OtVZTokHY~!Nf7B&;I zPu1=q-`VGu71r6gBV0v_e299RgGHrurLWDPuG8=%vihn{435V}^*+>TvUanvollH< zyu*3Rrg8}jd*GrOKU*ARo#lBjcFWCD%fs>!$4zjmdM{+SH-(K;_d2TUatlm~ZEeG; zn}#oO3ONM|kvDdU6t@_?d2J&bsU2uPbJNPkc0_{Pz#dGAQSAN7!`jt9)n6xh<@te= z$5X2JHMLRnbTJr+*W|CU#^#52W^*2c%DQ{mq-m%4t63i1qs@pQYQtVv znZYj6eZctF4-!Y{rmtPTi7!ZU%vbr@)%JFL@bPx=Em`qWEZ}sb&9?)e+D9Tn zR5_0C`&cSUWhcxSZ8j0U9^cJ9mFsX1Hj?H7>ew1VOCW=34P9@f#}kPw+OJ$2zIQtP zJM+73dHem6bve7YnW@KaP%F6+5t5vp!*#oJVFE*)i#LBTTsnV>UU#zUYuzgDeDtLl z@qs<}gSJs{?^P8KGAnNeq+Km=xaZKt2(3+2xCi`W5Qr-)ZGurf%-tUjBnP~U*ZOnNnQN$aoZK(7bQav>s)AK(1 R!^M)OxF$!25Ri}-5Z>kS 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 literal 0 HcmV?d00001 diff --git a/uploads/images/2025-09-07/dcb9b568-abea-43f4-bc39-acd71dc2438c.jpg b/uploads/images/2025-09-07/dcb9b568-abea-43f4-bc39-acd71dc2438c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b0ee46ff34513969f079f249cfc9eb57ef5c8ef9 GIT binary patch literal 4467 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 z zQoorjEk4LPE;lQ7Y1zoW(VfrKk}4dVJG9JrM`$rD30L)|TEqSlqAsRv#YMp))IP%O fO^-W+U~dm!nIJY(KU-^~-p8c&xo}SN(fI!Ww=$;S literal 0 HcmV?d00001 diff --git a/uploads/images/2025-09-07/e85885e6-4502-45fe-a2fe-71dd1f8595ba.jpg b/uploads/images/2025-09-07/e85885e6-4502-45fe-a2fe-71dd1f8595ba.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05084e6f9ba909f30ddadac9c3ccffe6f0a878e1 GIT binary patch literal 7851 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 literal 0 HcmV?d00001 diff --git a/uploads/images/2025-09-07/f87ee3b0-0a90-4c54-be85-a91f04d517df.jpg b/uploads/images/2025-09-07/f87ee3b0-0a90-4c54-be85-a91f04d517df.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6436ef050eae9fd98b8edf56245767a35fe5c20e GIT binary patch literal 3627 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 literal 0 HcmV?d00001