Profile logic for UI
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package org.to.telegramfinalproject.Client;
|
package org.to.telegramfinalproject.Client;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
|
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.SearchRequestModel;
|
||||||
import org.to.telegramfinalproject.Models.SearchResultModel;
|
import org.to.telegramfinalproject.Models.SearchResultModel;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.DataOutputStream;
|
import java.nio.file.Files;
|
||||||
import java.io.IOException;
|
import java.nio.file.Path;
|
||||||
import java.io.PrintWriter;
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
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 {
|
try {
|
||||||
System.out.println("👂 Real-Time Listener started.");
|
System.out.println("👂 Real-Time Listener started.");
|
||||||
|
|
||||||
String line;
|
while (true) {
|
||||||
while ((line = in.readLine()) != null) {
|
|
||||||
|
|
||||||
if (TelegramClient.mediaBusy.get()) {
|
if (TelegramClient.mediaBusy.get()) {
|
||||||
try { Thread.sleep(15); } catch (InterruptedException ignored) {}
|
try { Thread.sleep(15); } catch (InterruptedException ignored) {}
|
||||||
continue;
|
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);
|
System.out.println("📥 Received raw line: " + line);
|
||||||
|
|
||||||
//if it has reqID answer
|
// 1) اول message_id را روت کن (برای ACK نهایی مدیا)
|
||||||
String mid = response.optString("message_id", "");
|
String mid = response.optString("message_id", "");
|
||||||
if (!mid.isEmpty()) {
|
if (!mid.isEmpty()) {
|
||||||
BlockingQueue<JSONObject> q = TelegramClient.pendingResponses.get(mid);
|
BlockingQueue<JSONObject> q = TelegramClient.pendingResponses.get(mid);
|
||||||
if (q != null) {
|
if (q != null) {
|
||||||
q.put(response);
|
q.put(response);
|
||||||
continue; // این پیام مصرف شد
|
continue; // مصرف شد
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) بعد request_id را روت کن (برای INIT و بقیه درخواستها)
|
||||||
if (response.has("request_id")) {
|
if (response.has("request_id")) {
|
||||||
String requestId = response.getString("request_id");
|
String requestId = response.getString("request_id");
|
||||||
System.out.println("📬 Response with request_id: " + requestId);
|
System.out.println("📬 Response with request_id: " + requestId);
|
||||||
System.out.println("📬 Full response: " + response.toString(2));
|
System.out.println("📬 Full response: " + response.toString(2));
|
||||||
|
|
||||||
|
|
||||||
BlockingQueue<JSONObject> queue = TelegramClient.pendingResponses.get(requestId);
|
BlockingQueue<JSONObject> queue = TelegramClient.pendingResponses.get(requestId);
|
||||||
if (queue != null) {
|
if (queue != null) {
|
||||||
queue.put(response);
|
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...");
|
System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue...");
|
||||||
TelegramClient.responseQueue.put(response);
|
TelegramClient.responseQueue.put(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3) رویدادهای real-time
|
||||||
|
|
||||||
//if it has action check it
|
|
||||||
if (response.has("action")) {
|
if (response.has("action")) {
|
||||||
String action = response.getString("action");
|
String action = response.getString("action");
|
||||||
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
|
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
|
||||||
@@ -88,9 +94,12 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
} else {
|
} else {
|
||||||
TelegramClient.responseQueue.put(response);
|
TelegramClient.responseQueue.put(response);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (response.has("status") && response.has("message")) {
|
// 4) سایر پاسخهای عمومی
|
||||||
TelegramClient.responseQueue.put(response); // general answer
|
if (response.has("status") && response.has("message")) {
|
||||||
|
TelegramClient.responseQueue.put(response);
|
||||||
} else {
|
} else {
|
||||||
TelegramClient.responseQueue.put(response); // fallback
|
TelegramClient.responseQueue.put(response); // fallback
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,9 +222,6 @@ public class TelegramClient {
|
|||||||
if (socket != null && socket.isConnected() && !socket.isClosed()) return;
|
if (socket != null && socket.isConnected() && !socket.isClosed()) return;
|
||||||
|
|
||||||
socket = new Socket(SERVER_HOST, SERVER_PORT);
|
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();
|
InputStream rawIn = socket.getInputStream();
|
||||||
OutputStream rawOut = socket.getOutputStream();
|
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) {
|
private void startListenerOnce(IncomingMessageListener.UIMode mode) {
|
||||||
if (listenerStarted) return;
|
if (listenerStarted) return;
|
||||||
listenerStarted = true;
|
listenerStarted = true;
|
||||||
|
|
||||||
Thread listenerThread = new Thread(
|
listener = new IncomingMessageListener(in, mode);
|
||||||
new IncomingMessageListener(in, mode),
|
Thread listenerThread = new Thread(listener, "socket-listener");
|
||||||
"socket-listener"
|
|
||||||
);
|
|
||||||
listenerThread.setDaemon(true);
|
listenerThread.setDaemon(true);
|
||||||
listenerThread.start();
|
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) {
|
// 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 (?, ?, ?, ?, ?, ?)";
|
// 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.setObject(1, internalUUID);
|
||||||
stmt.setString(2, channelId);
|
stmt.setString(2, channelId);
|
||||||
stmt.setString(3, channelName);
|
stmt.setString(3, channelName);
|
||||||
stmt.setObject(4, creatorId);
|
stmt.setObject(4, creatorId);
|
||||||
stmt.setString(5, imageUrl);
|
|
||||||
stmt.setObject(6, createdAt);
|
if (imageUrl != null && !imageUrl.isBlank()) {
|
||||||
|
stmt.setString(5, imageUrl);
|
||||||
|
} 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();
|
stmt.executeUpdate();
|
||||||
return true;
|
return true;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ChannelService {
|
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();
|
UUID internalUUID = UUID.randomUUID();
|
||||||
LocalDateTime now = LocalDateTime.now();
|
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) {
|
if (inserted) {
|
||||||
ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner");
|
ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner");
|
||||||
|
|||||||
@@ -970,8 +970,8 @@ public class ClientHandler implements Runnable {
|
|||||||
UUID creatorUUID = UUID.fromString(userIdStr);
|
UUID creatorUUID = UUID.fromString(userIdStr);
|
||||||
|
|
||||||
// اگر سرویسات ورودی توضیح را میپذیرد، از متد اورلودشده استفاده کن:
|
// اگر سرویسات ورودی توضیح را میپذیرد، از متد اورلودشده استفاده کن:
|
||||||
// boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl, description);
|
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);
|
||||||
|
|
||||||
if (created) {
|
if (created) {
|
||||||
Channel createdChannel = ChannelDatabase.findByChannelId(channelId);
|
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 String err(String m) { return new JSONObject().put("status","error").put("message",m).toString(); }
|
||||||
|
|
||||||
private static boolean isAllowedImageMime(String mime) {
|
private static boolean isAllowedImageMime(String mime) {
|
||||||
@@ -3344,8 +3348,8 @@ public class ClientHandler implements Runnable {
|
|||||||
private boolean updateProfileImageUrl(String targetType, UUID targetId, String url) {
|
private boolean updateProfileImageUrl(String targetType, UUID targetId, String url) {
|
||||||
String sql = switch (targetType) {
|
String sql = switch (targetType) {
|
||||||
case "user" -> "UPDATE users SET image_url=? WHERE internal_uuid=?";
|
case "user" -> "UPDATE users SET image_url=? WHERE internal_uuid=?";
|
||||||
case "channel" -> "UPDATE channels SET image_url=? WHERE id=?";
|
case "channel" -> "UPDATE channels SET image_url=? WHERE internal_uuid=?";
|
||||||
case "group" -> "UPDATE groups SET image_url=? WHERE id=?";
|
case "group" -> "UPDATE groups SET image_url=? WHERE internal_uuid=?";
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
if (sql == null) return false;
|
if (sql == null) return false;
|
||||||
|
|||||||
@@ -117,4 +117,28 @@ public class ChatItemController {
|
|||||||
unreadCount.setManaged(show);
|
unreadCount.setManaged(show);
|
||||||
if (show) unreadCount.setText(String.valueOf(unread));
|
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.time.temporal.ChronoUnit;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.to.telegramfinalproject.Client.Session.currentChatId;
|
||||||
|
|
||||||
public class ChatPageController {
|
public class ChatPageController {
|
||||||
|
|
||||||
// ===== messages area =====
|
// ===== 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 {
|
private static final class ForwardTarget {
|
||||||
|
|||||||
@@ -437,6 +437,9 @@ public class MainController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ChatPageController controller = loader.getController();
|
ChatPageController controller = loader.getController();
|
||||||
controller.showChat(chat);
|
controller.showChat(chat);
|
||||||
|
|
||||||
|
|||||||
@@ -103,12 +103,12 @@ public class NewChannelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCreateChannel() {
|
private void onCreateChannel() {
|
||||||
String name = nz(channelNameField.getText()).trim();
|
String name = nz(channelNameField.getText()).trim();
|
||||||
String dispId = nz(channelIdField.getText()).trim();
|
String dispId = nz(channelIdField.getText()).trim();
|
||||||
String description = nz(channelDescField.getText()).trim();
|
String description = nz(channelDescField.getText()).trim();
|
||||||
|
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
if (name.isEmpty()) { channelNameField.getStyleClass().add("error"); channelNameLabel.getStyleClass().add("error"); ok = false; }
|
if (name.isEmpty()) { channelNameField.getStyleClass().add("error"); channelNameLabel.getStyleClass().add("error"); ok = false; }
|
||||||
if (dispId.isEmpty()) { channelIdField.getStyleClass().add("error"); channelIdLabel.getStyleClass().add("error"); ok = false; }
|
if (dispId.isEmpty()) { channelIdField.getStyleClass().add("error"); channelIdLabel.getStyleClass().add("error"); ok = false; }
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
@@ -118,70 +118,93 @@ public class NewChannelController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: اگر آپلود تصویر داری، فایل را آپلود کن و URL نهایی را اینجا بفرست
|
// در مرحله ساخت، image_url را خالی بفرست؛ بعداً آپلود میکنیم
|
||||||
final String imageUrl = null;
|
|
||||||
|
|
||||||
JSONObject req = new JSONObject()
|
JSONObject req = new JSONObject()
|
||||||
.put("action", "create_channel")
|
.put("action", "create_channel")
|
||||||
.put("channel_id", dispId) // آیدی نمایشی/عمومی
|
.put("channel_id", dispId)
|
||||||
.put("channel_name", name)
|
.put("channel_name", name)
|
||||||
.put("user_id", me) // internal_uuid سازنده
|
.put("user_id", me)
|
||||||
.put("image_url", imageUrl) // اختیاری
|
.put("image_url", (Object) null)
|
||||||
.put("description", description); // اختیاری (سرور اگر ساپورت نکند نادیده میگیرد)
|
.put("description", description);
|
||||||
|
|
||||||
|
createButton.setDisable(true);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
try {
|
||||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
Platform.runLater(() -> showToast("Create failed: " +
|
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||||
(resp == null ? "no response" : resp.optString("message",""))));
|
Platform.runLater(() -> {
|
||||||
return;
|
createButton.setDisable(false);
|
||||||
}
|
showToast("Create failed: " + (resp == null ? "no response" : resp.optString("message","")));
|
||||||
|
});
|
||||||
JSONObject data = resp.optJSONObject("data");
|
return;
|
||||||
if (data == null) {
|
|
||||||
Platform.runLater(() -> showToast("Create failed: empty data."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID internalId = UUID.fromString(data.optString("internal_id"));
|
|
||||||
String returnedName = data.optString("name", name);
|
|
||||||
String returnedDisp = data.optString("id", dispId);
|
|
||||||
String returnedImg = data.optString("image_url", "");
|
|
||||||
|
|
||||||
// 1) چت کانال تازهساخته را به سایدبار اضافه کن و انتخاب کن
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
ChatEntry entry = ChatEntry.fromServer(
|
|
||||||
internalId,
|
|
||||||
"channel",
|
|
||||||
returnedName,
|
|
||||||
returnedDisp,
|
|
||||||
returnedImg,
|
|
||||||
/*isOwner*/ true,
|
|
||||||
/*isAdmin*/ true
|
|
||||||
);
|
|
||||||
MainController.getInstance().addChatAndSelect(entry);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2) اُورلی افزودن سابسکرایبر را باز کن و internal_id را پاس بده
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
MainController.getInstance().showOverlay(addSubsOverlay);
|
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
showToast("Failed to open Add Subscribers.");
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
JSONObject data = resp.optJSONObject("data");
|
||||||
|
if (data == null) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
createButton.setDisable(false);
|
||||||
|
showToast("Create failed: empty data.");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID internalId = UUID.fromString(data.optString("internal_id"));
|
||||||
|
String returnedName = data.optString("name", name);
|
||||||
|
String returnedDisp = data.optString("id", dispId);
|
||||||
|
String returnedImg = data.optString("image_url", "");
|
||||||
|
|
||||||
|
// ✅ اگر کاربر عکس انتخاب کرده بود: الان آپلود کن (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, finalImg,
|
||||||
|
/*isOwner*/ true, /*isAdmin*/ true
|
||||||
|
);
|
||||||
|
MainController.getInstance().addChatAndSelect(entry);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
MainController.getInstance().showOverlay(addSubsOverlay);
|
||||||
|
MainController.getInstance().closeOverlay(overlayRoot);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
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();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void showToast(String msg) {
|
private void showToast(String msg) {
|
||||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
a.initOwner(overlayRoot.getScene().getWindow());
|
a.initOwner(overlayRoot.getScene().getWindow());
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ import javafx.scene.layout.Pane;
|
|||||||
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.Session;
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.to.telegramfinalproject.Client.TelegramClient;
|
||||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
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 {
|
public class NewGroupController {
|
||||||
|
|
||||||
@@ -83,34 +87,25 @@ public class NewGroupController {
|
|||||||
String groupId = groupIdField.getText() == null ? "" : groupIdField.getText().trim();
|
String groupId = groupIdField.getText() == null ? "" : groupIdField.getText().trim();
|
||||||
|
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
if (groupName.isEmpty()) {
|
if (groupName.isEmpty()) { groupNameField.getStyleClass().add("error"); groupNameLabel.getStyleClass().add("error"); ok = false; }
|
||||||
groupNameField.getStyleClass().add("error");
|
if (groupId.isEmpty()) { groupIdField.getStyleClass().add("error"); groupIdLabel.getStyleClass().add("error"); ok = false; }
|
||||||
groupNameLabel.getStyleClass().add("error");
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
if (groupId.isEmpty()) {
|
|
||||||
groupIdField.getStyleClass().add("error");
|
|
||||||
groupIdLabel.getStyleClass().add("error");
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
// ساخت گروه روی سرور
|
final String me = Session.getUserUUID();
|
||||||
final String me = Session.getUserUUID(); // internal_uuid
|
|
||||||
if (me == null || me.isBlank()) {
|
if (me == null || me.isBlank()) {
|
||||||
showToast("Cannot create group: current user UUID missing.");
|
showToast("Cannot create group: current user UUID missing.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// اگر آپلود تصویر داری، اینجا عکس رو آپلود کن و imageUrl واقعی بفرست (TODO)
|
// در مرحلهٔ ساخت، image_url را خالی بفرست (بعداً آپلود میکنیم)
|
||||||
final String imageUrl = null;
|
final String imageUrl = null;
|
||||||
|
|
||||||
JSONObject req = new JSONObject()
|
JSONObject req = new JSONObject()
|
||||||
.put("action", "create_group")
|
.put("action", "create_group")
|
||||||
.put("group_id", groupId)
|
.put("group_id", groupId)
|
||||||
.put("group_name", groupName)
|
.put("group_name", groupName)
|
||||||
.put("user_id", me) // ← internal_uuid سازنده
|
.put("user_id", me)
|
||||||
.put("image_url", imageUrl); // ← اختیاری
|
.put("image_url", imageUrl);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||||
@@ -131,21 +126,37 @@ public class NewGroupController {
|
|||||||
String returnedDisp = data.optString("id", groupId);
|
String returnedDisp = data.optString("id", groupId);
|
||||||
String returnedImg = data.optString("image_url", "");
|
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(() -> {
|
Platform.runLater(() -> {
|
||||||
ChatEntry entry = ChatEntry.fromServer(
|
ChatEntry entry = ChatEntry.fromServer(
|
||||||
internalId,
|
internalId,
|
||||||
"group",
|
"group",
|
||||||
returnedName,
|
returnedName,
|
||||||
returnedDisp,
|
returnedDisp,
|
||||||
returnedImg,
|
imageUrlToUse,
|
||||||
/*isOwner*/ true,
|
/*isOwner*/ true,
|
||||||
/*isAdmin*/ true
|
/*isAdmin*/ true
|
||||||
);
|
);
|
||||||
MainController.getInstance().addChatAndSelect(entry);
|
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(() -> {
|
Platform.runLater(() -> {
|
||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||||
@@ -154,10 +165,7 @@ public class NewGroupController {
|
|||||||
AddMembersController controller = loader.getController();
|
AddMembersController controller = loader.getController();
|
||||||
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
|
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
|
||||||
|
|
||||||
// این اُورلی را روی UI نشان بده
|
|
||||||
MainController.getInstance().showOverlay(addMembersOverlay);
|
MainController.getInstance().showOverlay(addMembersOverlay);
|
||||||
|
|
||||||
// این اُورلی NewGroup را ببند
|
|
||||||
MainController.getInstance().closeOverlay(overlayRoot);
|
MainController.getInstance().closeOverlay(overlayRoot);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
@@ -167,10 +175,14 @@ public class NewGroupController {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void showToast(String msg) {
|
private void showToast(String msg) {
|
||||||
// جایگزینش کن با سیستم نوتی شما
|
// جایگزینش کن با سیستم نوتی شما
|
||||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||||
a.initOwner(overlayRoot.getScene().getWindow());
|
a.initOwner(overlayRoot.getScene().getWindow());
|
||||||
a.show();
|
a.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user