Generated
+1
@@ -0,0 +1 @@
|
||||
Telegram-Final-Project
|
||||
Generated
+1
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
Generated
+3
@@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
|
||||
+8
-1
@@ -26,7 +26,7 @@ tasks.withType(JavaCompile) {
|
||||
|
||||
application {
|
||||
mainModule = 'org.to.telegramfinalproject'
|
||||
mainClass = 'org.to.telegramfinalproject.HelloApplication'
|
||||
mainClass = 'org.to.telegramfinalproject.Server.MainServer'
|
||||
}
|
||||
|
||||
javafx {
|
||||
@@ -40,13 +40,20 @@ dependencies {
|
||||
implementation('com.dlsc.formsfx:formsfx-core:11.6.0') {
|
||||
exclude group: 'org.openjfx'
|
||||
}
|
||||
implementation 'org.postgresql:postgresql:42.7.3'
|
||||
implementation('net.synedra:validatorfx:0.5.0') {
|
||||
exclude group: 'org.openjfx'
|
||||
}
|
||||
//For upload files
|
||||
implementation 'org.slf4j:slf4j-simple:2.0.13'
|
||||
implementation 'com.sparkjava:spark-core:2.9.4'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1' //for mp3
|
||||
implementation 'org.json:json:20231013'
|
||||
implementation 'org.kordamp.ikonli:ikonli-javafx:12.3.1'
|
||||
implementation 'org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0'
|
||||
implementation('eu.hansolo:tilesfx:21.0.3') {
|
||||
exclude group: 'org.openjfx'
|
||||
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
module org.to.telegramfinalproject {
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires javafx.graphics; // بهتره اضافه باشه
|
||||
requires javafx.media;
|
||||
requires javafx.web;
|
||||
|
||||
requires org.json;
|
||||
requires java.sql;
|
||||
requires java.desktop;
|
||||
|
||||
// لایبرریهای جانبی — اگر واقعاً در مسیر ماژول هستند
|
||||
requires org.controlsfx.controls;
|
||||
requires com.dlsc.formsfx;
|
||||
requires net.synedra.validatorfx;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires org.kordamp.bootstrapfx.core;
|
||||
requires eu.hansolo.tilesfx;
|
||||
opens org.to.telegramfinalproject to javafx.fxml;
|
||||
exports org.to.telegramfinalproject;
|
||||
}
|
||||
requires mp3agic;
|
||||
requires spark.core; // فقط اگر واقعاً استفاده میکنی
|
||||
requires javax.servlet.api; // فقط اگر واقعاً استفاده میکنی
|
||||
|
||||
// کنترلرها/کلاسهایی که FXML به آنها دسترسی بازتابی دارد
|
||||
opens org.to.telegramfinalproject.UI to javafx.fxml;
|
||||
opens org.to.telegramfinalproject.Client to javafx.fxml;
|
||||
opens org.to.telegramfinalproject.Models to javafx.fxml; // اگر مدلها داخل FXML مصرف میشوند
|
||||
|
||||
// اگر پکیجهایی را میخواهی خارج از ماژول در دسترس قرار دهی
|
||||
exports org.to.telegramfinalproject.UI;
|
||||
exports org.to.telegramfinalproject.Client;
|
||||
exports org.to.telegramfinalproject.Models;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import java.nio.file.*;
|
||||
|
||||
public final class AvatarLocalResolver {
|
||||
private static final Path UPLOADS_ROOT = Paths.get(
|
||||
System.getProperty("app.uploads.root", "uploads")
|
||||
).toAbsolutePath().normalize();
|
||||
|
||||
private static boolean isHttp(String s) {
|
||||
return s.startsWith("http://") || s.startsWith("https://");
|
||||
}
|
||||
|
||||
public static String resolve(String serverValue) {
|
||||
if (serverValue == null) return null;
|
||||
serverValue = serverValue.trim();
|
||||
if (serverValue.isEmpty()) return null;
|
||||
|
||||
// URL کامل یا file: → همون رو برگردون
|
||||
if (isHttp(serverValue) || serverValue.startsWith("file:")) {
|
||||
return serverValue;
|
||||
}
|
||||
|
||||
// ⬅️ مهم: برای مسیرهای لوکال، هر چیزی بعد از ? یا # را حذف کن
|
||||
serverValue = stripQueryAndHash(serverValue);
|
||||
|
||||
// حذف اسلش ابتدایی (اگر بود)
|
||||
String rel = serverValue.startsWith("/") ? serverValue.substring(1) : serverValue;
|
||||
|
||||
// ساخت مسیر امن داخل uploads
|
||||
Path p = UPLOADS_ROOT.resolve(rel).normalize();
|
||||
|
||||
// جلوگیری از خروج از دایرکتوری uploads (path traversal)
|
||||
if (!p.startsWith(UPLOADS_ROOT)) return null;
|
||||
|
||||
// وجود واقعی فایل
|
||||
if (!Files.exists(p)) {
|
||||
System.err.println("Avatar resolve: NOT FOUND -> " + p);
|
||||
return null;
|
||||
}
|
||||
|
||||
// خروجی به صورت file:// URI که JavaFX Image میفهمد
|
||||
String url = p.toUri().toString();
|
||||
System.out.println("Avatar resolve: " + serverValue + " -> " + url);
|
||||
return url;
|
||||
}
|
||||
|
||||
private static String stripQueryAndHash(String s) {
|
||||
int q = s.indexOf('?');
|
||||
if (q >= 0) s = s.substring(0, q);
|
||||
int h = s.indexOf('#');
|
||||
if (h >= 0) s = s.substring(0, h);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
public static Image load(String serverValue) {
|
||||
String url = resolve(serverValue);
|
||||
if (url == null) return null;
|
||||
|
||||
// فقط برای http/https کشبریکر
|
||||
if (isHttp(url)) {
|
||||
url += (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
Image img = new Image(url, false); // sync load تا خطا را همانجا بفهمیم
|
||||
if (img.isError()) {
|
||||
System.err.println("Avatar load failed: " + url + " -> " + img.getException());
|
||||
return null;
|
||||
}
|
||||
return img;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
|
||||
public class ClientConnection {
|
||||
private Socket socket;
|
||||
private BufferedReader in;
|
||||
private PrintWriter out;
|
||||
|
||||
public ClientConnection(String host, int port) throws IOException {
|
||||
this.socket = new Socket(host, port);
|
||||
this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
this.out = new PrintWriter(socket.getOutputStream(), true);
|
||||
}
|
||||
|
||||
public void send(String request) {
|
||||
out.println(request);
|
||||
}
|
||||
|
||||
public String receive() throws IOException {
|
||||
return in.readLine();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
in.close();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// DownloadIndexRegistry.java
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class DownloadIndexRegistry {
|
||||
private static final ConcurrentHashMap<UUID, DownloadsIndex> INSTANCES = new ConcurrentHashMap<>();
|
||||
private static volatile boolean HOOK_REGISTERED = false;
|
||||
|
||||
private DownloadIndexRegistry() {}
|
||||
|
||||
public static DownloadsIndex forAccount(UUID accountId) {
|
||||
registerHookOnce();
|
||||
return INSTANCES.computeIfAbsent(accountId, DownloadsIndex::new);
|
||||
}
|
||||
|
||||
public static void closeAccount(UUID accountId) {
|
||||
DownloadsIndex idx = INSTANCES.remove(accountId);
|
||||
if (idx != null) idx.saveQuietly();
|
||||
}
|
||||
|
||||
private static synchronized void registerHookOnce() {
|
||||
if (HOOK_REGISTERED) return;
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
for (DownloadsIndex idx : INSTANCES.values()) idx.saveQuietly();
|
||||
}));
|
||||
HOOK_REGISTERED = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class DownloadsIndex {
|
||||
private final UUID accountId;
|
||||
private final Path indexFile;
|
||||
private final Map<UUID, Entry> map = new ConcurrentHashMap<>();
|
||||
|
||||
public DownloadsIndex(UUID accountId) {
|
||||
this.accountId = accountId;
|
||||
this.indexFile = resolveIndexPath(accountId.toString());
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
private static Path resolveIndexPath(String accountId) {
|
||||
String os = System.getProperty("os.name", "").toLowerCase();
|
||||
String home = System.getProperty("user.home");
|
||||
|
||||
Path dir;
|
||||
if (os.contains("win")) {
|
||||
String appData = System.getenv("APPDATA");
|
||||
dir = (appData != null)
|
||||
? Paths.get(appData, "TeleSock")
|
||||
: Paths.get(home, "AppData", "Roaming", "TeleSock");
|
||||
} else {
|
||||
dir = Paths.get(home, ".telesock");
|
||||
}
|
||||
try { Files.createDirectories(dir); } catch (IOException ignored) {}
|
||||
return dir.resolve("downloads-index-" + accountId + ".json");
|
||||
}
|
||||
|
||||
private synchronized void load() {
|
||||
map.clear();
|
||||
try {
|
||||
if (!Files.exists(indexFile)) return;
|
||||
String json = Files.readString(indexFile, StandardCharsets.UTF_8);
|
||||
if (json == null || json.isBlank()) return;
|
||||
|
||||
JSONObject root = new JSONObject(json);
|
||||
JSONObject items = root.optJSONObject("items");
|
||||
if (items == null) return;
|
||||
|
||||
for (String key : items.keySet()) {
|
||||
JSONObject e = items.getJSONObject(key);
|
||||
map.put(UUID.fromString(key), new Entry(
|
||||
e.getString("path"),
|
||||
e.optLong("size", 0L),
|
||||
e.optLong("ts", System.currentTimeMillis())
|
||||
));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("⚠️ DownloadsIndex load failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void save() throws IOException {
|
||||
JSONObject items = new JSONObject();
|
||||
for (Map.Entry<UUID, Entry> it : map.entrySet()) {
|
||||
JSONObject e = new JSONObject();
|
||||
e.put("path", it.getValue().path);
|
||||
e.put("size", it.getValue().size);
|
||||
e.put("ts", it.getValue().ts);
|
||||
items.put(it.getKey().toString(), e);
|
||||
}
|
||||
byte[] data = new JSONObject().put("items", items).toString(2).getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Path tmp = indexFile.resolveSibling(indexFile.getFileName() + ".tmp");
|
||||
Files.write(tmp, data);
|
||||
try {
|
||||
Files.move(tmp, indexFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException ex) {
|
||||
Files.move(tmp, indexFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveQuietly() {
|
||||
try { save(); } catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public Path find(UUID mediaKey) {
|
||||
Entry e = map.get(mediaKey);
|
||||
if (e == null) return null;
|
||||
Path p = Paths.get(e.path);
|
||||
if (Files.exists(p)) return p;
|
||||
map.remove(mediaKey);
|
||||
saveQuietly();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void put(UUID mediaKey, Path path, long size) {
|
||||
map.put(mediaKey, new Entry(path.toString(), size, System.currentTimeMillis()));
|
||||
saveQuietly();
|
||||
}
|
||||
|
||||
public void remove(UUID mediaKey) {
|
||||
map.remove(mediaKey);
|
||||
saveQuietly();
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
final String path; final long size; final long ts;
|
||||
Entry(String path, long size, long ts) {
|
||||
this.path = path; this.size = size; this.ts = ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
||||
//public class EventProcessorThread extends Thread {
|
||||
// private final ActionHandler handler;
|
||||
// private final BufferedReader in;
|
||||
//
|
||||
// public EventProcessorThread(ActionHandler handler, BufferedReader in) {
|
||||
// this.handler = handler;
|
||||
// this.in = in;
|
||||
// setDaemon(true);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void run() {
|
||||
// try {
|
||||
// System.out.println("👂 Real-Time Listener started.");
|
||||
// String line;
|
||||
// while ((line = in.readLine()) != null) {
|
||||
// JSONObject json = new JSONObject(line);
|
||||
// System.out.println("📥 Received raw line: " + line);
|
||||
//
|
||||
// if (json.has("action")) {
|
||||
// // پیام real-time
|
||||
// handler.processIncomingEvent(json);
|
||||
// } else {
|
||||
// // پیام پاسخ معمولی
|
||||
// TelegramClient.responseQueue.put(json);
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// System.err.println("❌ Error in EventProcessorThread: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,658 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.UI.MainController;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
public class IncomingMessageListener implements Runnable {
|
||||
private final BufferedReader in;
|
||||
public enum UIMode { CONSOLE, UI }
|
||||
private final UIMode uiMode; // runtime mode
|
||||
|
||||
private final java.util.Set<String> seenMessageIds =
|
||||
java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>());
|
||||
|
||||
|
||||
public IncomingMessageListener(BufferedReader in, UIMode uiMode) {
|
||||
this.in = in;
|
||||
this.uiMode = uiMode;
|
||||
}
|
||||
|
||||
// public IncomingMessageListener(BufferedReader in) {
|
||||
// this.in = in;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("👂 Real-Time Listener started.");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
System.out.println("📥 Received raw line: " + line);
|
||||
|
||||
// 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; // مصرف شد
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
} else {
|
||||
System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue...");
|
||||
TelegramClient.responseQueue.put(response);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) رویدادهای real-time
|
||||
if (response.has("action")) {
|
||||
String action = response.getString("action");
|
||||
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
|
||||
System.out.println("🎯 Received action: " + action);
|
||||
|
||||
if (isRealTimeEvent(action)) {
|
||||
handleRealTimeEvent(response);
|
||||
} else {
|
||||
TelegramClient.responseQueue.put(response);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4) سایر پاسخهای عمومی
|
||||
if (response.has("status") && response.has("message")) {
|
||||
TelegramClient.responseQueue.put(response);
|
||||
} else {
|
||||
TelegramClient.responseQueue.put(response); // fallback
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("🔴 [Listener] Crashed due to: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRealTimeEvent(String action) {
|
||||
return switch (action) {
|
||||
case "new_message",
|
||||
"message_edited",
|
||||
"message_deleted_global", "message_deleted_one_sided", "message_deleted",
|
||||
"message_reacted", "message_unreacted",
|
||||
"user_status_changed",
|
||||
"added_to_group", "added_to_channel",
|
||||
"update_group_or_channel", "chat_deleted",
|
||||
"blocked_by_user", "unblocked_by_user", "message_seen",
|
||||
"removed_from_group", "removed_from_channel",
|
||||
"became_admin", "removed_admin", "ownership_transferred",
|
||||
"admin_permissions_updated",
|
||||
"created_private_chat",
|
||||
"chat_updated" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void handleRealTimeEvent(JSONObject response) throws IOException {
|
||||
String action = response.getString("action");
|
||||
JSONObject msg = response.has("data") ? response.getJSONObject("data") : new JSONObject();
|
||||
|
||||
JSONObject finalMsg = msg;
|
||||
JSONObject finalMsg1 = msg;
|
||||
switch (action) {
|
||||
// case "added_to_group", "added_to_channel",
|
||||
// "removed_from_group", "removed_from_channel",
|
||||
// "chat_deleted", "created_private_chat" -> {
|
||||
// // این قسمت مستقل از UI/کنسول است
|
||||
// System.out.println("🔄 Chat list changed. Updating...");
|
||||
// Session.forceRefreshChatList = true;
|
||||
//
|
||||
// String chatId = msg.getString("chat_id");
|
||||
// String chatType = msg.getString("chat_type");
|
||||
// ActionHandler.requestChatInfo(chatId, chatType);
|
||||
//
|
||||
// if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) {
|
||||
// System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting...");
|
||||
// ActionHandler.forceExitChat = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// case "chat_updated" -> {
|
||||
// if (uiMode == UIMode.UI) {
|
||||
// bumpChatListFromUpdate(msg); // برای UI (سایدبار و سورت)
|
||||
// } else {
|
||||
// updateLastMessageTime(msg); // برای کنسول (لیستهای Session)
|
||||
// }
|
||||
// }
|
||||
|
||||
case "added_to_group":
|
||||
case "added_to_channel":
|
||||
case "created_private_chat": {
|
||||
// دادهها
|
||||
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||
String type = msg.getString("chat_type"); // "group" | "channel" | "private"
|
||||
String name = msg.optString("name", "");
|
||||
String imgUrl = msg.optString("image_url", "");
|
||||
|
||||
// یک ChatEntry مینیمال بساز (تا UI سریع واکنش بده)
|
||||
ChatEntry ce = new ChatEntry();
|
||||
ce.setId(chatId.toString());
|
||||
ce.setType(type);
|
||||
ce.setName(name);
|
||||
ce.setImageUrl(imgUrl);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
// به لیستها اضافه و UI را رفرش میکند (متد خودت)
|
||||
mc.onJoinedOrAdded(ce);
|
||||
|
||||
// اگر همین چت الان بازه، مود مناسب را اعمال کن
|
||||
var cpc = mc.getChatPageController();
|
||||
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||
// ❗ اگر applyMode در ChatPageController private است،
|
||||
// یا publicش کن یا این دو خط را حذف کن.
|
||||
// گروه → NORMAL ، کانال → READ_ONLY (مگر اینکه اجازه پست داشته باشی)
|
||||
// cpc.applyMode("group".equalsIgnoreCase(type) ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY);
|
||||
// cpc.fetchAndRenderHeader(ce); // اختیاری: هدر را تازه کن
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "removed_from_group":
|
||||
case "removed_from_channel": {
|
||||
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||
String type = msg.getString("chat_type");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
removeFromAllLists(chatId);
|
||||
mc.refreshChatListUI();
|
||||
|
||||
// اگر همین چت باز است → به حالت نیاز به Join برگرد
|
||||
var cpc = mc.getChatPageController();
|
||||
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||
// اگر applyMode private است، این خط را کامنت کن یا publicش کن
|
||||
// cpc.applyMode(ChatViewMode.NEEDS_JOIN);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "chat_deleted": {
|
||||
UUID chatId = UUID.fromString(msg.getString("chat_id"));
|
||||
String type = msg.getString("chat_type");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
removeFromAllLists(chatId);
|
||||
mc.refreshChatListUI();
|
||||
|
||||
var cpc = mc.getChatPageController();
|
||||
if (cpc != null && cpc.isSameChat(chatId, type)) {
|
||||
// حداقل ورودی را ببندیم/غیرفعال کنیم
|
||||
// اگر applyMode private است، این خط را کامنت کن یا publicش کن
|
||||
// cpc.applyMode(ChatViewMode.READ_ONLY);
|
||||
// و یک پیام سیستمی هم نشان بده
|
||||
cpc.addSystemMessage("This chat was deleted.");
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case "became_admin", "removed_admin", "ownership_transferred", "admin_permissions_updated" : {
|
||||
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
handleAdminRoleChanged(finalMsg1);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
break;
|
||||
}
|
||||
|
||||
case "new_message" :{
|
||||
JSONObject data = response.optJSONObject("data");
|
||||
if (data == null) break;
|
||||
|
||||
String mid = data.optString("id", data.optString("message_id",""));
|
||||
if (mid.isEmpty()) break;
|
||||
if (!seenMessageIds.add(mid)) break;
|
||||
|
||||
bumpChatListFromMessage(data);
|
||||
|
||||
JSONObject uiMsg = new JSONObject(data.toString());
|
||||
if (!uiMsg.has("message_id") && uiMsg.has("id")) {
|
||||
uiMsg.put("message_id", uiMsg.getString("id"));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
var chatCtl = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (chatCtl != null) {
|
||||
chatCtl.onRealTimeNewMessage(uiMsg);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "message_edited": {
|
||||
JSONObject ui = normalizeMessageId(msg);
|
||||
// (اختیاری) اگر ایونت زمان و چت را هم میدهد، میتوانی چتلیست را آپدیت کنی
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
var chatCtl = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (chatCtl != null) chatCtl.onRealTimeMessageEdited(ui);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "message_deleted_global", "message_deleted_one_sided", "message_deleted" : {
|
||||
JSONObject ui = normalizeMessageId(msg);
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
var chatCtl = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (chatCtl != null) chatCtl.onRealTimeMessageDeleted(ui);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "message_reacted", "message_unreacted" : {
|
||||
JSONObject ui = normalizeMessageId(msg);
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
var chatCtl = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (chatCtl != null) chatCtl.onRealTimeReaction(ui);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
case "chat_updated": {
|
||||
var data = response.getJSONObject("data");
|
||||
bumpChatListFromUpdate(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "user_status_changed" : {
|
||||
|
||||
displayRealTimeMessage(action, msg);
|
||||
Platform.runLater(() -> {
|
||||
var mc = org.to.telegramfinalproject.UI.MainController.getInstance();
|
||||
var cp = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (cp != null) cp.onUserStatusChanged(
|
||||
msg.optString("user_id",""),
|
||||
msg.optString("status",""),
|
||||
msg.optString("last_seen","")
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
case "blocked_by_user", "unblocked_by_user", "message_seen" : {
|
||||
displayRealTimeMessage(action, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
default :{
|
||||
System.out.println("\n❓ Unknown real-time action: " + action);
|
||||
System.out.println(msg.toString(2));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.print(">> ");
|
||||
}
|
||||
|
||||
private void updateLastMessageTime(JSONObject msg) {
|
||||
try {
|
||||
UUID chatUUID = UUID.fromString(msg.getString("chat_id"));
|
||||
String newTime = msg.optString("last_message_time", null);
|
||||
|
||||
Session.chatList.stream()
|
||||
.filter(chat -> chat.getId().equals(chatUUID))
|
||||
.findFirst()
|
||||
.ifPresent(chat -> {
|
||||
chat.setLastMessageTime(newTime);
|
||||
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
|
||||
});
|
||||
|
||||
Session.activeChats.stream()
|
||||
.filter(chat -> chat.getId().equals(chatUUID))
|
||||
.findFirst()
|
||||
.ifPresent(chat -> {
|
||||
chat.setLastMessageTime(newTime);
|
||||
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
|
||||
});
|
||||
|
||||
Session.archivedChats.stream()
|
||||
.filter(chat -> chat.getId().equals(chatUUID))
|
||||
.findFirst()
|
||||
.ifPresent(chat -> {
|
||||
chat.setLastMessageTime(newTime);
|
||||
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
|
||||
});
|
||||
|
||||
Session.chatList.sort((c1, c2) -> {
|
||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||
if (c1.getLastMessageTime() == null) return 1;
|
||||
if (c2.getLastMessageTime() == null) return -1;
|
||||
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
|
||||
});
|
||||
Session.activeChats.sort((c1, c2) -> {
|
||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||
if (c1.getLastMessageTime() == null) return 1;
|
||||
if (c2.getLastMessageTime() == null) return -1;
|
||||
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
|
||||
});
|
||||
Session.archivedChats.sort((c1, c2) -> {
|
||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||
if (c1.getLastMessageTime() == null) return 1;
|
||||
if (c2.getLastMessageTime() == null) return -1;
|
||||
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
if (Session.inChatListMenu) {
|
||||
ActionHandler.displayChatList();
|
||||
System.out.print("Select a chat by number: ");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("❌ Failed to update last message time: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleAdminRoleChanged(JSONObject data) throws IOException {
|
||||
String chatType = data.getString("chat_type");
|
||||
String chatId = data.optString("group_id", data.optString("channel_id", data.optString("chat_id", null)));
|
||||
|
||||
if (chatId == null) {
|
||||
System.out.println("⚠️ No valid ID found in real-time data: " + data.toString(2));
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("\n🔄 Your admin status changed. Updating chat info...");
|
||||
|
||||
try {
|
||||
// 1. get chat info
|
||||
JSONObject chatInfoReq = new JSONObject();
|
||||
chatInfoReq.put("action", "get_chat_info");
|
||||
chatInfoReq.put("receiver_id", chatId);
|
||||
chatInfoReq.put("receiver_type", chatType);
|
||||
System.out.println("📤 Sending get_chat_info: " + chatInfoReq);
|
||||
JSONObject chatInfoResp = ActionHandler.sendWithResponse(chatInfoReq);
|
||||
JSONObject chatData = chatInfoResp.getJSONObject("data");
|
||||
|
||||
UUID chatUUID = UUID.fromString(chatData.getString("internal_id"));
|
||||
|
||||
Optional<ChatEntry> entry = Session.chatList.stream()
|
||||
.filter(e -> e.getId().equals(chatUUID))
|
||||
.findFirst();
|
||||
|
||||
if (entry.isEmpty()) {
|
||||
System.out.println("❌ Chat not found in session.");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.ifPresent(chat -> {
|
||||
chat.setAdmin(chatData.optBoolean("is_admin", false));
|
||||
chat.setOwner(chatData.optBoolean("is_owner", false));
|
||||
chat.setName(chatData.optString("name", ""));
|
||||
chat.setDisplayId(chatData.optString("id", ""));
|
||||
chat.setImageUrl(chatData.optString("image_url", ""));
|
||||
chat.setType(chatData.optString("type", ""));
|
||||
Session.currentChatEntry = chat;
|
||||
});
|
||||
|
||||
// 2. get permission
|
||||
JSONObject permissionReq = new JSONObject();
|
||||
if (chatType.equalsIgnoreCase("group")) {
|
||||
permissionReq.put("action", "get_group_permissions");
|
||||
permissionReq.put("group_id", chatId);
|
||||
} else {
|
||||
permissionReq.put("action", "get_channel_permissions");
|
||||
permissionReq.put("channel_id", chatId);
|
||||
}
|
||||
|
||||
JSONObject permissionResp = ActionHandler.sendWithResponse(permissionReq);
|
||||
JSONObject perm = permissionResp.getJSONObject("data");
|
||||
entry.ifPresent(chat -> chat.setPermissions(perm));
|
||||
|
||||
// 3. set currentChatId
|
||||
Session.currentChatId = chatUUID.toString();
|
||||
|
||||
System.out.println("🧪 Checking refresh conditions...");
|
||||
System.out.println("🔹 inChatMenu: " + Session.inChatMenu);
|
||||
System.out.println("🔹 currentChatId: " + Session.currentChatId);
|
||||
System.out.println("🔹 chatUUID: " + chatUUID);
|
||||
|
||||
if (Session.inChatMenu && Session.currentChatId != null && Session.currentChatId.equals(chatUUID.toString())) {
|
||||
synchronized (Session.class) {
|
||||
Session.refreshCurrentChatMenu = true;
|
||||
}
|
||||
System.out.println("✅ Admin status updated. Refreshing menu...");
|
||||
} else {
|
||||
System.out.println("❌ Refresh conditions not met.");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("❌ Exception while handling admin role change: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void displayRealTimeMessage(String action, JSONObject msg) {
|
||||
switch (action) {
|
||||
case "new_message" -> {
|
||||
String senderName = msg.optString("sender_name","Unknown");
|
||||
String content = msg.optString("content","(empty)");
|
||||
String sendAt = msg.optString("send_at","-");
|
||||
String chatId = msg.optString("receiver_id", msg.optString("chat_id",""));
|
||||
String kind = msg.optString("kind","plain");
|
||||
JSONObject meta = msg.optJSONObject("meta");
|
||||
|
||||
String prefix = "";
|
||||
if ("reply".equals(kind) && meta != null) {
|
||||
var rt = meta.optJSONObject("reply_to");
|
||||
prefix = "[reply → " + (rt!=null?rt.optString("excerpt",""):"") + "] ";
|
||||
} else if ("forward".equals(kind) && meta != null) {
|
||||
var ff = meta.optJSONObject("forwarded_from");
|
||||
prefix = "[forwarded from " + (ff!=null?ff.optString("sender_name","unknown"):"unknown") + "] ";
|
||||
}
|
||||
|
||||
boolean isInCurrentChat = Session.inChatMenu &&
|
||||
Session.currentChatId != null && Session.currentChatId.equals(chatId);
|
||||
|
||||
if (isInCurrentChat) {
|
||||
System.out.println(senderName + ": " + prefix + content + " (" + sendAt + ")");
|
||||
} else {
|
||||
System.out.println("💬 Message from " + senderName + ": " + prefix + content);
|
||||
Session.forceRefreshChatList = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case "message_edited" -> {
|
||||
System.out.println("\n✏️ Message Edited:");
|
||||
System.out.println("ID: " + msg.getString("message_id"));
|
||||
System.out.println("New Content: " + msg.getString("new_content"));
|
||||
System.out.println("Edit Time: " + msg.getString("edited_at"));
|
||||
}
|
||||
case "message_deleted_global" -> {
|
||||
System.out.println("\n🗑️ Message Deleted:");
|
||||
System.out.println("Message ID: " + msg.getString("message_id"));
|
||||
}
|
||||
case "message_reacted", "message_unreacted" -> {
|
||||
String mid = msg.getString("message_id");
|
||||
String emoji = msg.getString("emoji");
|
||||
JSONObject counts = msg.optJSONObject("counts");
|
||||
int n = msg.optInt("count_for_emoji", 0);
|
||||
System.out.println("\n⭐ Reaction update on " + mid + " : " + emoji + " → " + n);
|
||||
}
|
||||
|
||||
case "user_status_changed" -> {
|
||||
System.out.println("\n🔄 User Status Changed:");
|
||||
System.out.println("User: " + msg.getString("user_id"));
|
||||
System.out.println("Status: " + msg.getString("status"));
|
||||
}
|
||||
case "blocked_by_user" -> {
|
||||
System.out.println("\n⛔ You were blocked by user: " + msg.getString("blocker_id"));
|
||||
}
|
||||
case "unblocked_by_user" -> {
|
||||
System.out.println("\n✅ You were unblocked by user: " + msg.getString("unblocker_id"));
|
||||
}
|
||||
case "message_seen" -> {
|
||||
System.out.println("\n👁️ Your message was seen:");
|
||||
System.out.println("Message ID: " + msg.getString("message_id"));
|
||||
System.out.println("Seen at: " + msg.getString("seen_at"));
|
||||
}
|
||||
default -> {
|
||||
System.out.println("\n❓ Unknown real-time action: " + action);
|
||||
System.out.println(msg.toString(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static LocalDateTime parseIsoFlexible(String iso) {
|
||||
if (iso == null || iso.isBlank()) return null;
|
||||
try { return LocalDateTime.parse(iso); } catch (Exception ignore) {}
|
||||
try { return OffsetDateTime.parse(iso).toLocalDateTime(); } catch (Exception ignore) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void bumpChatListFromUpdate(JSONObject data) {
|
||||
try {
|
||||
UUID chatId = UUID.fromString(data.optString("chat_id",""));
|
||||
String chatType = data.optString("chat_type","");
|
||||
LocalDateTime ts = parseIsoFlexible(data.optString("last_message_time", null));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
if (mc != null) mc.onChatUpdated(chatId, chatType, ts, /*isIncoming*/ false, null);
|
||||
});
|
||||
} catch (Exception e) { System.err.println("[RT] bumpChatListFromUpdate: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String previewOf(String type, String content) {
|
||||
String t = type == null ? "" : type.trim().toUpperCase();
|
||||
return switch (t) {
|
||||
case "IMAGE" -> "[Image]";
|
||||
case "AUDIO" -> "[Audio]";
|
||||
case "VIDEO" -> "[Video]";
|
||||
case "FILE" -> "[File]";
|
||||
default -> (content == null ? "" : content);
|
||||
};
|
||||
}
|
||||
|
||||
private void bumpChatListFromMessage(JSONObject m) {
|
||||
try {
|
||||
UUID chatId = UUID.fromString(m.optString("receiver_id",""));
|
||||
String chatType = m.optString("receiver_type","");
|
||||
LocalDateTime ts = parseIsoFlexible(m.optString("send_at", null));
|
||||
|
||||
String preview = previewOf(m.optString("message_type","TEXT"),
|
||||
m.optString("content",""));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var mc = MainController.getInstance();
|
||||
if (mc != null) mc.onChatUpdated(chatId, chatType, ts, /*isIncoming*/ true, preview);
|
||||
});
|
||||
} catch (Exception e) { System.err.println("[RT] bumpChatListFromMessage: " + e.getMessage()); }
|
||||
}
|
||||
|
||||
// --- add this helper ---
|
||||
private static JSONObject normalizeMessageId(JSONObject j) {
|
||||
if (j == null) return new JSONObject();
|
||||
if (!j.has("message_id") && j.has("id")) {
|
||||
JSONObject copy = new JSONObject(j.toString());
|
||||
copy.put("message_id", copy.optString("id", ""));
|
||||
return copy;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
|
||||
private void removeFromAllLists(UUID chatId) {
|
||||
if (Session.chatList != null) {
|
||||
Session.chatList.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||
}
|
||||
if (Session.activeChats != null) {
|
||||
Session.activeChats.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||
}
|
||||
if (Session.archivedChats != null) {
|
||||
Session.archivedChats.removeIf(c -> chatId.toString().equals(String.valueOf(c.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.util.UUID;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
|
||||
|
||||
class MediaSender {
|
||||
|
||||
public static void sendImageOrAudio(Socket socket,
|
||||
UUID senderId,
|
||||
String receiverType,
|
||||
UUID receiverId,
|
||||
File file,
|
||||
String messageType, // "IMAGE" یا "AUDIO"
|
||||
String captionOrEmpty) throws Exception {
|
||||
|
||||
if (!"IMAGE".equals(messageType) && !"AUDIO".equals(messageType))
|
||||
throw new IllegalArgumentException("Only IMAGE/AUDIO");
|
||||
|
||||
// 1) اعلام سوییچ به باینری
|
||||
PrintWriter textOut = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
|
||||
textOut.println("MEDIA");
|
||||
|
||||
// 2) متادیتا
|
||||
String mime = Files.probeContentType(file.toPath());
|
||||
if (mime == null) mime = "application/octet-stream";
|
||||
|
||||
Integer width = null, height = null;
|
||||
if ("IMAGE".equals(messageType)) {
|
||||
try {
|
||||
BufferedImage img = ImageIO.read(file);
|
||||
if (img != null) { width = img.getWidth(); height = img.getHeight(); }
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
UUID messageId = UUID.randomUUID();
|
||||
JSONObject header = new JSONObject()
|
||||
.put("message_id", messageId.toString())
|
||||
.put("sender_id", senderId.toString())
|
||||
.put("receiver_type", receiverType)
|
||||
.put("receiver_id", receiverId.toString())
|
||||
.put("message_type", messageType)
|
||||
.put("file_name", file.getName())
|
||||
.put("mime_type", mime)
|
||||
.put("file_size", file.length())
|
||||
.put("text", captionOrEmpty == null ? "" : captionOrEmpty);
|
||||
|
||||
if (width != null) header.put("width", width);
|
||||
if (height != null) header.put("height", height);
|
||||
|
||||
byte[] headerBytes = header.toString().getBytes("UTF-8");
|
||||
|
||||
// 3) ارسال فریم باینری
|
||||
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
|
||||
dos.writeInt(0x4D444D31); // MAGIC
|
||||
dos.writeInt(headerBytes.length); // headerLen
|
||||
dos.write(headerBytes); // header
|
||||
dos.writeLong(file.length()); // contentLen
|
||||
|
||||
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
|
||||
byte[] buf = new byte[8192];
|
||||
int n;
|
||||
while ((n = fis.read(buf)) != -1) {
|
||||
dos.write(buf, 0, n);
|
||||
}
|
||||
}
|
||||
dos.flush();
|
||||
|
||||
// (اختیاری) Ack متنی
|
||||
BufferedReader textIn = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
|
||||
String ack = textIn.readLine();
|
||||
if (!"OK".equalsIgnoreCase(ack)) {
|
||||
throw new IOException("Server did not ACK: " + ack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class RealTimeBuffer {
|
||||
public static final BlockingQueue<JSONObject> incomingEvents = new LinkedBlockingQueue<>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.Models.ContactEntry;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
// method for save data from server response
|
||||
public class Session {
|
||||
public static JSONObject currentUser;
|
||||
public static List<ChatEntry> chatList = new ArrayList<>();
|
||||
public static List<ChatEntry> archivedChats = new ArrayList<>();
|
||||
public static List<ChatEntry> activeChats = new ArrayList<>();
|
||||
public static UUID currentPrivateChatUserId = null;
|
||||
public static volatile boolean forceRefreshChatList = false;
|
||||
public static volatile boolean backToChatList = false;
|
||||
public static boolean inChatListMenu = false;
|
||||
public static String currentChatType = null;
|
||||
public static volatile boolean inChatMenu = false;
|
||||
public static volatile boolean refreshCurrentChatMenu = false;
|
||||
public static String currentChatId = null;
|
||||
public static ChatEntry currentChatEntry = null;
|
||||
public static List<ContactEntry> contactEntries = new ArrayList<>();
|
||||
public static boolean inContactListMenu = false;
|
||||
public static DownloadsIndex downloadsIndex = null;
|
||||
|
||||
public static String getUserUUID() {
|
||||
if (currentUser.has("uuid")) return currentUser.getString("uuid");
|
||||
if (currentUser.has("internal_uuid")) return currentUser.getString("internal_uuid");
|
||||
if (currentUser.has("internalUUID")) return currentUser.getString("internalUUID");
|
||||
throw new RuntimeException("❌ No UUID found in currentUser!");
|
||||
}
|
||||
|
||||
// public static void updateChatList(JSONArray chatArray) {
|
||||
// chatList.clear();
|
||||
// for (int i = 0; i < chatArray.length(); i++) {
|
||||
// JSONObject obj = chatArray.getJSONObject(i);
|
||||
// ChatEntry entry = new ChatEntry(
|
||||
// UUID.fromString(obj.getString("internal_id")),
|
||||
// obj.optString("id", ""), // displayId
|
||||
// obj.optString("name", ""), // name
|
||||
// obj.optString("image_url", ""),
|
||||
// obj.getString("type"),
|
||||
// null, // last message time (if needed, parse it)
|
||||
// obj.optBoolean("is_owner", false),
|
||||
// obj.optBoolean("is_admin", false)
|
||||
// );
|
||||
// entry.setPermissions(obj.optJSONObject("permissions"));
|
||||
// chatList.add(entry);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public static void updateChatList(JSONArray chatArray) {
|
||||
chatList.clear();
|
||||
for (int i = 0; i < chatArray.length(); i++) {
|
||||
JSONObject obj = chatArray.getJSONObject(i);
|
||||
|
||||
LocalDateTime lastMessageTime = null;
|
||||
if (obj.has("last_message_time") && !obj.isNull("last_message_time")) {
|
||||
String timeStr = obj.getString("last_message_time");
|
||||
if (!timeStr.isBlank()) {
|
||||
lastMessageTime = LocalDateTime.parse(timeStr);
|
||||
}
|
||||
}
|
||||
|
||||
ChatEntry entry = new ChatEntry(
|
||||
UUID.fromString(obj.getString("internal_id")),
|
||||
obj.optString("id", ""), // displayId
|
||||
obj.optString("name", ""), // name
|
||||
obj.optString("image_url", ""),
|
||||
obj.getString("type"),
|
||||
lastMessageTime,
|
||||
obj.optBoolean("is_owner", false),
|
||||
obj.optBoolean("is_admin", false)
|
||||
);
|
||||
|
||||
entry.setPermissions(obj.optJSONObject("permissions"));
|
||||
chatList.add(entry);
|
||||
}
|
||||
|
||||
chatList.sort((c1, c2) -> {
|
||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||
if (c1.getLastMessageTime() == null) return 1;
|
||||
if (c2.getLastMessageTime() == null) return -1;
|
||||
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime());
|
||||
});
|
||||
|
||||
activeChats = chatList.stream().filter(c -> !c.isArchived()).toList();
|
||||
archivedChats = chatList.stream().filter(ChatEntry::isArchived).toList();
|
||||
}
|
||||
|
||||
public static List<ChatEntry> getChatList() {
|
||||
return chatList;
|
||||
}
|
||||
|
||||
|
||||
public static void refreshChatLists() {
|
||||
Session.activeChats = Session.chatList.stream()
|
||||
.filter(c -> !c.isArchived())
|
||||
.toList();
|
||||
|
||||
Session.archivedChats = Session.chatList.stream()
|
||||
.filter(ChatEntry::isArchived)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void setDownloadIndex(DownloadsIndex idx){ this.downloadsIndex = idx; }
|
||||
|
||||
|
||||
// ==== ADD to Session class ====
|
||||
|
||||
/** پیدا کردن چت با internal_id */
|
||||
public static ChatEntry findChatByInternalId(UUID internalId) {
|
||||
for (ChatEntry c : chatList) {
|
||||
if (c.getId().equals(internalId)) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** درج در ابتدای لیست (با جلوگیری از دوبل) و سپس resort + refresh */
|
||||
public static void prependChat(ChatEntry entry) {
|
||||
// اگر قبلاً تو لیست هست، اول پاکش کن
|
||||
chatList.removeIf(c -> c.getId().equals(entry.getId()));
|
||||
// اول لیست بذار
|
||||
chatList.add(0, entry);
|
||||
resortAndRefresh();
|
||||
}
|
||||
|
||||
public static void resortAndRefresh() {
|
||||
chatList.sort((c1, c2) -> {
|
||||
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
|
||||
if (c1.getLastMessageTime() == null) return 1;
|
||||
if (c2.getLastMessageTime() == null) return -1;
|
||||
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime());
|
||||
});
|
||||
refreshChatLists();
|
||||
}
|
||||
|
||||
|
||||
public static ChatEntry upsertSavedMessages(UUID chatId, String name, String chatType, String imageUrlIfAny) {
|
||||
ChatEntry existing = findChatByInternalId(chatId);
|
||||
if (existing != null) {
|
||||
// برای اینکه بیاد بالا، میتونی زمان آخرین پیام رو الان بذاری (اختیاری)
|
||||
if (existing.getLastMessageTime() == null) {
|
||||
existing.setLastMessageTime(LocalDateTime.now());
|
||||
resortAndRefresh();
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
ChatEntry entry = new ChatEntry(
|
||||
chatId,
|
||||
"", // displayId برای Saved لازم نیست
|
||||
(name == null || name.isBlank()) ? "Saved Messages" : name,
|
||||
imageUrlIfAny == null ? "" : imageUrlIfAny,
|
||||
(chatType == null || chatType.isBlank()) ? "private" : chatType,
|
||||
LocalDateTime.now(), // بذار بالا بیاد
|
||||
true, // owner? برای self-chat میتونه true باشه (اثری روی permission نداره)
|
||||
true // admin? اختیاری—تأثیر خاصی نداره؛ میتونی false بذاری
|
||||
);
|
||||
// اگر توی UI برای Saved Messages آیکن خاص داری، اینجا ست کن:
|
||||
// entry.setImageUrl("/org/to/telegramfinalproject/Icons/saved_messages_dark.png");
|
||||
|
||||
prependChat(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean inArchivedView = false;
|
||||
|
||||
public static boolean isArchived(UUID chatId) {
|
||||
if (archivedChats == null) return false;
|
||||
for (ChatEntry e : archivedChats) if (e.getId().equals(chatId)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void moveToArchived(ChatEntry e) {
|
||||
if (e == null) return;
|
||||
if (chatList != null) chatList.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (activeChats != null) activeChats.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (archivedChats != null) archivedChats.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (archivedChats != null) archivedChats.add(e);
|
||||
}
|
||||
|
||||
public static void moveToActive(ChatEntry e) {
|
||||
if (e == null) return;
|
||||
if (archivedChats != null) archivedChats.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (activeChats != null) activeChats.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (chatList != null) chatList.removeIf(x -> x.getId().equals(e.getId()));
|
||||
if (activeChats != null) activeChats.add(e);
|
||||
}
|
||||
|
||||
// مرتبسازی بر اساس آخرین پیام (در صورت داشتن فیلد)
|
||||
public static void sortListsByLastMessage() {
|
||||
java.util.Comparator<ChatEntry> cmp =
|
||||
java.util.Comparator.comparing(ChatEntry::getLastMessageTime, java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder()))
|
||||
.reversed();
|
||||
if (activeChats != null) activeChats.sort(cmp);
|
||||
if (archivedChats != null) archivedChats.sort(cmp);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
public enum SidebarAction {
|
||||
MY_PROFILE,
|
||||
NEW_GROUP,
|
||||
NEW_CHANNEL,
|
||||
CONTACTS,
|
||||
SAVED_MESSAGES,
|
||||
SETTINGS,
|
||||
FEATURES,
|
||||
Q_AND_A
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.Models.FileAttachment;
|
||||
import org.to.telegramfinalproject.Models.Message;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse;
|
||||
|
||||
public class SidebarHandler {
|
||||
private final Scanner scanner;
|
||||
private ActionHandler actionHandler ;
|
||||
private final String userUUID;
|
||||
|
||||
public SidebarHandler(Scanner scanner, ActionHandler actionHandler, ActionHandler action) {
|
||||
this.scanner = scanner;
|
||||
this.actionHandler = action;
|
||||
this.userUUID = Session.getUserUUID();
|
||||
|
||||
}
|
||||
|
||||
public SidebarHandler(Scanner scanner, ActionHandler actionHandler) {
|
||||
this.scanner = scanner;
|
||||
this.actionHandler = actionHandler;
|
||||
this.userUUID = Session.getUserUUID();
|
||||
|
||||
}
|
||||
|
||||
public void handleSidebarAction(SidebarAction action) {
|
||||
switch (action) {
|
||||
case MY_PROFILE:
|
||||
openUserProfile();
|
||||
break;
|
||||
case NEW_GROUP:
|
||||
createNewGroup();
|
||||
break;
|
||||
case NEW_CHANNEL:
|
||||
createNewChannel();
|
||||
break;
|
||||
case CONTACTS:
|
||||
showContacts();
|
||||
break;
|
||||
case SAVED_MESSAGES:
|
||||
ensureSavedMessagesCreated();
|
||||
break;
|
||||
case SETTINGS:
|
||||
openSettings();
|
||||
break;
|
||||
case FEATURES:
|
||||
showTelegramFeatures();
|
||||
break;
|
||||
case Q_AND_A:
|
||||
showTelegramQA();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void openUserProfile() {
|
||||
// Step 1: Request profile info from the server
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "get_user_profile");
|
||||
|
||||
JSONObject response = sendWithResponse(request);
|
||||
if (response == null || !response.optString("status", "fail").equals("success")) {
|
||||
System.out.println("Failed to load profile information.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject profile;
|
||||
try {
|
||||
profile = response.getJSONObject("data");
|
||||
} catch (JSONException e) {
|
||||
System.out.println("Received malformed profile data from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
String profileName = profile.optString("profile_name", "NOT-AVAILABLE");
|
||||
String userId = profile.optString("user_id", "NOT-AVAILABLE");
|
||||
String status = profile.optString("status", "NOT-AVAILABLE");
|
||||
String bio = profile.optString("bio", "NOT-AVAILABLE");
|
||||
String profilePictureUrl = profile.optString("profile_picture_url", "NOT-AVAILABLE");
|
||||
|
||||
// Step 2: Show profile info
|
||||
System.out.println("===== My Profile =====");
|
||||
System.out.println("Profile Picture URL : " + profilePictureUrl);
|
||||
System.out.println("Profile Name : " + profileName);
|
||||
System.out.println("User ID : @" + userId);
|
||||
System.out.println("Status : " + status);
|
||||
System.out.println("Bio : " + bio);
|
||||
System.out.println("======================");
|
||||
|
||||
// Step 3: Ask if user wants to edit anything
|
||||
System.out.println("Do you want to edit any of these? (yes/no)");
|
||||
while (true) {
|
||||
String choice = scanner.nextLine().trim().toLowerCase();
|
||||
if (choice.equals("no")) return;
|
||||
if (!choice.equals("yes")) {
|
||||
System.out.println("Invalid input. Please enter 'yes' or 'no'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 4: Show editable fields
|
||||
System.out.println("Which field do you want to edit?");
|
||||
System.out.println("1. Profile Name");
|
||||
System.out.println("2. User ID");
|
||||
System.out.println("3. Bio");
|
||||
System.out.println("4. Profile Picture URL");
|
||||
System.out.println("0. Cancel");
|
||||
|
||||
String option = scanner.nextLine().trim();
|
||||
switch (option) {
|
||||
case "1" -> editProfileName();
|
||||
case "2" -> editUserId();
|
||||
case "3" -> editBio();
|
||||
case "4" -> editProfilePictureUrl();
|
||||
case "0" -> { return; }
|
||||
default -> System.out.println("Invalid choice. Try again.");
|
||||
}
|
||||
|
||||
// Re-fetch and re-display profile after editing
|
||||
openUserProfile();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void editProfileName() {
|
||||
System.out.println("Enter your new profile name:");
|
||||
String newProfileName = scanner.nextLine().trim();
|
||||
|
||||
// Step 1: Validate input
|
||||
while (newProfileName.trim().isEmpty()) {
|
||||
System.out.println("Profile name cannot be empty. Enter another name.");
|
||||
newProfileName = scanner.nextLine().trim();
|
||||
}
|
||||
|
||||
// Step 2: Create request
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "edit_profile_name");
|
||||
request.put("new_profile_name", newProfileName);
|
||||
|
||||
// Step 3: Send request and receive response
|
||||
JSONObject response = sendWithResponse(request);
|
||||
|
||||
// Step 4: Handle response
|
||||
if (response == null || !response.optString("status", "fail").equals("success")) {
|
||||
System.out.println("Failed to update profile name.");
|
||||
} else {
|
||||
System.out.println("Profile name updated successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
private void editUserId() {
|
||||
while (true) {
|
||||
System.out.println("Enter your new user ID: ");
|
||||
String newUserId = scanner.nextLine().trim();
|
||||
|
||||
if (newUserId.isEmpty()) {
|
||||
System.out.println("User ID cannot be empty.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newUserId.contains(" ")) {
|
||||
System.out.println("User ID cannot contain spaces.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newUserId.matches("^[a-zA-Z0-9_]+$")) {
|
||||
System.out.println("User ID can only contain letters, digits, and underscores.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send to server
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "edit_user_id");
|
||||
request.put("new_user_id", newUserId);
|
||||
JSONObject response = sendWithResponse(request);
|
||||
|
||||
if (response.getString("status").equals("success")) {
|
||||
System.out.println("User ID updated successfully.");
|
||||
break;
|
||||
} else {
|
||||
// Server-side error message (e.g., ID already exists)
|
||||
System.out.println(response.getString("message"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void editBio() {
|
||||
System.out.println("Enter your new bio:");
|
||||
String newBio = scanner.nextLine().trim();
|
||||
|
||||
// Limit the bio length
|
||||
if (newBio.length() > 70) {
|
||||
System.out.println("Bio cannot be more than 70 characters.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "edit_bio");
|
||||
request.put("new_bio", newBio);
|
||||
|
||||
JSONObject response = sendWithResponse(request);
|
||||
|
||||
if (response.getString("status").equals("success")) {
|
||||
System.out.println("Bio updated successfully.");
|
||||
} else {
|
||||
System.out.println(response.getString("message"));
|
||||
}
|
||||
}
|
||||
|
||||
private void editProfilePictureUrl() {
|
||||
while (true) {
|
||||
System.out.println("Enter new profile picture URL (or leave empty to remove):");
|
||||
String newImageUrl = scanner.nextLine().trim();
|
||||
|
||||
// URL validation
|
||||
if (newImageUrl.contains(" ")) {
|
||||
System.out.println("URL cannot contain spaces.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newImageUrl.isEmpty() && !newImageUrl.matches("^(http|https)://.*$")) {
|
||||
System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link.");
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "edit_profile_picture");
|
||||
request.put("new_image_url", newImageUrl);
|
||||
JSONObject response = sendWithResponse(request);
|
||||
|
||||
if (response.getString("status").equals("success")) {
|
||||
System.out.println("Bio updated successfully.");
|
||||
} else {
|
||||
System.out.println(response.getString("message"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewGroup() {
|
||||
actionHandler.createGroup();
|
||||
}
|
||||
|
||||
private void createNewChannel() {
|
||||
actionHandler.createChannel();
|
||||
}
|
||||
|
||||
private void showContacts() {
|
||||
actionHandler.showContactList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void ensureSavedMessagesCreated() {
|
||||
// 1) Client-side quick check
|
||||
boolean alreadyExists = false;
|
||||
if (Session.activeChats != null) {
|
||||
for (ChatEntry e : Session.activeChats) {
|
||||
if (e != null && e.isSavedMessages()) { alreadyExists = true; break; }
|
||||
}
|
||||
}
|
||||
if (alreadyExists) {
|
||||
System.out.println("ℹ️ Saved Messages already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Ask server to get or create the self-chat
|
||||
JSONObject res = sendWithResponse(new JSONObject().put("action", "get_or_create_saved_messages"));
|
||||
if (res == null || !"success".equals(res.optString("status"))) {
|
||||
System.out.println("❌ Failed to create/open Saved Messages: " + (res != null ? res.optString("message","") : ""));
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = res.optJSONObject("data");
|
||||
if (data == null) {
|
||||
System.out.println("❌ Invalid server response for Saved Messages.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean created = data.optBoolean("created", true); // if server sends it
|
||||
String chatId = data.optString("chat_id", null);
|
||||
|
||||
if (created) {
|
||||
System.out.println("✅ Saved Messages created." + (chatId != null ? " chat_id=" + chatId : ""));
|
||||
try {
|
||||
UUID cid = UUID.fromString(chatId);
|
||||
ChatEntry saved = new ChatEntry(cid, "Saved Messages", "Saved Messages", "", "private", null, true, false);
|
||||
saved.setSavedMessages(true);
|
||||
if (Session.activeChats == null) Session.activeChats = new ArrayList<>();
|
||||
Session.activeChats.add(0, saved);
|
||||
} catch (Exception ignore) {}
|
||||
} else {
|
||||
System.out.println("ℹ️ Saved Messages already exists." + (chatId != null ? " chat_id=" + chatId : ""));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void openSettings() {
|
||||
while (true) {
|
||||
System.out.println("⚙️ Settings\n");
|
||||
|
||||
String imageUrl = Session.currentUser.optString("image_url", "—");
|
||||
String profileName = Session.currentUser.optString("profile_name", "—");
|
||||
String userId = Session.currentUser.optString("user_id", "—");
|
||||
|
||||
renderUserCard(imageUrl, profileName, userId);
|
||||
System.out.println();
|
||||
renderMenu();
|
||||
|
||||
System.out.print("\nChoose an option (0-4): ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1": openUserProfile(); break;
|
||||
case "2": showPrivacySettings(); break;
|
||||
case "3": showTelegramQA(); break;
|
||||
case "4": showTelegramFeatures(); break;
|
||||
case "0": return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter to continue...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void showPrivacySettings() {
|
||||
while (true) {
|
||||
System.out.println("🔒 Privacy\n");
|
||||
System.out.println("1) Blocked users");
|
||||
System.out.println("2) Change username / password");
|
||||
System.out.println("0) Back");
|
||||
System.out.print("\nChoose: ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1": viewBlockedUsers(); break;
|
||||
case "2": changeCredentialsFlow(); break;
|
||||
case "0": return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void viewBlockedUsers() {
|
||||
System.out.println("🚫 Blocked Users\n");
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "get_blocked_users");
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res == null || !res.optString("status","error").equals("success")) {
|
||||
System.out.println("❌ Failed to fetch blocked users. Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
org.json.JSONArray arr = res.getJSONObject("data").optJSONArray("blocked_users");
|
||||
if (arr == null || arr.isEmpty()) {
|
||||
System.out.println("📭 No blocked users.");
|
||||
System.out.println("\nPress Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
org.json.JSONObject u = arr.getJSONObject(i);
|
||||
String profileName = u.optString("profile_name", "—");
|
||||
String userId = u.optString("user_id", "—");
|
||||
System.out.printf("%d) %s (@%s)\n", i + 1, profileName, userId);
|
||||
}
|
||||
System.out.println("\n0) Back");
|
||||
System.out.print("\nChoose a user to UNBLOCK (number): ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
if (pick.equals("0")) return;
|
||||
int idx;
|
||||
try { idx = Integer.parseInt(pick) - 1; } catch (Exception e) { idx = -1; }
|
||||
if (idx < 0 || idx >= arr.length()) {
|
||||
System.out.println("❌ Invalid index. Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
org.json.JSONObject target = arr.getJSONObject(idx);
|
||||
String targetDisplayId = target.optString("user_id", "");
|
||||
java.util.UUID targetInternalId = java.util.UUID.fromString(target.getString("internal_uuid"));
|
||||
|
||||
System.out.printf("Unblock %s (@%s)? (yes/no): ", target.optString("profile_name","—"), targetDisplayId);
|
||||
if (!scanner.nextLine().trim().equalsIgnoreCase("yes")) return;
|
||||
|
||||
org.json.JSONObject unReq = new org.json.JSONObject();
|
||||
unReq.put("action", "toggle_block");
|
||||
unReq.put("user_id",Session.getUserUUID());
|
||||
unReq.put("target_id", targetInternalId.toString());
|
||||
|
||||
org.json.JSONObject unRes = sendWithResponse(unReq);
|
||||
if (unRes != null && unRes.optString("status","error").equals("success")) {
|
||||
System.out.println("✅ User unblocked.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to unblock.");
|
||||
}
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
|
||||
|
||||
private void changeCredentialsFlow() {
|
||||
System.out.println("🛡️ Change Username / Password\n");
|
||||
|
||||
System.out.print("Enter current password: ");
|
||||
String currentPassword = scanner.nextLine();
|
||||
|
||||
org.json.JSONObject verReq = new org.json.JSONObject();
|
||||
verReq.put("action", "verify_password");
|
||||
verReq.put("current_password", currentPassword);
|
||||
|
||||
org.json.JSONObject verRes = sendWithResponse(verReq);
|
||||
if (verRes == null || !verRes.optString("status","error").equals("success")) {
|
||||
System.out.println("❌ Current password is incorrect.");
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
String currentUsername = Session.currentUser.optString("username", "—");
|
||||
System.out.println("\n✅ Verified.");
|
||||
System.out.println("Current username: " + currentUsername);
|
||||
System.out.println("Current password: ******** (hidden)");
|
||||
System.out.println("\nWhat do you want to change?");
|
||||
System.out.println("1) Username");
|
||||
System.out.println("2) Password");
|
||||
System.out.println("3) Both");
|
||||
System.out.println("0) Back");
|
||||
System.out.print("\nChoose: ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1":
|
||||
changeUsername(currentPassword);
|
||||
break;
|
||||
case "2":
|
||||
changePassword(currentPassword);
|
||||
break;
|
||||
case "3":
|
||||
boolean uOk = changeUsername(currentPassword);
|
||||
boolean pOk = changePassword(currentPassword);
|
||||
if (uOk && pOk) System.out.println("✅ Username and password updated.");
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
break;
|
||||
case "0":
|
||||
return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean changeUsername(String currentPassword) {
|
||||
System.out.print("\nNew username: ");
|
||||
String newUsername = scanner.nextLine().trim();
|
||||
|
||||
if (!isValidUsername(newUsername)) {
|
||||
System.out.println("❌ Invalid username. Use 4–32 chars: letters, digits, underscore.");
|
||||
return false;
|
||||
}
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "update_username");
|
||||
req.put("current_password", currentPassword);
|
||||
req.put("new_username", newUsername);
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.optString("status","error").equals("success")) {
|
||||
Session.currentUser.put("username", newUsername);
|
||||
System.out.println("✅ Username updated.");
|
||||
return true;
|
||||
} else {
|
||||
String msg = (res == null) ? "No response." : res.optString("message","Update failed.");
|
||||
System.out.println("❌ " + msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidUsername(String s) {
|
||||
//4-32 char
|
||||
return s != null && s.matches("^[A-Za-z0-9_]{4,32}$");
|
||||
}
|
||||
|
||||
private boolean changePassword(String currentPassword) {
|
||||
System.out.print("\nNew password: ");
|
||||
String newPassword = scanner.nextLine();
|
||||
System.out.print("Repeat new password: ");
|
||||
String repeat = scanner.nextLine();
|
||||
|
||||
if (!newPassword.equals(repeat)) {
|
||||
System.out.println("❌ Passwords do not match.");
|
||||
return false;
|
||||
}
|
||||
if (!isStrongPassword(newPassword)) {
|
||||
System.out.println("❌ Weak password. Min 8 chars, include letters and digits.");
|
||||
return false;
|
||||
}
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "update_password");
|
||||
req.put("current_password", currentPassword);
|
||||
req.put("new_password", newPassword);
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.optString("status","error").equals("success")) {
|
||||
System.out.println("✅ Password updated.");
|
||||
return true;
|
||||
} else {
|
||||
String msg = (res == null) ? "No response." : res.optString("message","Update failed.");
|
||||
System.out.println("❌ " + msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void renderUserCard(String imageUrl, String profileName, String userId) {
|
||||
int w = 60;
|
||||
String top = "┌" + "─".repeat(w - 2) + "┐";
|
||||
String bot = "└" + "─".repeat(w - 2) + "┘";
|
||||
System.out.println(top);
|
||||
System.out.println(padBoxLine("Profile", w));
|
||||
System.out.println("├" + "─".repeat(w - 2) + "┤");
|
||||
System.out.println(padBoxLine("Image URL: " + imageUrl, w));
|
||||
System.out.println(padBoxLine("Profile Name: "+ profileName, w));
|
||||
System.out.println(padBoxLine("User ID: " + userId, w));
|
||||
System.out.println(bot);
|
||||
}
|
||||
|
||||
private String padBoxLine(String text, int width) {
|
||||
final int inner = width - 2;
|
||||
if (text.length() > inner) {
|
||||
text = text.substring(0, inner - 1) + "…";
|
||||
}
|
||||
int spaces = inner - text.length();
|
||||
return "│" + text + " ".repeat(Math.max(0, spaces)) + "│";
|
||||
}
|
||||
|
||||
private void renderMenu() {
|
||||
System.out.println("1) My Account");
|
||||
System.out.println("2) Privacy");
|
||||
System.out.println("3) Telegram Q&A");
|
||||
System.out.println("4) Telegram Features");
|
||||
System.out.println("0) Back");
|
||||
}
|
||||
|
||||
private void showTelegramFeatures() {
|
||||
System.out.println("🌟 Showing Telegram features...");
|
||||
}
|
||||
|
||||
private void showTelegramQA() {
|
||||
System.out.println("❓ Showing Q&A...");
|
||||
}
|
||||
|
||||
private boolean isStrongPassword(String s) {
|
||||
boolean approved = s.matches("\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b");
|
||||
return approved ;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public final class SocketMediaDownloader {
|
||||
private static final int MAGIC_DL = 0x4D444D32;
|
||||
private final PrintWriter outText; // NEW
|
||||
private final DataInputStream inBin;
|
||||
private final DataOutputStream outBin;
|
||||
|
||||
public SocketMediaDownloader(PrintWriter outText, DataInputStream inBin, DataOutputStream outBin) {
|
||||
this.outText = outText;
|
||||
this.inBin = inBin;
|
||||
this.outBin = outBin;
|
||||
}
|
||||
|
||||
|
||||
public java.nio.file.Path download(java.util.UUID mediaKey, java.nio.file.Path saveDir, String fileNameHint) throws Exception {
|
||||
outText.print("MEDIA_DL\n");
|
||||
outText.flush();
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject()
|
||||
.put("op","download")
|
||||
.put("media_key", mediaKey.toString())
|
||||
.put("offset", 0);
|
||||
byte[] hb = req.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
|
||||
outBin.writeInt(MAGIC_DL);
|
||||
outBin.writeInt(hb.length);
|
||||
outBin.write(hb);
|
||||
outBin.flush();
|
||||
|
||||
int magic = inBin.readInt();
|
||||
if (magic != MAGIC_DL) throw new java.io.IOException("bad magic");
|
||||
|
||||
int hlen = inBin.readInt();
|
||||
byte[] hbytes = inBin.readNBytes(hlen);
|
||||
org.json.JSONObject hdr = new org.json.JSONObject(new String(hbytes, java.nio.charset.StandardCharsets.UTF_8));
|
||||
if (!"success".equalsIgnoreCase(hdr.optString("status"))) {
|
||||
throw new java.io.IOException("download error: " + hdr.optString("message"));
|
||||
}
|
||||
|
||||
long contentLen = inBin.readLong();
|
||||
String serverName = hdr.optString("file_name", fileNameHint != null ? fileNameHint : mediaKey.toString());
|
||||
|
||||
java.nio.file.Files.createDirectories(saveDir);
|
||||
java.nio.file.Path dest = uniquePath(saveDir, serverName);
|
||||
|
||||
try (java.io.OutputStream os = java.nio.file.Files.newOutputStream(dest)) {
|
||||
byte[] buf = new byte[8192];
|
||||
long remain = contentLen;
|
||||
while (remain > 0) {
|
||||
int toRead = (int) Math.min(buf.length, remain);
|
||||
int n = inBin.read(buf, 0, toRead);
|
||||
if (n == -1) throw new java.io.EOFException("unexpected EOF");
|
||||
os.write(buf, 0, n);
|
||||
remain -= n;
|
||||
}
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
private static java.nio.file.Path uniquePath(java.nio.file.Path dir, String name) throws java.io.IOException {
|
||||
java.nio.file.Path p = dir.resolve(name);
|
||||
if (!java.nio.file.Files.exists(p)) return p;
|
||||
String base = name, ext = "";
|
||||
int dot = name.lastIndexOf('.');
|
||||
if (dot >= 0) { base = name.substring(0, dot); ext = name.substring(dot); }
|
||||
int i = 1;
|
||||
while (java.nio.file.Files.exists(dir.resolve(base + " (" + i + ")" + ext))) i++;
|
||||
return dir.resolve(base + " (" + i + ")" + ext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
//package org.to.telegramfinalproject.Client;
|
||||
//
|
||||
//import org.json.JSONObject;
|
||||
//
|
||||
//import java.io.BufferedReader;
|
||||
//import java.io.IOException;
|
||||
//import java.io.InputStreamReader;
|
||||
//import java.io.PrintWriter;
|
||||
//import java.net.Socket;
|
||||
//import java.util.Map;
|
||||
//import java.util.Scanner;
|
||||
//import java.util.UUID;
|
||||
//import java.util.concurrent.BlockingQueue;
|
||||
//import java.util.concurrent.ConcurrentHashMap;
|
||||
//import java.util.concurrent.LinkedBlockingQueue;
|
||||
//
|
||||
//public class TelegramClient {
|
||||
// private static final String SERVER_HOST = "localhost";
|
||||
// private static final int SERVER_PORT = 8000;
|
||||
// private static Socket socket;
|
||||
// private BufferedReader in;
|
||||
// private PrintWriter out;
|
||||
// private final Scanner scanner;
|
||||
// private ActionHandler handler;
|
||||
// public static BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>();
|
||||
// public static UUID loggedInUserId = null;
|
||||
// public static final Map<String, BlockingQueue<JSONObject>> pendingResponses = new ConcurrentHashMap<>();
|
||||
//
|
||||
//
|
||||
// private static TelegramClient instance;
|
||||
//
|
||||
// public TelegramClient() {
|
||||
// this.scanner = new Scanner(System.in);
|
||||
// instance = this;
|
||||
// }
|
||||
//
|
||||
// public static TelegramClient getInstance() {
|
||||
// return instance;
|
||||
// }
|
||||
//
|
||||
// public void start() {
|
||||
// try {
|
||||
// socket = new Socket(SERVER_HOST, SERVER_PORT);
|
||||
// in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
// out = new PrintWriter(socket.getOutputStream(), true);
|
||||
// System.out.println("✅ Connected to Telegram Server");
|
||||
// handler = new ActionHandler(out, in, scanner);
|
||||
//
|
||||
// Thread listenerThread = new Thread(new IncomingMessageListener(in));
|
||||
// listenerThread.setDaemon(true);
|
||||
// listenerThread.start();
|
||||
//
|
||||
// showMainMenu();
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// System.err.println("❌ Error connecting to server: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void showMainMenu() throws IOException {
|
||||
// while (true) {
|
||||
// System.out.println("Main Menu:");
|
||||
// System.out.println("1. Register");
|
||||
// System.out.println("2. Login");
|
||||
// System.out.println("3. Exit");
|
||||
// System.out.print("Choose an option: ");
|
||||
// String choice = scanner.nextLine();
|
||||
//
|
||||
// switch (choice) {
|
||||
// case "1" -> handler.register();
|
||||
// case "2" -> {
|
||||
// handler.loginHandler();
|
||||
// if (Session.currentUser != null) {
|
||||
// System.out.println("✅ Login successful.");
|
||||
// new Thread(new ActionHandler.ChatStateMonitor(out)).start();
|
||||
//// new Thread(new ActionHandler.CurrentChatMenuRefresher(this.handler)).start();
|
||||
//
|
||||
//
|
||||
// UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||
// loggedInUserId = internalId;
|
||||
//
|
||||
// handler.userMenu(internalId);
|
||||
// } else {
|
||||
// System.out.println("❌ Login failed.");
|
||||
// }
|
||||
// }
|
||||
// case "3" -> {
|
||||
// System.out.println("Exiting...");
|
||||
// return;
|
||||
// }
|
||||
// default -> System.out.println("Invalid choice.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void send(JSONObject req) {
|
||||
// try {
|
||||
// responseQueue.clear(); // optional: clear old responses
|
||||
// getInstance().out.println(req.toString());
|
||||
// System.out.println("📤 [SEND] " + req.toString(2));
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// System.err.println("❌ Error sending request: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// public static Socket getSocket() {
|
||||
// return socket;
|
||||
// }
|
||||
//
|
||||
// public static void main(String[] args) {
|
||||
// new TelegramClient().start();
|
||||
// }
|
||||
//
|
||||
// public PrintWriter getOut() {
|
||||
// return out;
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
|
||||
|
||||
|
||||
package org.to.telegramfinalproject.Client;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class TelegramClient {
|
||||
private static final String SERVER_HOST = "localhost";
|
||||
private static final int SERVER_PORT = 8080;
|
||||
|
||||
private static TelegramClient instance;
|
||||
|
||||
private static Socket socket;
|
||||
private BufferedReader in;
|
||||
private PrintWriter out;
|
||||
|
||||
|
||||
private final Scanner scanner;
|
||||
private ActionHandler handler;
|
||||
|
||||
public static final BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>();
|
||||
public static final Map<String, BlockingQueue<JSONObject>> pendingResponses = new ConcurrentHashMap<>();
|
||||
public static UUID loggedInUserId = null;
|
||||
private DataInputStream inBin; // NEW
|
||||
private DataOutputStream outBin; // NEW
|
||||
private static SocketMediaDownloader downloader; // NEW
|
||||
public static final java.util.concurrent.atomic.AtomicBoolean mediaBusy = new java.util.concurrent.atomic.AtomicBoolean(false); // NEW
|
||||
|
||||
public static SocketMediaDownloader getDownloader() { return downloader; }
|
||||
public DataInputStream getInBin() { return inBin; }
|
||||
public DataOutputStream getOutBin() { return outBin; }
|
||||
|
||||
private volatile boolean listenerStarted = false;
|
||||
|
||||
public TelegramClient() {
|
||||
this.scanner = new Scanner(System.in);
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static synchronized TelegramClient getInstance() {
|
||||
if (instance == null) instance = new TelegramClient();
|
||||
return instance;
|
||||
}
|
||||
|
||||
// public void startConsole() {
|
||||
// try {
|
||||
// connectIfNeeded();
|
||||
// initHandlerIfNeeded();
|
||||
// startListenerOnce();
|
||||
// showMainMenu();
|
||||
// } catch (IOException e) {
|
||||
// System.err.println("❌ Error connecting to server: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
|
||||
public void startConsole() {
|
||||
try {
|
||||
connectIfNeeded();
|
||||
initHandlerIfNeeded();
|
||||
startListenerOnce(IncomingMessageListener.UIMode.CONSOLE); // ← کنسول
|
||||
showMainMenu();
|
||||
} catch (IOException e) {
|
||||
System.err.println("❌ Error connecting to server: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void start() { startConsole(); }
|
||||
|
||||
//UI only
|
||||
// public static synchronized TelegramClient getOrInitForUI() throws IOException {
|
||||
// TelegramClient cli = getInstance();
|
||||
// cli.connectIfNeeded();
|
||||
// cli.initHandlerIfNeeded();
|
||||
// cli.startListenerOnce();
|
||||
// return cli;
|
||||
// }
|
||||
|
||||
public static synchronized TelegramClient getOrInitForUI() throws IOException {
|
||||
TelegramClient cli = getInstance();
|
||||
cli.connectIfNeeded();
|
||||
cli.initHandlerIfNeeded();
|
||||
cli.startListenerOnce(IncomingMessageListener.UIMode.UI); // ← UI
|
||||
return cli;
|
||||
}
|
||||
|
||||
|
||||
private synchronized void connectIfNeeded() throws IOException {
|
||||
if (socket != null && socket.isConnected() && !socket.isClosed()) return;
|
||||
|
||||
socket = new Socket(SERVER_HOST, SERVER_PORT);
|
||||
InputStream rawIn = socket.getInputStream();
|
||||
OutputStream rawOut = socket.getOutputStream();
|
||||
|
||||
in = new BufferedReader(new InputStreamReader(rawIn, java.nio.charset.StandardCharsets.UTF_8));
|
||||
out = new PrintWriter(new OutputStreamWriter(rawOut, java.nio.charset.StandardCharsets.UTF_8), true);
|
||||
|
||||
// NEW: binary streams
|
||||
inBin = new DataInputStream(rawIn);
|
||||
outBin = new DataOutputStream(rawOut);
|
||||
|
||||
// NEW: media downloader helper
|
||||
downloader = new SocketMediaDownloader(out, inBin, outBin);
|
||||
|
||||
System.out.println("✅ Connected to Telegram Server");
|
||||
}
|
||||
|
||||
private synchronized void initHandlerIfNeeded() {
|
||||
if (handler == null) {
|
||||
handler = new ActionHandler(out, in, outBin, scanner);
|
||||
}
|
||||
}
|
||||
|
||||
// private void startListenerOnce() {
|
||||
// if (listenerStarted) return;
|
||||
// listenerStarted = true;
|
||||
// Thread listenerThread = new Thread(new IncomingMessageListener(in), "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"
|
||||
// );
|
||||
// listenerThread.setDaemon(true);
|
||||
// listenerThread.start();
|
||||
// }
|
||||
|
||||
private void startListenerOnce(IncomingMessageListener.UIMode mode) {
|
||||
if (listenerStarted) return;
|
||||
listenerStarted = true;
|
||||
|
||||
listener = new IncomingMessageListener(in, mode);
|
||||
Thread listenerThread = new Thread(listener, "socket-listener");
|
||||
listenerThread.setDaemon(true);
|
||||
listenerThread.start();
|
||||
}
|
||||
|
||||
//console
|
||||
private void showMainMenu() throws IOException {
|
||||
while (true) {
|
||||
System.out.println("Main Menu:");
|
||||
System.out.println("1. Register");
|
||||
System.out.println("2. Login");
|
||||
System.out.println("3. Exit");
|
||||
System.out.print("Choose an option: ");
|
||||
String choice = scanner.nextLine();
|
||||
|
||||
switch (choice) {
|
||||
case "1" -> handler.register();
|
||||
case "2" -> {
|
||||
handler.loginHandler();
|
||||
if (Session.currentUser != null) {
|
||||
System.out.println("✅ Login successful.");
|
||||
new Thread(new ActionHandler.ChatStateMonitor(out)).start();
|
||||
|
||||
UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||
loggedInUserId = internalId;
|
||||
|
||||
handler.userMenu(internalId);
|
||||
} else {
|
||||
System.out.println("❌ Login failed.");
|
||||
}
|
||||
}
|
||||
case "3" -> {
|
||||
System.out.println("Exiting...");
|
||||
return;
|
||||
}
|
||||
default -> System.out.println("Invalid choice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void send(JSONObject req) {
|
||||
try {
|
||||
responseQueue.clear();
|
||||
getInstance().out.println(req.toString());
|
||||
System.out.println("📤 [SEND] " + req.toString(2));
|
||||
} catch (Exception e) {
|
||||
System.err.println("❌ Error sending request: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedReader getIn() { return in; }
|
||||
public PrintWriter getOut() { return out; }
|
||||
public ActionHandler getHandler() { return handler; }
|
||||
public static Socket getSocket() { return socket; }
|
||||
|
||||
public static void main(String[] args) {
|
||||
new TelegramClient().startConsole();
|
||||
}
|
||||
|
||||
// TelegramClient.java
|
||||
private IncomingMessageListener listener;
|
||||
|
||||
public IncomingMessageListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ArchivedChatDatabase {
|
||||
public static boolean archiveChat(UUID userId, UUID chatId, String chatType) {
|
||||
String sql = "INSERT INTO archived_chats (user_id, chat_id, chat_type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, chatId);
|
||||
ps.setString(3, chatType);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean unarchiveChat(UUID userId, UUID chatId) {
|
||||
String sql = "DELETE FROM archived_chats WHERE user_id = ? AND chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, chatId);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isChatArchived(UUID userId, UUID chatId) {
|
||||
String sql = "SELECT 1 FROM archived_chats WHERE user_id = ? AND chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, chatId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<UUID> getArchivedChats(UUID userId) {
|
||||
List<UUID> list = new ArrayList<>();
|
||||
String sql = "SELECT chat_id FROM archived_chats WHERE user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
list.add(UUID.fromString(rs.getString("chat_id")));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.Channel;
|
||||
import org.to.telegramfinalproject.Models.Group;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelDatabase {
|
||||
public static List<Channel> getChannelsByUser(UUID internalUuid) {
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT c.* FROM channels c
|
||||
JOIN channel_subscribers cs ON c.internal_uuid = cs.channel_id
|
||||
WHERE cs.user_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, internalUuid);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
Channel channel = new Channel();
|
||||
|
||||
channel.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
channel.setChannel_id(rs.getString("channel_id"));
|
||||
channel.setChannel_name(rs.getString("channel_name"));
|
||||
channel.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
channel.setImage_url(rs.getString("image_url"));
|
||||
channel.setDescription(rs.getString("description"));
|
||||
channel.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
|
||||
channels.add(channel);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
|
||||
public static List<Channel> searchChannels(String keyword) {
|
||||
List<Channel> result = new ArrayList<>();
|
||||
String sql = "SELECT * FROM channels WHERE channel_name ILIKE ? OR channel_id ILIKE ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, "%" + keyword + "%");
|
||||
stmt.setString(2, keyword);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
Channel channel = new Channel(
|
||||
UUID.fromString(rs.getString("internal_uuid")),
|
||||
rs.getString("channel_name"),
|
||||
UUID.fromString(rs.getString("creator_id")),
|
||||
rs.getTimestamp("created_at").toLocalDateTime()
|
||||
);
|
||||
channel.setChannel_id(rs.getString("channel_id"));
|
||||
channel.setImage_url(rs.getString("image_url"));
|
||||
channel.setDescription(rs.getString("description"));
|
||||
result.add(channel);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<UUID> getSubscriberUUIDs(UUID channelInternalUUID) {
|
||||
List<UUID> subscriberIds = new ArrayList<>();
|
||||
String sql = "SELECT user_id FROM channel_subscribers WHERE channel_id = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelInternalUUID);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
subscriberIds.add((UUID) rs.getObject("user_id"));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return subscriberIds;
|
||||
}
|
||||
|
||||
public static Channel findByChannelId(String channelId) {
|
||||
String sql = "SELECT * FROM channels WHERE channel_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setString(1, channelId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
Channel channel = new Channel();
|
||||
channel.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
channel.setChannel_id(rs.getString("channel_id"));
|
||||
channel.setChannel_name(rs.getString("channel_name"));
|
||||
channel.setImage_url(rs.getString("image_url"));
|
||||
channel.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
channel.setDescription(rs.getString("description"));
|
||||
channel.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
return channel;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static boolean isUserSubscribed(UUID userId, UUID channelInternalId) {
|
||||
String sql = "SELECT * FROM channel_subscribers WHERE user_id = ? AND channel_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, channelInternalId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static UUID findInternalUUIDByChannelId(String channelId) {
|
||||
String sql = "SELECT internal_uuid FROM channels WHERE channel_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, channelId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("internal_uuid");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean addSubscriberToChannel(UUID userId, UUID channelUUID) {
|
||||
String sql = """
|
||||
INSERT INTO channel_subscribers (channel_id, user_id)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelUUID);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean createChannel(Channel channel, UUID creatorId) {
|
||||
String sql = """
|
||||
INSERT INTO channels (
|
||||
internal_uuid, channel_id, channel_name,
|
||||
creator_id, image_url, description, created_at
|
||||
)
|
||||
VALUES (gen_random_uuid(), ?, ?, ?, ?, ?, ?)
|
||||
RETURNING internal_uuid
|
||||
""";
|
||||
|
||||
String subscriberSql = """
|
||||
INSERT INTO channel_subscribers (channel_id, user_id) VALUES (?, ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
stmt.setString(1, channel.getChannel_id());
|
||||
stmt.setString(2, channel.getChannel_name());
|
||||
stmt.setObject(3, creatorId);
|
||||
stmt.setString(4, channel.getImage_url());
|
||||
stmt.setString(5, channel.getDescription());
|
||||
stmt.setObject(6, channel.getCreated_at());
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (!rs.next()) return false;
|
||||
|
||||
UUID internalUUID = (UUID) rs.getObject("internal_uuid");
|
||||
channel.setInternal_uuid(internalUUID);
|
||||
|
||||
PreparedStatement subStmt = conn.prepareStatement(subscriberSql);
|
||||
subStmt.setObject(1, internalUUID);
|
||||
subStmt.setObject(2, creatorId);
|
||||
subStmt.executeUpdate();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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)) {
|
||||
|
||||
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);
|
||||
} 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) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSubscriber(UUID channelId, UUID userId, String role) {
|
||||
String sql = "INSERT INTO channel_subscribers (channel_id, user_id, role) VALUES (?, ?, ?) ON CONFLICT DO NOTHING";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setString(3, role);
|
||||
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Channel findByInternalUUID(UUID internalUUID) {
|
||||
String sql = "SELECT * FROM channels WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, internalUUID);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
Channel channel = new Channel();
|
||||
channel.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
channel.setChannel_id(rs.getString("channel_id"));
|
||||
channel.setChannel_name(rs.getString("channel_name"));
|
||||
channel.setImage_url(rs.getString("image_url"));
|
||||
channel.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
channel.setDescription(rs.getString("description"));
|
||||
channel.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
return channel;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean addOwnerToChannel(UUID channelId, UUID userId) {
|
||||
String sql = "INSERT INTO channel_subscribers (channel_id, user_id, role) VALUES (?, ?, 'owner')";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean addAdminToChannel(UUID channelId, UUID userId, JSONObject permissions) {
|
||||
String sql = "UPDATE channel_subscribers SET role = 'admin', permissions = ?::jsonb WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, channelId);
|
||||
stmt.setObject(3, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getChannelRole(UUID channelId, UUID userId) {
|
||||
String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("role");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "subscriber"; // پیشفرض
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static JSONObject getChannelPermissions(UUID channelId, UUID userId) {
|
||||
String sql = "SELECT permissions FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return new JSONObject(rs.getString("permissions"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean updateChannelAdminPermissions(UUID channelId, UUID userId, JSONObject permissions) {
|
||||
String sql = "UPDATE channel_subscribers SET permissions = ?::jsonb WHERE channel_id = ? AND user_id = ? AND role = 'admin'";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, channelId);
|
||||
stmt.setObject(3, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<JSONObject> getChannelAdminsAndOwner(UUID channelId) {
|
||||
List<JSONObject> admins = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT u.internal_uuid, u.profile_name, u.user_id, cs.role, cs.permissions
|
||||
FROM channel_subscribers cs
|
||||
JOIN users u ON cs.user_id = u.internal_uuid
|
||||
WHERE cs.channel_id = ? AND (cs.role = 'owner' OR cs.role = 'admin')
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_uuid", rs.getObject("internal_uuid").toString());
|
||||
obj.put("profile_name", rs.getString("profile_name"));
|
||||
obj.put("user_id", rs.getString("user_id"));
|
||||
obj.put("role", rs.getString("role"));
|
||||
obj.put("permissions", new JSONObject(rs.getString("permissions")));
|
||||
admins.add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return admins;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isOwner(UUID channelId, UUID userId) {
|
||||
String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return "owner".equals(rs.getString("role"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAdmin(UUID channelId, UUID userId) {
|
||||
String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
String role = rs.getString("role");
|
||||
return "admin".equals(role) || "owner".equals(role); // owner هم admin هست
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isUserInChannel(UUID userId, UUID channelId) {
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT 1 FROM channel_subscribers WHERE channel_id = ? AND user_id = ?")) {
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean removeSubscriberFromChannel(UUID channelId, UUID userId) {
|
||||
String sql = "DELETE FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
|
||||
try (Connection conn =ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
int affectedRows = stmt.executeUpdate();
|
||||
return affectedRows > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
System.err.println("Error removing subscriber from channel: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static JSONArray getChannelSubscribers(UUID channelId) {
|
||||
JSONArray subscribers = new JSONArray();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT u.internal_uuid, u.user_id, u.profile_name, " +
|
||||
"CASE WHEN cs.role = 'owner' THEN 'owner' " +
|
||||
" WHEN cs.role = 'admin' THEN 'admin' " +
|
||||
" ELSE 'subscriber' END AS role " +
|
||||
"FROM channel_subscribers cs " +
|
||||
"JOIN users u ON cs.user_id = u.internal_uuid " +
|
||||
"WHERE cs.channel_id = ?")) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_uuid", rs.getObject("internal_uuid").toString());
|
||||
obj.put("user_id", rs.getString("user_id"));
|
||||
obj.put("profile_name", rs.getString("profile_name"));
|
||||
obj.put("role", rs.getString("role"));
|
||||
subscribers.put(obj);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean updateChannelInfo(UUID channelId, String newId, String name, String description, String imageUrl) {
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"UPDATE channels SET channel_id = ?, channel_name = ?, description = ?, image_url = ? WHERE internal_uuid = ?")) {
|
||||
|
||||
stmt.setString(1, newId);
|
||||
stmt.setString(2, name);
|
||||
stmt.setString(3, description);
|
||||
stmt.setString(4, imageUrl);
|
||||
stmt.setObject(5, channelId);
|
||||
|
||||
int rows = stmt.executeUpdate();
|
||||
return rows > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isChannelIdUnique(String channelId, UUID excludeChannelUUID) {
|
||||
String query = "SELECT COUNT(*) FROM channels WHERE channel_id = ? AND internal_uuid != ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
|
||||
stmt.setString(1, channelId);
|
||||
stmt.setObject(2, excludeChannelUUID);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
int count = rs.getInt(1);
|
||||
return count == 0;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean demoteAdminToSubscriber(UUID channelId, UUID userId) {
|
||||
String sql = "UPDATE channel_subscribers SET role = 'member', permissions = '{}'::jsonb WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
int affected = stmt.executeUpdate();
|
||||
return affected > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteChannel(UUID channelId) {
|
||||
String sql = "DELETE FROM channels WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
int affected = stmt.executeUpdate();
|
||||
return affected > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) {
|
||||
String updateRoles = """
|
||||
UPDATE channel_subscribers
|
||||
SET role = CASE
|
||||
WHEN user_id = ? THEN 'owner'
|
||||
WHEN role = 'owner' THEN 'admin'
|
||||
ELSE role
|
||||
END
|
||||
WHERE channel_id = ?
|
||||
""";
|
||||
|
||||
String clearPermissions = """
|
||||
UPDATE channel_subscribers
|
||||
SET permissions = '{}'::jsonb
|
||||
WHERE channel_id = ? AND user_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement roleStmt = conn.prepareStatement(updateRoles);
|
||||
PreparedStatement clearPermsStmt = conn.prepareStatement(clearPermissions)) {
|
||||
|
||||
roleStmt.setObject(1, newOwnerUUID);
|
||||
roleStmt.setObject(2, channelId);
|
||||
roleStmt.executeUpdate();
|
||||
|
||||
clearPermsStmt.setObject(1, channelId);
|
||||
clearPermsStmt.setObject(2, newOwnerUUID);
|
||||
clearPermsStmt.executeUpdate();
|
||||
|
||||
conn.commit();
|
||||
return true;
|
||||
|
||||
} catch (SQLException e) {
|
||||
conn.rollback();
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static List<UUID> getChannelSubscriberUUIDs(UUID channelId) {
|
||||
List<UUID> subscriberIds = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM channel_subscribers WHERE channel_id = ?")) {
|
||||
stmt.setObject(1, channelId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
subscriberIds.add(UUID.fromString(rs.getString("user_id")));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return subscriberIds;
|
||||
}
|
||||
|
||||
public static boolean updateAdminPermissions(UUID channelId, UUID userId, JSONObject permissions) {
|
||||
String sql = "UPDATE channel_subscribers SET permissions = ?::jsonb WHERE channel_id = ? AND user_id = ? AND role = 'admin'";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, channelId);
|
||||
stmt.setObject(3, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONObject getChannelInfo(UUID channelId, UUID viewerUuid) throws SQLException {
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
// === Channel header (name, subscriber count, etc.)
|
||||
String channelQuery = """
|
||||
SELECT c.internal_uuid, c.channel_id, c.channel_name, c.image_url, c.description,
|
||||
COUNT(s.user_id) as subscriber_count
|
||||
FROM channels c
|
||||
LEFT JOIN channel_subscribers s ON c.internal_uuid = s.channel_id
|
||||
WHERE c.internal_uuid = ?
|
||||
GROUP BY c.internal_uuid, c.channel_id, c.channel_name, c.image_url, c.description
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(channelQuery)) {
|
||||
ps.setObject(1, channelId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
result.put("internal_uuid", rs.getString("internal_uuid"));
|
||||
result.put("channel_id", rs.getString("channel_id")); // varchar if needed
|
||||
result.put("channel_name", rs.getString("channel_name"));
|
||||
result.put("subscriber_count", rs.getInt("subscriber_count"));
|
||||
result.put("image_url", rs.getString("image_url"));
|
||||
result.put("description", rs.getString("description")); // ✅ here
|
||||
} else {
|
||||
return null; // no such channel
|
||||
}
|
||||
}
|
||||
|
||||
// === Subscribers (id, name, role, status, image)
|
||||
JSONArray subscribersArr = new JSONArray();
|
||||
|
||||
String subscribersQuery = """
|
||||
SELECT u.internal_uuid, u.profile_name, u.user_id, u.image_url,
|
||||
cs.role, u.status, u.last_seen
|
||||
FROM channel_subscribers cs
|
||||
JOIN users u ON cs.user_id = u.internal_uuid
|
||||
WHERE cs.channel_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(subscribersQuery)) {
|
||||
ps.setObject(1, channelId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
JSONObject sub = new JSONObject();
|
||||
sub.put("user_id", rs.getString("internal_uuid"));
|
||||
sub.put("profile_name", rs.getString("profile_name"));
|
||||
sub.put("username", rs.getString("user_id"));
|
||||
sub.put("image_url", rs.getString("image_url"));
|
||||
sub.put("role", rs.getString("role"));
|
||||
sub.put("status", rs.getString("status"));
|
||||
sub.put("last_seen", rs.getString("last_seen"));
|
||||
subscribersArr.put(sub);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("subscribers", subscribersArr);
|
||||
|
||||
// === Viewer role (so UI knows if viewer can manage/delete)
|
||||
String roleQuery = """
|
||||
SELECT role
|
||||
FROM channel_subscribers
|
||||
WHERE channel_id = ? AND user_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(roleQuery)) {
|
||||
ps.setObject(1, channelId);
|
||||
ps.setObject(2, viewerUuid);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
result.put("my_role", rs.getString("role"));
|
||||
} else {
|
||||
result.put("my_role", ""); // not a subscriber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatInfoDatabase {
|
||||
|
||||
/**
|
||||
* هدر چت را بر اساس نوع آن برمیگرداند.
|
||||
* private: name,image_url, online,last_seen
|
||||
* group : name,image_url, member_count
|
||||
* channel: name,image_url, member_count
|
||||
*/
|
||||
public static JSONObject getHeaderInfo(String type, UUID receiverId, UUID viewerId) throws SQLException {
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
switch (type.toLowerCase()) {
|
||||
case "private":
|
||||
return getPrivateHeader(conn, receiverId, viewerId);
|
||||
case "group":
|
||||
return getGroupHeader(conn, receiverId);
|
||||
case "channel":
|
||||
return getChannelHeader(conn, receiverId);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported receiver_type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** هدر چت خصوصی: other-user نسبت به viewerId را پیدا میکنیم */
|
||||
private static JSONObject getPrivateHeader(Connection conn, UUID chatId, UUID viewerId) throws SQLException {
|
||||
if (viewerId == null) throw new IllegalArgumentException("viewerId is required for private header.");
|
||||
|
||||
final String sql = """
|
||||
SELECT
|
||||
u.profile_name AS name,
|
||||
COALESCE(u.image_url, '') AS image_url,
|
||||
(u.status = 'online') AS online,
|
||||
u.status AS status,
|
||||
u.last_seen AS last_seen
|
||||
FROM private_chat pc
|
||||
JOIN users u
|
||||
ON u.internal_uuid = CASE WHEN pc.user1_id = ? THEN pc.user2_id ELSE pc.user1_id END
|
||||
WHERE pc.chat_id = ?
|
||||
AND (pc.user1_id = ? OR pc.user2_id = ?)
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
int i = 1;
|
||||
ps.setObject(i++, viewerId);
|
||||
ps.setObject(i++, chatId);
|
||||
ps.setObject(i++, viewerId);
|
||||
ps.setObject(i++, viewerId);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Private chat not found or viewer is not a participant.");
|
||||
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "private");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
|
||||
// خواندن بولینِ online با سازگاری DB
|
||||
Object onlineObj = rs.getObject("online");
|
||||
boolean online = (onlineObj instanceof Boolean) ? (Boolean) onlineObj
|
||||
: rs.getInt("online") == 1;
|
||||
j.put("online", online);
|
||||
j.put("status", rs.getString("status")); // رشتهی وضعیت هم میآید
|
||||
|
||||
Timestamp ts = rs.getTimestamp("last_seen");
|
||||
j.put("last_seen", ts == null ? JSONObject.NULL : ts.toInstant().toString());
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** هدر گروه: نام، عکس و تعداد اعضا */
|
||||
private static JSONObject getGroupHeader(Connection conn, UUID groupInternalId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT
|
||||
g.group_name AS name,
|
||||
COALESCE(g.image_url, '') AS image_url,
|
||||
(SELECT COUNT(*) FROM group_members gm WHERE gm.group_id = g.internal_uuid) AS member_count
|
||||
FROM groups g
|
||||
WHERE g.internal_uuid = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, groupInternalId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Group not found.");
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "group");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
j.put("member_count", rs.getInt("member_count"));
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** هدر کانال: نام، عکس و تعداد سابسکرایبر */
|
||||
private static JSONObject getChannelHeader(Connection conn, UUID channelInternalId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT
|
||||
c.channel_name AS name,
|
||||
COALESCE(c.image_url, '') AS image_url,
|
||||
(SELECT COUNT(*) FROM channel_subscribers cs WHERE cs.channel_id = c.internal_uuid) AS member_count
|
||||
FROM channels c
|
||||
WHERE c.internal_uuid = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, channelInternalId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Channel not found.");
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "channel");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
j.put("member_count", rs.getInt("member_count"));
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ConnectionDb {
|
||||
private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram";
|
||||
private static final String USERNAME = "postgres";
|
||||
private static final String PASSWORD = "Partow@1384";
|
||||
|
||||
public ConnectionDb() {
|
||||
}
|
||||
|
||||
public static Connection connect() throws SQLException {
|
||||
return DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.Contact;
|
||||
import org.to.telegramfinalproject.Models.ContactEntry;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ContactDatabase {
|
||||
public ContactDatabase(){}
|
||||
|
||||
|
||||
private static Connection getConnection() throws SQLException {
|
||||
return ConnectionDb.connect();
|
||||
}
|
||||
|
||||
public static boolean addContact(UUID userId, UUID contactId) {
|
||||
String sql = """
|
||||
INSERT INTO contacts (user_id, contact_id)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, contactId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<UUID> getContactUUIDs(UUID userId) {
|
||||
List<Contact> contacts = getContacts(userId);
|
||||
List<UUID> contactIds = new ArrayList<>();
|
||||
for (Contact c : contacts) {
|
||||
contactIds.add(c.getContact_id());
|
||||
}
|
||||
return contactIds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean removeContact(UUID user_id, UUID contact_id) {
|
||||
String sql = "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?";
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setObject(1, user_id);
|
||||
stmt.setObject(2, contact_id);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean toggleBlock(UUID userId, UUID targetId) {
|
||||
String selectSql = "SELECT is_blocked FROM contacts WHERE user_id = ? AND contact_id = ?";
|
||||
String updateSql = "UPDATE contacts SET is_blocked = ? WHERE user_id = ? AND contact_id = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement selectStmt = conn.prepareStatement(selectSql)) {
|
||||
|
||||
selectStmt.setObject(1, userId);
|
||||
selectStmt.setObject(2, targetId);
|
||||
|
||||
ResultSet rs = selectStmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
boolean currentlyBlocked = rs.getBoolean("is_blocked");
|
||||
|
||||
try (PreparedStatement updateStmt = conn.prepareStatement(updateSql)) {
|
||||
updateStmt.setBoolean(1, !currentlyBlocked);
|
||||
updateStmt.setObject(2, userId);
|
||||
updateStmt.setObject(3, targetId);
|
||||
updateStmt.executeUpdate();
|
||||
}
|
||||
return !currentlyBlocked;
|
||||
} else {
|
||||
String insertSql = "INSERT INTO contacts (user_id, contact_id, is_blocked) VALUES (?, ?, ?)";
|
||||
try (PreparedStatement insertStmt = conn.prepareStatement(insertSql)) {
|
||||
insertStmt.setObject(1, userId);
|
||||
insertStmt.setObject(2, targetId);
|
||||
insertStmt.setBoolean(3, true);
|
||||
insertStmt.executeUpdate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static List<JSONObject> getBlockedUsers(UUID userId) {
|
||||
String sql = """
|
||||
SELECT u.internal_uuid, u.user_id, u.profile_name
|
||||
FROM contacts c
|
||||
JOIN users u ON u.internal_uuid = c.contact_id
|
||||
WHERE c.user_id = ? AND c.is_blocked = TRUE
|
||||
ORDER BY u.profile_name NULLS LAST, u.user_id
|
||||
""";
|
||||
List<JSONObject> out = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("internal_uuid", rs.getObject("internal_uuid").toString());
|
||||
j.put("user_id", rs.getString("user_id"));
|
||||
j.put("profile_name", rs.getString("profile_name"));
|
||||
out.add(j);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static UUID findOtherUserInPrivateChat(UUID chatId, UUID viewerId) {
|
||||
if (chatId == null || viewerId == null) return null;
|
||||
|
||||
final String sql = """
|
||||
SELECT user1_id, user2_id
|
||||
FROM private_chat
|
||||
WHERE chat_id = ?
|
||||
""";
|
||||
|
||||
try (Connection cn = getConnection();
|
||||
PreparedStatement ps = cn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, chatId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
|
||||
UUID u1 = (UUID) rs.getObject("user1_id");
|
||||
UUID u2 = (UUID) rs.getObject("user2_id");
|
||||
|
||||
if (viewerId.equals(u1)) return u2;
|
||||
if (viewerId.equals(u2)) return u1;
|
||||
|
||||
// اگر viewer عضو این چت نیست
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unblockContact(UUID user_id, UUID contact_id) {
|
||||
String sql = "UPDATE contacts SET is_blocked = FALSE WHERE user_id = ? AND contact_id = ?";
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setObject(1, user_id);
|
||||
stmt.setObject(2, contact_id);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Contact> getContacts(UUID user_id) {
|
||||
List<Contact> contacts = new ArrayList<>();
|
||||
String sql = "SELECT * FROM contacts WHERE user_id = ?";
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setObject(1, user_id);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
UUID contactId = (UUID) rs.getObject("contact_id");
|
||||
boolean isBlocked = rs.getBoolean("is_blocked");
|
||||
Timestamp addedAt = rs.getTimestamp("added_at");
|
||||
|
||||
Contact contact = new Contact(user_id, contactId);
|
||||
contact.setIs_blocked(isBlocked);
|
||||
contact.setAdd_at(addedAt.toLocalDateTime());
|
||||
contacts.add(contact);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
|
||||
public static boolean existsContact(UUID user_id, UUID contact_id) {
|
||||
String sql = "SELECT 1 FROM contacts WHERE user_id = ? AND contact_id = ? LIMIT 1"; // stop searching when find the first item in DB(LIMIT 1)
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setObject(1, user_id);
|
||||
stmt.setObject(2, contact_id);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean eitherBlocks(UUID userA, UUID userB) {
|
||||
return isBlocked(userA, userB) || isBlocked(userB, userA);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean isBlocked(UUID user_id, UUID contact_id) {
|
||||
String sql = "SELECT is_blocked FROM contacts WHERE user_id = ? AND contact_id = ?";
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setObject(1, user_id);
|
||||
stmt.setObject(2, contact_id);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getBoolean("is_blocked");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean deleteChatOneSide(UUID currentUserId, UUID otherUserId) {
|
||||
String sql = """
|
||||
UPDATE private_chat
|
||||
SET user1_deleted = CASE WHEN user1_id = ? THEN TRUE ELSE user1_deleted END,
|
||||
user2_deleted = CASE WHEN user2_id = ? THEN TRUE ELSE user2_deleted END
|
||||
WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, currentUserId);
|
||||
stmt.setObject(2, currentUserId);
|
||||
stmt.setObject(3, currentUserId);
|
||||
stmt.setObject(4, otherUserId);
|
||||
stmt.setObject(5, otherUserId);
|
||||
stmt.setObject(6, currentUserId);
|
||||
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteChatBoth(UUID currentUserId, UUID otherUserId) {
|
||||
String sqlDeleteMessages = """
|
||||
DELETE FROM messages
|
||||
WHERE receiver_type = 'private' AND (
|
||||
(sender_id = ? AND receiver_id = ?) OR
|
||||
(sender_id = ? AND receiver_id = ?)
|
||||
)
|
||||
""";
|
||||
|
||||
String sqlDeleteChat = """
|
||||
DELETE FROM private_chat
|
||||
WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = getConnection()) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement stmtMsg = conn.prepareStatement(sqlDeleteMessages);
|
||||
PreparedStatement stmtChat = conn.prepareStatement(sqlDeleteChat)) {
|
||||
|
||||
stmtMsg.setObject(1, currentUserId);
|
||||
stmtMsg.setObject(2, otherUserId);
|
||||
stmtMsg.setObject(3, otherUserId);
|
||||
stmtMsg.setObject(4, currentUserId);
|
||||
stmtMsg.executeUpdate();
|
||||
|
||||
stmtChat.setObject(1, currentUserId);
|
||||
stmtChat.setObject(2, otherUserId);
|
||||
stmtChat.setObject(3, otherUserId);
|
||||
stmtChat.setObject(4, currentUserId);
|
||||
stmtChat.executeUpdate();
|
||||
|
||||
conn.commit();
|
||||
return true;
|
||||
|
||||
} catch (SQLException e) {
|
||||
conn.rollback();
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ContactEntry> getContactEntries(UUID userId) {
|
||||
List<ContactEntry> entries = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT c.contact_id, c.is_blocked, u.user_id, u.profile_name, u.image_url
|
||||
FROM contacts c
|
||||
JOIN users u ON c.contact_id = u.internal_uuid
|
||||
WHERE c.user_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
UUID contactId = UUID.fromString(rs.getString("contact_id"));
|
||||
boolean isBlocked = rs.getBoolean("is_blocked");
|
||||
String userIdStr = rs.getString("user_id");
|
||||
String profileName = rs.getString("profile_name");
|
||||
String imageUrl = rs.getString("image_url");
|
||||
|
||||
ContactEntry entry = new ContactEntry(contactId, userIdStr, profileName, imageUrl, isBlocked);
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static List<ContactEntry> searchContacts(UUID userId, String searchTerm) {
|
||||
List<ContactEntry> results = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT u.internal_uuid, u.user_id, c.is_blocked
|
||||
FROM contacts c
|
||||
JOIN users u ON c.contact_id = u.internal_uuid
|
||||
WHERE c.user_id = ?
|
||||
AND (u.user_id ILIKE ? OR u.profile_name ILIKE ?)
|
||||
""";
|
||||
|
||||
try (Connection connection = getConnection();
|
||||
PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setString(2, "%" + searchTerm + "%");
|
||||
stmt.setString(3, "%" + searchTerm + "%");
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
userDatabase userDB = new userDatabase();
|
||||
while (rs.next()) {
|
||||
UUID contactId = (UUID) rs.getObject("internal_uuid");
|
||||
String displayId = rs.getString("user_id");
|
||||
String contact_displayId = userDB.getUserId(contactId);
|
||||
String profileName = userDB.getProfileName(contactId);
|
||||
String imageUrl = userDB.getProfilePicture(contactId);
|
||||
boolean isBlocked = rs.getBoolean("is_blocked");
|
||||
|
||||
String lastSeenString = userDB.getLastSeen(contactId);
|
||||
|
||||
// Convert to LocalDateTime
|
||||
LocalDateTime lastSeen = null;
|
||||
if (!"Unknown".equals(lastSeenString)) {
|
||||
lastSeen = LocalDateTime.parse(lastSeenString);
|
||||
}
|
||||
|
||||
results.add(new ContactEntry(contactId, displayId, contact_displayId, profileName, imageUrl, isBlocked, lastSeen));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,784 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.Group;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
public class GroupDatabase {
|
||||
|
||||
public static List<Group> getGroupsByUser(UUID internalUuid) {
|
||||
List<Group> groups = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT g.* FROM groups g
|
||||
JOIN group_members gm ON g.internal_uuid = gm.group_id
|
||||
WHERE gm.user_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, internalUuid);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
Group group = new Group();
|
||||
|
||||
group.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
group.setGroup_id(rs.getString("group_id"));
|
||||
group.setGroup_name(rs.getString("group_name"));
|
||||
group.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
group.setImage_url(rs.getString("image_url"));
|
||||
group.setDescription(rs.getString("description"));
|
||||
group.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
public static List<Group> searchGroups(String keyword) {
|
||||
List<Group> result = new ArrayList<>();
|
||||
String sql = "SELECT * FROM groups WHERE group_name ILIKE ? OR group_id ILIKE ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, "%" + keyword + "%");
|
||||
stmt.setString(2, keyword);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
Group group = new Group(
|
||||
UUID.fromString(rs.getString("internal_uuid")),
|
||||
rs.getString("group_name"),
|
||||
UUID.fromString(rs.getString("creator_id")),
|
||||
rs.getTimestamp("created_at").toLocalDateTime()
|
||||
);
|
||||
group.setGroup_id(rs.getString("group_id"));
|
||||
group.setImage_url(rs.getString("image_url"));
|
||||
group.setDescription(rs.getString("description"));
|
||||
result.add(group);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<UUID> getMemberUUIDs(UUID groupInternalUUID) {
|
||||
List<UUID> memberIds = new ArrayList<>();
|
||||
String sql = "SELECT user_id FROM group_members WHERE group_id = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupInternalUUID);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
memberIds.add((UUID) rs.getObject("user_id"));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return memberIds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Group findByGroupId(String groupId) {
|
||||
String sql = "SELECT * FROM groups WHERE group_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setString(1, groupId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
Group group = new Group();
|
||||
group.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
group.setGroup_id(rs.getString("group_id"));
|
||||
group.setGroup_name(rs.getString("group_name"));
|
||||
group.setImage_url(rs.getString("image_url"));
|
||||
group.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
group.setDescription(rs.getString("description"));
|
||||
group.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
return group;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean isUserInGroup(UUID userId, UUID groupInternalId) {
|
||||
String sql = "SELECT * FROM group_members WHERE user_id = ? AND group_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, groupInternalId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean updateGroupInfo(UUID internalUUID, String newGroupId, String name, String description, String imageUrl) {
|
||||
String sql = "UPDATE groups SET group_id = ?, group_name = ?, description = ?, image_url = ? WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, newGroupId);
|
||||
stmt.setString(2, name);
|
||||
stmt.setString(3, description);
|
||||
if (imageUrl == null) {
|
||||
stmt.setNull(4, Types.VARCHAR);
|
||||
} else {
|
||||
stmt.setString(4, imageUrl);
|
||||
}
|
||||
stmt.setObject(5, internalUUID);
|
||||
|
||||
int affectedRows = stmt.executeUpdate();
|
||||
return affectedRows > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isGroupIdUnique(String groupId, UUID excludeUUID) {
|
||||
String sql = "SELECT COUNT(*) FROM groups WHERE group_id = ? AND internal_uuid != ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, groupId);
|
||||
stmt.setObject(2, excludeUUID);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1) == 0;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void addMember(UUID groupInternalId, UUID userId) {
|
||||
String sql = "INSERT INTO group_members (group_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupInternalId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public static UUID findInternalUUIDByGroupId(String groupId) {
|
||||
String sql = "SELECT internal_uuid FROM groups WHERE group_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, groupId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("internal_uuid");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean addMemberToGroup(UUID userId, UUID groupUUID) {
|
||||
String sql = """
|
||||
INSERT INTO group_members (group_id, user_id)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupUUID);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static boolean createGroup(Group group, UUID creatorId) {
|
||||
String sql = """
|
||||
INSERT INTO groups (
|
||||
internal_uuid, group_id, group_name,
|
||||
creator_id, image_url, description, created_at
|
||||
)
|
||||
VALUES (gen_random_uuid(), ?, ?, ?, ?, ?, ?)
|
||||
RETURNING internal_uuid
|
||||
""";
|
||||
|
||||
String memberSql = """
|
||||
INSERT INTO group_members (group_id, user_id) VALUES (?, ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
stmt.setString(1, group.getGroup_id());
|
||||
stmt.setString(2, group.getGroup_name());
|
||||
stmt.setObject(3, creatorId);
|
||||
stmt.setString(4, group.getImage_url());
|
||||
stmt.setString(5, group.getDescription());
|
||||
stmt.setObject(6, group.getCreated_at());
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (!rs.next()) return false;
|
||||
|
||||
UUID internalUUID = (UUID) rs.getObject("internal_uuid");
|
||||
group.setInternal_uuid(internalUUID);
|
||||
|
||||
PreparedStatement memberStmt = conn.prepareStatement(memberSql);
|
||||
memberStmt.setObject(1, internalUUID);
|
||||
memberStmt.setObject(2, creatorId);
|
||||
memberStmt.executeUpdate();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean insertGroup(UUID internalUUID, String groupId, String groupName, UUID creatorId, String imageUrl, LocalDateTime createdAt) {
|
||||
String sql = "INSERT INTO groups (internal_uuid, group_id, group_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, groupId);
|
||||
stmt.setString(3, groupName);
|
||||
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 void addMember(UUID groupId, UUID userId, String role) {
|
||||
String sql = "INSERT INTO group_members (group_id, user_id, role) VALUES (?, ?, ?) ON CONFLICT DO NOTHING";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setString(3, role);
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Group findByInternalUUID(UUID internalUUID) {
|
||||
String sql = "SELECT * FROM groups WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, internalUUID);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
Group group = new Group();
|
||||
group.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid")));
|
||||
group.setGroup_id(rs.getString("group_id"));
|
||||
group.setGroup_name(rs.getString("group_name"));
|
||||
group.setImage_url(rs.getString("image_url"));
|
||||
group.setCreator_id(UUID.fromString(rs.getString("creator_id")));
|
||||
group.setDescription(rs.getString("description"));
|
||||
group.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime());
|
||||
return group;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean addOwnerToGroup(UUID groupId, UUID userId) {
|
||||
String sql = "INSERT INTO group_members (group_id, user_id, role) VALUES (?, ?, 'owner')";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean addAdminToGroup(UUID groupId, UUID userId, JSONObject permissions) {
|
||||
String sql = """
|
||||
UPDATE group_members
|
||||
SET role = 'admin',
|
||||
permissions = ?::jsonb
|
||||
WHERE group_id = ? AND user_id = ? AND role = 'member'
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, groupId);
|
||||
stmt.setObject(3, userId);
|
||||
|
||||
return stmt.executeUpdate() > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String getGroupRole(UUID groupId, UUID userId) {
|
||||
String sql = "SELECT role FROM group_members WHERE group_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return rs.getString("role");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "member";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static boolean updateGroupAdminPermissions(UUID groupId, UUID userId, JSONObject permissions) {
|
||||
String sql = """
|
||||
UPDATE group_members
|
||||
SET permissions = ?::jsonb
|
||||
WHERE group_id = ? AND user_id = ? AND role = 'admin'
|
||||
""";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, groupId);
|
||||
stmt.setObject(3, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<JSONObject> getGroupAdminsAndOwner(UUID groupId) {
|
||||
List<JSONObject> admins = new ArrayList<>();
|
||||
|
||||
String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " +
|
||||
"FROM group_members gm " +
|
||||
"JOIN users u ON gm.user_id = u.internal_uuid " +
|
||||
"WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, groupId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("user_id", rs.getObject("user_id").toString());
|
||||
obj.put("role", rs.getString("role"));
|
||||
obj.put("permissions", new JSONObject(rs.getString("permissions")));
|
||||
obj.put("profile_name", rs.getString("profile_name"));
|
||||
admins.add(obj);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return admins;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isOwner(UUID groupId, UUID userId) {
|
||||
String sql = "SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ? AND role = 'owner'";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAdmin(UUID groupId, UUID userId) {
|
||||
String sql = "SELECT role FROM group_members WHERE group_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
String role = rs.getString("role");
|
||||
return "admin".equals(role) || "owner".equals(role);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static JSONObject getGroupPermissions(UUID groupId, UUID userId) {
|
||||
String sql = "SELECT permissions FROM group_members WHERE group_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
String permissions = rs.getString("permissions");
|
||||
if (permissions != null && !permissions.isBlank()) {
|
||||
return new JSONObject(permissions);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static JSONArray getGroupMembers(UUID groupId) {
|
||||
String sql = """
|
||||
SELECT u.profile_name, u.user_id, u.internal_uuid, gm.role, gm.permissions
|
||||
FROM group_members gm
|
||||
JOIN users u ON gm.user_id = u.internal_uuid
|
||||
WHERE gm.group_id = ?
|
||||
""";
|
||||
|
||||
JSONArray members = new JSONArray();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, groupId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
JSONObject member = new JSONObject();
|
||||
member.put("profile_name", rs.getString("profile_name"));
|
||||
member.put("user_id", rs.getString("user_id"));
|
||||
member.put("internal_uuid", rs.getObject("internal_uuid").toString());
|
||||
member.put("role", rs.getString("role"));
|
||||
|
||||
String permissions = rs.getString("permissions");
|
||||
if (permissions != null && !permissions.isBlank()) {
|
||||
member.put("permissions", new JSONObject(permissions));
|
||||
}
|
||||
|
||||
members.put(member);
|
||||
}
|
||||
|
||||
return members;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean demoteAdminToMember(UUID groupId, UUID userId) {
|
||||
String sql = "UPDATE group_members SET role = 'member', permissions = '{}'::jsonb WHERE group_id = ? AND user_id = ? AND role = 'admin'";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeMemberFromGroup(UUID groupId, UUID userId) {
|
||||
String sql = "DELETE FROM group_members WHERE group_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean transferOwnership(UUID groupId, UUID newOwnerId) {
|
||||
String demoteOldOwner = "UPDATE group_members SET role = 'admin' WHERE group_id = ? AND role = 'owner'";
|
||||
String promoteNewOwner = "UPDATE group_members SET role = 'owner', permissions = '{}'::jsonb WHERE group_id = ? AND user_id = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement demoteStmt = conn.prepareStatement(demoteOldOwner);
|
||||
PreparedStatement promoteStmt = conn.prepareStatement(promoteNewOwner)) {
|
||||
|
||||
demoteStmt.setObject(1, groupId);
|
||||
demoteStmt.executeUpdate();
|
||||
|
||||
promoteStmt.setObject(1, groupId);
|
||||
promoteStmt.setObject(2, newOwnerId);
|
||||
promoteStmt.executeUpdate();
|
||||
|
||||
conn.commit();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
conn.rollback();
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static boolean deleteGroup(UUID groupId) {
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement("DELETE FROM groups WHERE internal_uuid = ?")) {
|
||||
|
||||
stmt.setObject(1, groupId);
|
||||
int affectedRows = stmt.executeUpdate();
|
||||
|
||||
return affectedRows > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<UUID> getGroupMemberUUIDs(UUID groupId) {
|
||||
List<UUID> memberIds = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM group_members WHERE group_id = ?")) {
|
||||
stmt.setObject(1, groupId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
memberIds.add(UUID.fromString(rs.getString("user_id")));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memberIds;
|
||||
}
|
||||
|
||||
public static boolean updateAdminPermissions(UUID groupId, UUID userId, JSONObject permissions) {
|
||||
String sql = "UPDATE group_members SET permissions = ?::jsonb WHERE group_id = ? AND user_id = ? AND role = 'admin'";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, permissions.toString());
|
||||
stmt.setObject(2, groupId);
|
||||
stmt.setObject(3, userId);
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isMember(UUID groupId, UUID userId) {
|
||||
String sql = "SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// public static List<User> searchGroupMembers(UUID groupId, String keyword) {
|
||||
// List<User> result = new ArrayList<>();
|
||||
// String sql = """
|
||||
// SELECT u.*
|
||||
// FROM users u
|
||||
// JOIN group_members gm ON gm.user_id = u.internal_uuid
|
||||
// WHERE gm.group_id = ?
|
||||
// AND (
|
||||
// LOWER(u.profile_name) LIKE ?
|
||||
// OR LOWER(u.username) LIKE ?
|
||||
// OR LOWER(u.user_id) LIKE ?
|
||||
// )
|
||||
// """;
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
//
|
||||
// stmt.setObject(1, groupId);
|
||||
// String likePattern = "%" + keyword.toLowerCase() + "%";
|
||||
// stmt.setString(2, likePattern);
|
||||
// stmt.setString(3, likePattern);
|
||||
// stmt.setString(4, likePattern);
|
||||
//
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// User user = User.fromResultSet(rs);
|
||||
// result.add(user);
|
||||
// }
|
||||
//
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
public static JSONObject getGroupInfo(UUID groupId, UUID viewerUuid) throws SQLException {
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
// === Group header (name, member count, etc.)
|
||||
String groupQuery = """
|
||||
SELECT g.internal_uuid, g.group_id, g.group_name, g.image_url,
|
||||
COUNT(m.user_id) as member_count
|
||||
FROM groups g
|
||||
LEFT JOIN group_members m ON g.internal_uuid = m.group_id
|
||||
WHERE g.internal_uuid = ?
|
||||
GROUP BY g.internal_uuid, g.group_id, g.group_name, g.image_url
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(groupQuery)) {
|
||||
ps.setObject(1, groupId); // ✅ compare UUID with UUID
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
result.put("internal_uuid", rs.getString("internal_uuid"));
|
||||
result.put("group_id", rs.getString("group_id")); // varchar handle
|
||||
result.put("group_name", rs.getString("group_name"));
|
||||
result.put("member_count", rs.getInt("member_count"));
|
||||
result.put("image_url", rs.getString("image_url"));
|
||||
} else {
|
||||
return null; // no such group
|
||||
}
|
||||
}
|
||||
|
||||
// === Members (id, name, role, status, image)
|
||||
JSONArray membersArr = new JSONArray();
|
||||
|
||||
String membersQuery = """
|
||||
SELECT u.internal_uuid, u.profile_name, u.user_id, u.image_url,
|
||||
gm.role, u.status, u.last_seen
|
||||
FROM group_members gm
|
||||
JOIN users u ON gm.user_id = u.internal_uuid
|
||||
WHERE gm.group_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(membersQuery)) {
|
||||
ps.setObject(1, groupId); // ✅ gm.group_id is UUID
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
JSONObject member = new JSONObject();
|
||||
member.put("user_id", rs.getString("internal_uuid"));
|
||||
member.put("profile_name", rs.getString("profile_name"));
|
||||
member.put("username", rs.getString("user_id"));
|
||||
member.put("image_url", rs.getString("image_url"));
|
||||
member.put("role", rs.getString("role"));
|
||||
member.put("status", rs.getString("status"));
|
||||
member.put("last_seen", rs.getString("last_seen"));
|
||||
membersArr.put(member);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("members", membersArr);
|
||||
|
||||
// === Viewer role (to decide if they can delete group, etc.)
|
||||
String roleQuery = """
|
||||
SELECT role
|
||||
FROM group_members
|
||||
WHERE group_id = ? AND user_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(roleQuery)) {
|
||||
ps.setObject(1, groupId);
|
||||
ps.setObject(2, viewerUuid);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
result.put("my_role", rs.getString("role"));
|
||||
} else {
|
||||
result.put("my_role", ""); // not a member
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static JSONObject getAdminPermissions(UUID groupId, UUID adminId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT permissions
|
||||
FROM group_members
|
||||
WHERE group_id = ? AND user_id = ? AND role = 'admin'
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, groupId);
|
||||
ps.setObject(2, adminId);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String jsonStr = rs.getString("permissions");
|
||||
if (jsonStr != null && !jsonStr.isBlank()) {
|
||||
return new JSONObject(jsonStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // not found or no permissions
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Models.MediaRow;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MessageReactionDatabase {
|
||||
|
||||
public static boolean saveOrUpdateReaction(UUID messageId, UUID userId, String reaction) {
|
||||
String sql = """
|
||||
INSERT INTO message_reactions (message_id, user_id, emoji)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT (message_id, user_id)
|
||||
DO UPDATE SET emoji = EXCLUDED.emoji, reacted_at = CURRENT_TIMESTAMP
|
||||
""";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ps.setObject(2, userId);
|
||||
ps.setString(3, reaction);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getReactions(UUID messageId) {
|
||||
String sql = "SELECT emoji FROM message_reactions WHERE message_id = ?";
|
||||
List<String> reactions = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
reactions.add(rs.getString("emoji"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return reactions;
|
||||
}
|
||||
public static JSONObject getCountsAsJson(UUID messageId) {
|
||||
String sql = """
|
||||
SELECT emoji, COUNT(*) AS c
|
||||
FROM message_reactions
|
||||
WHERE message_id = ?
|
||||
GROUP BY emoji
|
||||
""";
|
||||
|
||||
JSONObject counts = new JSONObject();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, messageId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String emoji = rs.getString("emoji");
|
||||
int c = rs.getInt("c");
|
||||
counts.put(emoji, c);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return counts; // مثال: {"❤️":2,"👍":1}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.to.telegramfinalproject.Models.PrivateChat;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PrivateChatDatabase {
|
||||
|
||||
|
||||
public static List<UUID> getMembers(UUID privateChatId) {
|
||||
String sql = "SELECT user1_id, user2_id FROM private_chat WHERE chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, privateChatId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
UUID user1 = (UUID) rs.getObject("user1_id");
|
||||
UUID user2 = (UUID) rs.getObject("user2_id");
|
||||
return new ArrayList<>(Arrays.asList(user1, user2));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static UUID getOrCreateSavedMessagesChat(UUID userId) {
|
||||
String query = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, userId); // Saved Messages is self-chat
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("chat_id"); // Chat already exists
|
||||
} else {
|
||||
// Create a new chat
|
||||
UUID chatId = UUID.randomUUID();
|
||||
String insert = "INSERT INTO private_chat (chat_id, user1_id, user2_id) VALUES (?, ?, ?)";
|
||||
try (PreparedStatement insertStmt = conn.prepareStatement(insert)) {
|
||||
insertStmt.setObject(1, chatId);
|
||||
insertStmt.setObject(2, userId);
|
||||
insertStmt.setObject(3, userId);
|
||||
insertStmt.executeUpdate();
|
||||
return chatId;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static UUID findChatIdByUsers(UUID user1, UUID user2) {
|
||||
UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2;
|
||||
UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1;
|
||||
|
||||
String sql = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, u1);
|
||||
ps.setObject(2, u2);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("chat_id");
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static UUID getOrCreateChat(UUID user1, UUID user2) {
|
||||
UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2;
|
||||
UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1;
|
||||
|
||||
String select = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(select)) {
|
||||
ps.setObject(1, u1);
|
||||
ps.setObject(2, u2);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return UUID.fromString(rs.getString("chat_id"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
UUID newChatId = UUID.randomUUID();
|
||||
String insert = "INSERT INTO private_chat(chat_id, user1_id, user2_id) VALUES (?, ?, ?)";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(insert)) {
|
||||
ps.setObject(1, newChatId);
|
||||
ps.setObject(2, u1);
|
||||
ps.setObject(3, u2);
|
||||
ps.executeUpdate();
|
||||
return newChatId;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<PrivateChat> findChatsOfUser(UUID userId) {
|
||||
List<PrivateChat> chats = new ArrayList<>();
|
||||
String sql = "SELECT * FROM private_chat WHERE user1_id = ? OR user2_id = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, userId);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
UUID chatId = (UUID) rs.getObject("chat_id");
|
||||
UUID user1 = (UUID) rs.getObject("user1_id");
|
||||
UUID user2 = (UUID) rs.getObject("user2_id");
|
||||
|
||||
chats.add(new PrivateChat(chatId, user1, user2));
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return chats;
|
||||
}
|
||||
|
||||
|
||||
public static UUID getOtherUserInChat(UUID chatId, UUID currentUserId) {
|
||||
String sql = "SELECT user1_id, user2_id FROM private_chat WHERE chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, chatId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
UUID user1 = (UUID) rs.getObject("user1_id");
|
||||
UUID user2 = (UUID) rs.getObject("user2_id");
|
||||
return currentUserId.equals(user1) ? user2 : user1;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static PrivateChat extractPrivateChat(ResultSet rs) throws SQLException {
|
||||
UUID chatId = UUID.fromString(rs.getString("chat_id"));
|
||||
UUID user1 = rs.getObject("user1_id", UUID.class);
|
||||
UUID user2 = rs.getObject("user2_id", UUID.class);
|
||||
boolean user1Deleted = rs.getBoolean("user1_deleted");
|
||||
boolean user2Deleted = rs.getBoolean("user2_deleted");
|
||||
LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime();
|
||||
|
||||
return new PrivateChat(chatId, user1, user2, user1Deleted, user2Deleted, createdAt);
|
||||
}
|
||||
|
||||
public static void markChatDeleted(UUID userId, UUID chatId) {
|
||||
String sql = """
|
||||
UPDATE private_chat
|
||||
SET user1_deleted = CASE WHEN user1_id = ? THEN true ELSE user1_deleted END,
|
||||
user2_deleted = CASE WHEN user2_id = ? THEN true ELSE user2_deleted END
|
||||
WHERE chat_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setObject(3, chatId);
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void unmarkChatDeleted(UUID userId, UUID chatId) {
|
||||
String sql = """
|
||||
UPDATE private_chat
|
||||
SET user1_deleted = CASE WHEN user1_id = ? THEN false ELSE user1_deleted END,
|
||||
user2_deleted = CASE WHEN user2_id = ? THEN false ELSE user2_deleted END
|
||||
WHERE chat_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setObject(3, chatId);
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void markUser1Deleted(UUID chatId) {
|
||||
updateBoolField(chatId, "user1_deleted", true);
|
||||
}
|
||||
|
||||
public static void markUser2Deleted(UUID chatId) {
|
||||
updateBoolField(chatId, "user2_deleted", true);
|
||||
}
|
||||
|
||||
public static void markBothDeleted(UUID chatId) {
|
||||
String sql = "UPDATE private_chat SET user1_deleted = true, user2_deleted = true WHERE chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, chatId);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateBoolField(UUID chatId, String field, boolean value) {
|
||||
String sql = "UPDATE private_chat SET " + field + " = ? WHERE chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setBoolean(1, value);
|
||||
ps.setObject(2, chatId);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static PrivateChat findById(UUID chatId) {
|
||||
String sql = "SELECT * FROM private_chat WHERE chat_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, chatId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
UUID user1_id = (UUID) rs.getObject("user1_id");
|
||||
UUID user2_id = (UUID) rs.getObject("user2_id");
|
||||
boolean user1_deleted = rs.getBoolean("user1_deleted");
|
||||
boolean user2_deleted = rs.getBoolean("user2_deleted");
|
||||
LocalDateTime created_at = rs.getTimestamp("created_at").toLocalDateTime();
|
||||
|
||||
return new PrivateChat(chatId, user1_id, user2_id, user1_deleted, user2_deleted, created_at);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void clearDeletedFlag(UUID senderId, UUID chatId) {
|
||||
String sql = """
|
||||
UPDATE private_chat
|
||||
SET user1_deleted = CASE WHEN user1_id = ? THEN FALSE ELSE user1_deleted END,
|
||||
user2_deleted = CASE WHEN user2_id = ? THEN FALSE ELSE user2_deleted END
|
||||
WHERE chat_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, senderId);
|
||||
ps.setObject(2, senderId);
|
||||
ps.setObject(3, chatId);
|
||||
int rows = ps.executeUpdate();
|
||||
System.out.println("✅ clearDeletedFlag updated rows = " + rows);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static UUID findChatBetween(UUID user1, UUID user2) {
|
||||
String sql = """
|
||||
SELECT chat_id FROM private_chat
|
||||
WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, user1);
|
||||
ps.setObject(2, user2);
|
||||
ps.setObject(3, user2);
|
||||
ps.setObject(4, user1);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("chat_id");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// public static UUID getOtherParticipant(UUID chatId, UUID me) {
|
||||
// List<UUID> members = getMembers(chatId);
|
||||
// for (UUID u : members) {
|
||||
// if (!u.equals(me)) return u;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
public static UUID getOtherParticipant(UUID chatId, UUID me) {
|
||||
List<UUID> members = getMembers(chatId); // باید [user1_id, user2_id] بده
|
||||
if (members == null || members.isEmpty()) return null;
|
||||
|
||||
boolean isMember = false;
|
||||
UUID other = null;
|
||||
|
||||
for (UUID u : members) {
|
||||
if (u == null) continue;
|
||||
if (u.equals(me)) {
|
||||
isMember = true;
|
||||
} else {
|
||||
other = u;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMember) return null;
|
||||
return (other != null) ? other
|
||||
: me;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static PrivateChat findSelfChat(UUID userId) {
|
||||
String sql = """
|
||||
SELECT chat_id, user1_id, user2_id, user1_deleted, user2_deleted
|
||||
FROM private_chat
|
||||
WHERE user1_id = ? AND user2_id = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (Connection c = ConnectionDb.connect();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, userId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return map(rs);
|
||||
}
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
public static UUID createSelfChat(UUID userId) {
|
||||
String sql = """
|
||||
INSERT INTO private_chat (chat_id, user1_id, user2_id, user1_deleted, user2_deleted, created_at)
|
||||
VALUES (gen_random_uuid(), ?, ?, FALSE, FALSE, NOW())
|
||||
RETURNING chat_id
|
||||
""";
|
||||
try (Connection c = ConnectionDb.connect();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ps.setObject(2, userId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return (UUID) rs.getObject("chat_id");
|
||||
}
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static PrivateChat map(ResultSet rs) throws SQLException {
|
||||
return new PrivateChat(
|
||||
(UUID) rs.getObject("chat_id"),
|
||||
(UUID) rs.getObject("user1_id"),
|
||||
(UUID) rs.getObject("user2_id"),
|
||||
rs.getBoolean("user1_deleted"),
|
||||
rs.getBoolean("user2_deleted")
|
||||
);
|
||||
}
|
||||
public static boolean isParticipant(java.util.UUID chatId, java.util.UUID userId) {
|
||||
String sql = """
|
||||
SELECT 1
|
||||
FROM private_chat
|
||||
WHERE chat_id = ?
|
||||
AND (user1_id = ? OR user2_id = ?)
|
||||
LIMIT 1
|
||||
""";
|
||||
try (var c = ConnectionDb.connect();
|
||||
var ps = c.prepareStatement(sql)) {
|
||||
ps.setObject(1, chatId);
|
||||
ps.setObject(2, userId);
|
||||
ps.setObject(3, userId);
|
||||
try (var rs = ps.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class userDatabase {
|
||||
|
||||
|
||||
public userDatabase() {
|
||||
}
|
||||
|
||||
public static boolean isUserOnline(UUID userId) {
|
||||
String sql = "SELECT status FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
String status = rs.getString("status");
|
||||
return "online".equalsIgnoreCase(status);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Connection getConnection() throws SQLException {
|
||||
return ConnectionDb.connect();
|
||||
}
|
||||
|
||||
public static String getLastSeen(UUID userId) {
|
||||
String sql = "SELECT last_seen FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
Timestamp lastSeen = rs.getTimestamp("last_seen");
|
||||
if (lastSeen != null) {
|
||||
return lastSeen.toLocalDateTime().toString();
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
||||
public User findByUserId(String userId) {
|
||||
String query = "SELECT * FROM users WHERE user_id = ?";
|
||||
|
||||
try {
|
||||
User var6;
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
stmt.setString(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (!rs.next()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var6 = this.extractUser(rs);
|
||||
}
|
||||
}
|
||||
|
||||
return var6;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public User findByUsername(String username) {
|
||||
String query = "SELECT * FROM users WHERE username = ?";
|
||||
|
||||
try {
|
||||
User var6;
|
||||
try (Connection conn = this.getConnection()) {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
stmt.setString(1, username);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
if (!rs.next()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var6 = this.extractUser(rs);
|
||||
}
|
||||
}
|
||||
|
||||
return var6;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean existsByUsername(String username) {
|
||||
String query = "SELECT 1 FROM users WHERE username = ?";
|
||||
|
||||
try {
|
||||
boolean var6;
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
) {
|
||||
stmt.setString(1, username);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
var6 = rs.next();
|
||||
}
|
||||
|
||||
return var6;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean existsByUserId(String user_id) {
|
||||
String query = "SELECT 1 FROM users WHERE user_id = ?";
|
||||
|
||||
try {
|
||||
boolean var6;
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
) {
|
||||
stmt.setString(1, user_id);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
var6 = rs.next();
|
||||
}
|
||||
|
||||
return var6;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean save(User user) {
|
||||
String query = "INSERT INTO users (user_id, internal_uuid, username, password, profile_name, bio, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try {
|
||||
boolean var5;
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
) {
|
||||
stmt.setString(1, user.getUser_id());
|
||||
stmt.setObject(2, user.getInternal_uuid());
|
||||
stmt.setString(3, user.getUsername());
|
||||
stmt.setString(4, user.getPassword());
|
||||
stmt.setString(5, user.getProfile_name());
|
||||
stmt.setString(6, user.getBio());
|
||||
stmt.setString(7, user.getImage_url());
|
||||
var5 = stmt.executeUpdate() > 0;
|
||||
}
|
||||
|
||||
return var5;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean updateByUUID(UUID uuid, User user) {
|
||||
String query = "UPDATE users SET user_id=?, username=?, password=?, profile_name=?, bio=?, image_url=?, status=?, last_seen=? WHERE internal_uuid=?";
|
||||
|
||||
try {
|
||||
boolean var6;
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
) {
|
||||
stmt.setString(1, user.getUser_id());
|
||||
stmt.setString(2, user.getUsername());
|
||||
stmt.setString(3, user.getPassword());
|
||||
stmt.setString(4, user.getProfile_name());
|
||||
stmt.setString(5, user.getBio());
|
||||
stmt.setString(6, user.getImage_url());
|
||||
stmt.setString(7, user.getStatus());
|
||||
stmt.setObject(8, user.getLast_seen());
|
||||
stmt.setObject(9, uuid);
|
||||
var6 = stmt.executeUpdate() > 0;
|
||||
}
|
||||
|
||||
return var6;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<User> getAll() {
|
||||
List<User> users = new ArrayList();
|
||||
String query = "SELECT * FROM users";
|
||||
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
) {
|
||||
while(rs.next()) {
|
||||
users.add(this.extractUser(rs));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
private User extractUser(ResultSet rs) throws SQLException {
|
||||
return new User(rs.getString("user_id"), UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), rs.getString("profile_name"), rs.getString("bio"), rs.getString("image_url"));
|
||||
}
|
||||
|
||||
public boolean deleteByUUID(UUID uuid) {
|
||||
String query = "DELETE FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try {
|
||||
boolean var5;
|
||||
try (
|
||||
Connection conn = this.getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query);
|
||||
) {
|
||||
stmt.setObject(1, uuid);
|
||||
var5 = stmt.executeUpdate() > 0;
|
||||
}
|
||||
|
||||
return var5;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void updateUserStatus(UUID uuid, String status) {
|
||||
String sql = "UPDATE users SET status = ? WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setString(1, status);
|
||||
stmt.setObject(2, uuid);
|
||||
int rows = stmt.executeUpdate();
|
||||
System.out.println("🔁 updateUserStatus: set '" + status + "' for " + uuid + " → affected rows = " + rows);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void updateLastSeen(UUID uuid) {
|
||||
String sql = "UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, uuid);
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static User findByInternalUUID(UUID internalUuid) {
|
||||
String sql = "SELECT * FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, internalUuid);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
User user = new User(
|
||||
rs.getString("user_id"),
|
||||
UUID.fromString(rs.getString("internal_uuid")),
|
||||
rs.getString("username"),
|
||||
rs.getString("password"),
|
||||
rs.getString("profile_name"),
|
||||
rs.getString("bio"),
|
||||
rs.getString("image_url")
|
||||
);
|
||||
|
||||
user.setBio(rs.getString("bio"));
|
||||
user.setImage_url(rs.getString("image_url"));
|
||||
user.setStatus(rs.getString("status"));
|
||||
|
||||
Timestamp lastSeenTs = rs.getTimestamp("last_seen");
|
||||
if (lastSeenTs != null) {
|
||||
user.setLast_seen(lastSeenTs.toLocalDateTime());
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<User> searchUsers(String keyword, UUID currentUserId) {
|
||||
String query = """
|
||||
SELECT * FROM users
|
||||
WHERE (user_id ILIKE ? OR profile_name ILIKE ?)
|
||||
AND internal_uuid <> ?
|
||||
""";
|
||||
|
||||
List<User> result = new ArrayList<>();
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||
|
||||
stmt.setString(1, "%" + keyword + "%");
|
||||
stmt.setString(2, "%" + keyword + "%");
|
||||
stmt.setObject(3, currentUserId);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractUser(rs));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void setAllUsersOffline() {
|
||||
String sql = "UPDATE users SET status = 'offline'";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
int affected = stmt.executeUpdate();
|
||||
System.out.println("🔁 All users set to offline. Rows affected: " + affected);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getProfileName(UUID userId) {
|
||||
String sql = "SELECT profile_name FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
String profileName = rs.getString("profile_name");
|
||||
return profileName;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public static String getProfilePicture(UUID userId) {
|
||||
String sql = "SELECT image_url FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
String imageUrl = rs.getString("image_url");
|
||||
return imageUrl;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public static String getUserId(UUID userId) {
|
||||
String sql = "SELECT user_id FROM users WHERE internal_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
String user_id = rs.getString("user_id");
|
||||
return user_id;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public static String getPasswordHash(UUID userId) {
|
||||
String sql = "SELECT password FROM users WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getString("password");
|
||||
}
|
||||
} catch (SQLException e) { e.printStackTrace(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean updateUsername(UUID userId, String newUsername) throws SQLException {
|
||||
String sql = "UPDATE users SET username = ? WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, newUsername);
|
||||
ps.setObject(2, userId);
|
||||
ps.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
// 23505 = unique_violation در PostgreSQL
|
||||
if ("23505".equals(e.getSQLState())) throw e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean updatePasswordHash(UUID userId, String newHash) {
|
||||
String sql = "UPDATE users SET password = ? WHERE internal_uuid = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, newHash);
|
||||
ps.setObject(2, userId);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) { e.printStackTrace(); return false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.to.telegramfinalproject;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HelloApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
|
||||
stage.setTitle("Hello!");
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.to.telegramfinalproject;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
public class HelloController {
|
||||
@FXML
|
||||
private Label welcomeText;
|
||||
|
||||
@FXML
|
||||
protected void onHelloButtonClick() {
|
||||
welcomeText.setText("Welcome to JavaFX Application!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Channel {
|
||||
private UUID internal_uuid;
|
||||
private String channel_id;
|
||||
private String channel_name;
|
||||
private UUID creator_id;
|
||||
private String image_url;
|
||||
private String description;
|
||||
private LocalDateTime created_at;
|
||||
private List<ChannelSubscribe> members;
|
||||
|
||||
public Channel(UUID internal_uuid, String channel_name, UUID creator_id, LocalDateTime created_at){
|
||||
this.internal_uuid = internal_uuid;
|
||||
this.channel_name = channel_name;
|
||||
this.creator_id = creator_id;
|
||||
this.created_at = created_at;
|
||||
}
|
||||
|
||||
public Channel() {
|
||||
|
||||
}
|
||||
|
||||
public void setChannel_id(String Channel_id){this.channel_id = Channel_id;}
|
||||
public void setCreator_id(UUID creator_id){this.creator_id = creator_id;}
|
||||
public void setChannel_name(String channel_name){this.channel_name = channel_name;}
|
||||
public void setImage_url(String image_url){this.image_url = image_url;}
|
||||
public void setCreated_at(LocalDateTime created_at){this.created_at = created_at;}
|
||||
public void setDescription(String description){this.description =description;}
|
||||
public void setMembers(List<ChannelSubscribe> members){this.members = members;}
|
||||
|
||||
public String getChannel_id(){return this.channel_id;}
|
||||
public UUID getCreator_id(){return this.creator_id;}
|
||||
public String getChannel_name(){return this.channel_name;}
|
||||
public String getImage_url(){return this.image_url;}
|
||||
public LocalDateTime getCreated_at(){return this.created_at;}
|
||||
public String getDescription(){return this.description;}
|
||||
public List<ChannelSubscribe> getMembers(){return this.members;}
|
||||
public UUID getInternal_uuid() {
|
||||
return this.internal_uuid;
|
||||
}
|
||||
|
||||
public void setInternal_uuid(UUID internalUuid) { this.internal_uuid = internalUuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelAdmin {
|
||||
private String channelId;
|
||||
private UUID userId;
|
||||
private LocalDateTime addedAt;
|
||||
private UUID addedBy;
|
||||
private String role; // e.g., "owner", "admin"
|
||||
private boolean canPostMessage;
|
||||
private boolean canEditSetting;
|
||||
private boolean canDeleteMessages;
|
||||
private boolean canDeleteMembers;
|
||||
private boolean canAddMembers;
|
||||
|
||||
public ChannelAdmin(String channelId, UUID userId, LocalDateTime addedAt, UUID addedBy,
|
||||
String role, boolean canPostMessage, boolean canEditSetting, boolean canDeleteMessages,
|
||||
boolean canDeleteMembers, boolean canAddMembers) {
|
||||
this.channelId = channelId;
|
||||
this.userId = userId;
|
||||
this.addedAt = addedAt;
|
||||
this.addedBy = addedBy;
|
||||
this.role = role;
|
||||
this.canPostMessage = canPostMessage;
|
||||
this.canEditSetting = canEditSetting;
|
||||
this.canDeleteMessages = canDeleteMessages;
|
||||
this.canDeleteMembers = canDeleteMembers;
|
||||
this.canAddMembers = canAddMembers;
|
||||
}
|
||||
|
||||
// Overloaded constructor with default values from SQL
|
||||
public ChannelAdmin(String channelId, UUID userId, UUID addedBy) {
|
||||
this.channelId = channelId;
|
||||
this.userId = userId;
|
||||
this.addedBy = addedBy;
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public void setChannelId(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(UUID userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public LocalDateTime getAddedAt() {
|
||||
return addedAt;
|
||||
}
|
||||
|
||||
public void setAddedAt(LocalDateTime addedAt) {
|
||||
this.addedAt = addedAt;
|
||||
}
|
||||
|
||||
public UUID getAddedBy() {
|
||||
return addedBy;
|
||||
}
|
||||
|
||||
public void setAddedBy(UUID addedBy) {
|
||||
this.addedBy = addedBy;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public boolean getCanPostMessage() {
|
||||
return canPostMessage;
|
||||
}
|
||||
|
||||
public void setCanPostMessage(boolean canPostMessage) {
|
||||
this.canPostMessage = canPostMessage;
|
||||
}
|
||||
|
||||
public boolean getCanEditSetting() {
|
||||
return canEditSetting;
|
||||
}
|
||||
|
||||
public void setCanEditSetting(boolean canEditSetting) {
|
||||
this.canEditSetting = canEditSetting;
|
||||
}
|
||||
|
||||
public boolean getCanDeleteMessages() {
|
||||
return canDeleteMessages;
|
||||
}
|
||||
|
||||
public void setCanDeleteMessages(boolean canDeleteMessages) {
|
||||
this.canDeleteMessages = canDeleteMessages;
|
||||
}
|
||||
|
||||
public boolean getCanDeleteMembers() {
|
||||
return canDeleteMembers;
|
||||
}
|
||||
|
||||
public void setCanDeleteMembers(boolean canDeleteMembers) {
|
||||
this.canDeleteMembers = canDeleteMembers;
|
||||
}
|
||||
|
||||
public boolean getCanAddMembers() {
|
||||
return canAddMembers;
|
||||
}
|
||||
|
||||
public void setCanAddMembers(boolean canAddMembers) {
|
||||
this.canAddMembers = canAddMembers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChannelAdmin{" +
|
||||
"channelId='" + channelId + '\'' +
|
||||
", userId=" + userId +
|
||||
", addedAt=" + addedAt +
|
||||
", addedBy=" + addedBy +
|
||||
", role='" + role + '\'' +
|
||||
", canPost=" + canPostMessage +
|
||||
", canEdit=" + canEditSetting +
|
||||
", canDeleteMessages=" + canDeleteMessages +
|
||||
", canDeleteMembers=" + canDeleteMembers +
|
||||
", canAddMembers=" + canAddMembers +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelSubscribe{
|
||||
private UUID channel_id;
|
||||
private UUID user_id;
|
||||
private LocalDateTime Subscribed_at;
|
||||
|
||||
|
||||
public ChannelSubscribe(UUID channel_id, UUID user_id, String role){
|
||||
this.channel_id = channel_id;
|
||||
this.user_id = user_id;
|
||||
this.Subscribed_at = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void setChannel_id(UUID group_id){this.channel_id = group_id;}
|
||||
public void setUser_id(UUID user_id){this.user_id = user_id;}
|
||||
public void setJoin_at(LocalDateTime join_at){this.Subscribed_at = join_at;}
|
||||
|
||||
|
||||
|
||||
public UUID getChannel_id(){return this.channel_id;}
|
||||
public UUID getUser_id(){return this.user_id;}
|
||||
public LocalDateTime getJoin_at(){return this.Subscribed_at;}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatEntry {
|
||||
private UUID internalId;
|
||||
private String displayId;
|
||||
private String name;
|
||||
private String imageUrl;
|
||||
private String type;
|
||||
private LocalDateTime lastMessageTime;
|
||||
private boolean archived = false;
|
||||
private UUID otherUser;
|
||||
private boolean savedMessages = false;
|
||||
|
||||
|
||||
private boolean isOwner = false;
|
||||
private boolean isAdmin = false;
|
||||
private JSONObject permissions;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// private UUID otherUserId;
|
||||
private int unreadCount;
|
||||
private String lastMessagePreview;
|
||||
private String lastMessageType; // TEXT / IMAGE / AUDIO / VIDEO / ...
|
||||
private UUID lastMessageSenderId;
|
||||
|
||||
|
||||
|
||||
public ChatEntry(UUID internalId, String displayId, String name, String imageUrl, String type, LocalDateTime lastMessageTime) {
|
||||
this.internalId = internalId;
|
||||
this.displayId = displayId;
|
||||
this.name = name;
|
||||
this.imageUrl = imageUrl;
|
||||
this.type = type;
|
||||
this.lastMessageTime = lastMessageTime;
|
||||
}
|
||||
|
||||
public ChatEntry(UUID internalId, String displayId, String name, String imageUrl, String type, LocalDateTime lastMessageTime, boolean isOwner, boolean isAdmin) {
|
||||
this(internalId, displayId, name, imageUrl, type, lastMessageTime);
|
||||
this.isOwner = isOwner;
|
||||
this.isAdmin = isAdmin;
|
||||
this.permissions = permissions;
|
||||
|
||||
}
|
||||
|
||||
public ChatEntry() {
|
||||
|
||||
}
|
||||
|
||||
public ChatEntry(UUID internalId, String type, String name, String displayId) {
|
||||
this.internalId = internalId;
|
||||
this.type = type;
|
||||
this.displayId = displayId;
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean isOwner() {
|
||||
return isOwner;
|
||||
}
|
||||
|
||||
public void setOwner(boolean owner) {
|
||||
isOwner = owner;
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
public void setAdmin(boolean admin) {
|
||||
isAdmin = admin;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return internalId;
|
||||
}
|
||||
|
||||
|
||||
public String getDisplayId() {
|
||||
return displayId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastMessageTime() {
|
||||
return lastMessageTime;
|
||||
}
|
||||
|
||||
|
||||
public JSONObject getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(JSONObject permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public void setName(String name) {this.name = name;
|
||||
}
|
||||
|
||||
public void setDisplayId(String id) {this.displayId = id;
|
||||
}
|
||||
|
||||
public void setImageUrl(String image_url) {this.imageUrl = image_url;
|
||||
}
|
||||
|
||||
public void setType(String type) {this.type =type;
|
||||
}
|
||||
|
||||
public void setId(String internalId) {this.internalId = UUID.fromString(internalId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isArchived() {
|
||||
return archived;
|
||||
}
|
||||
|
||||
public void setArchived(boolean archived) {
|
||||
this.archived = archived;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setLastMessageTime(String newTime) {
|
||||
if (newTime == null || newTime.isBlank()) {
|
||||
this.lastMessageTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.lastMessageTime = LocalDateTime.parse(newTime);
|
||||
} catch (Exception e) {
|
||||
System.out.println("❌ Failed to parse lastMessageTime: " + newTime);
|
||||
this.lastMessageTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOtherUserId(UUID otherId) {this.otherUser = otherId;
|
||||
}
|
||||
|
||||
public UUID getOtherUserId(){
|
||||
return otherUser;
|
||||
}
|
||||
|
||||
public boolean isSavedMessages() {
|
||||
return savedMessages;
|
||||
}
|
||||
|
||||
public void setSavedMessages(boolean savedMessages) {
|
||||
this.savedMessages = savedMessages;
|
||||
}
|
||||
|
||||
|
||||
public int getUnreadCount() { return unreadCount; }
|
||||
public void setUnreadCount(int unreadCount) { this.unreadCount = unreadCount; }
|
||||
|
||||
public String getLastMessagePreview() { return lastMessagePreview; }
|
||||
public void setLastMessagePreview(String lastMessagePreview) { this.lastMessagePreview = lastMessagePreview; }
|
||||
|
||||
public String getLastMessageType() { return lastMessageType; }
|
||||
public void setLastMessageType(String lastMessageType) { this.lastMessageType = lastMessageType; }
|
||||
|
||||
public UUID getLastMessageSenderId() { return lastMessageSenderId; }
|
||||
public void setLastMessageSenderId(UUID lastMessageSenderId) { this.lastMessageSenderId = lastMessageSenderId; }
|
||||
|
||||
public void setUnread(int unreadCount){this.unreadCount = unreadCount;}
|
||||
public int getUnread(){return unreadCount;}
|
||||
|
||||
|
||||
public void setLastMessageTime(LocalDateTime t) {
|
||||
this.lastMessageTime = t;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static ChatEntry fromServer(UUID internalId,
|
||||
String type,
|
||||
String name,
|
||||
String displayId,
|
||||
String imageUrl,
|
||||
boolean isOwner,
|
||||
boolean isAdmin) {
|
||||
ChatEntry e = new ChatEntry(internalId, type, name, displayId); // اگر سازندهات فرق دارد، مطابق آن بساز
|
||||
e.setImageUrl(imageUrl);
|
||||
e.setOwner(isOwner);
|
||||
e.setAdmin(isAdmin);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Contact {
|
||||
private UUID user_id;
|
||||
private UUID contact_id;
|
||||
private LocalDateTime added_at;
|
||||
private Boolean is_blocked;
|
||||
|
||||
public Contact(UUID user_id, UUID contact_id){
|
||||
this.user_id = user_id;
|
||||
this.contact_id = contact_id;
|
||||
this.added_at = LocalDateTime.now();
|
||||
}
|
||||
|
||||
|
||||
public void setUser_id(UUID user_id){this.user_id = user_id;}
|
||||
public void setContact_id(UUID contact_id){this.contact_id = contact_id;}
|
||||
public void setAdd_at(LocalDateTime add_at){this.added_at =add_at;}
|
||||
public void setIs_blocked(Boolean is_blocked){this.is_blocked =is_blocked;}
|
||||
|
||||
public UUID getUser_id(){
|
||||
return this.user_id;
|
||||
}
|
||||
|
||||
public UUID getContact_id(){return this.contact_id;}
|
||||
|
||||
public LocalDateTime getAdd_at() {
|
||||
return added_at;
|
||||
}
|
||||
public Boolean getIs_blocked(){
|
||||
return is_blocked;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import org.json.JSONObject;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ContactEntry {
|
||||
private UUID contactId; // internal UUID
|
||||
private String userId; // public ID
|
||||
private String profileName;
|
||||
private String imageUrl;
|
||||
private boolean isBlocked;
|
||||
private LocalDateTime lastSeenTime;
|
||||
private String contact_displayId;
|
||||
|
||||
|
||||
public ContactEntry(UUID contactId, String userId, String profileName, String imageUrl, boolean isBlocked) {
|
||||
this.contactId = contactId;
|
||||
this.userId = userId;
|
||||
this.profileName = profileName;
|
||||
this.imageUrl = imageUrl;
|
||||
this.isBlocked = isBlocked;
|
||||
}
|
||||
|
||||
public ContactEntry(UUID contactId, String userId,String contact_displayId , String profileName, String imageUrl, boolean isBlocked, LocalDateTime lastSeenTime){
|
||||
this.contactId = contactId;
|
||||
this.userId = userId;
|
||||
this.contact_displayId = contact_displayId;
|
||||
this.profileName = profileName;
|
||||
this.imageUrl = imageUrl;
|
||||
this.isBlocked = isBlocked;
|
||||
this.lastSeenTime = lastSeenTime;
|
||||
}
|
||||
|
||||
public UUID getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return isBlocked;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastSeenTime() {
|
||||
return lastSeenTime;
|
||||
}
|
||||
|
||||
public void setLastSeenTime(LocalDateTime lastSeenTime) {
|
||||
this.lastSeenTime = lastSeenTime;
|
||||
}
|
||||
|
||||
public String getContact_displayId() {
|
||||
return contact_displayId;
|
||||
}
|
||||
|
||||
public void setContact_displayId(String contact_displayId) {
|
||||
this.contact_displayId = contact_displayId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return profileName + " (@" + contact_displayId + ")" + (isBlocked ? " [Blocked]" : "") + " Last Seen:" + (lastSeenTime != null ? " " + lastSeenTime.toString() : "");
|
||||
}
|
||||
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("contact_id", contactId.toString());
|
||||
obj.put("user_id", userId);
|
||||
obj.put("profile_name", profileName);
|
||||
obj.put("image_url", imageUrl);
|
||||
obj.put("is_blocked", isBlocked);
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ContactRequestModel {
|
||||
private String event;
|
||||
private String contactId;
|
||||
private String userId;
|
||||
|
||||
public ContactRequestModel(String event, String contactId, String userId) {
|
||||
this.event = event;
|
||||
this.contactId = contactId;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("event", event);
|
||||
json.put("contact_id", contactId);
|
||||
json.put("user_id", userId);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FileAttachment {
|
||||
|
||||
private UUID attachmentId; // اختیاری؛ اگر null بود، تولید میکنیم
|
||||
private UUID mediaKey;
|
||||
private String fileUrl;
|
||||
private String fileType; // IMAGE, VIDEO, AUDIO, FILE, GIF, STICKER
|
||||
private String fileName;
|
||||
private Long fileSize;
|
||||
private String mimeType; // e.g., image/png
|
||||
private Integer width;
|
||||
private Integer height;
|
||||
private Integer durationSeconds; // for audio/video
|
||||
private String thumbnailUrl;
|
||||
private String storagePath;
|
||||
|
||||
public FileAttachment(String fileUrl,
|
||||
String fileType,
|
||||
String fileName,
|
||||
Long fileSize,
|
||||
String mimeType,
|
||||
Integer width,
|
||||
Integer height,
|
||||
Integer durationSeconds,
|
||||
String thumbnailUrl) {
|
||||
this.fileUrl = fileUrl;
|
||||
this.fileType = fileType;
|
||||
this.fileName = fileName;
|
||||
this.fileSize = fileSize;
|
||||
this.mimeType = mimeType;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.durationSeconds = durationSeconds;
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
}
|
||||
|
||||
public FileAttachment(String fileUrl, String fileType) {
|
||||
this(fileUrl, fileType, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public FileAttachment() {
|
||||
}
|
||||
|
||||
|
||||
// ساخت از JSON /upload
|
||||
public static FileAttachment fromUploadJson(JSONObject j) {
|
||||
return new FileAttachment(
|
||||
j.optString("file_url", ""),
|
||||
j.optString("file_type", "FILE"),
|
||||
emptyToNull(j.optString("file_name", null)),
|
||||
j.has("file_size") && !j.isNull("file_size") ? j.getLong("file_size") : null,
|
||||
emptyToNull(j.optString("mime_type", null)),
|
||||
j.has("width") && !j.isNull("width") ? j.getInt("width") : null,
|
||||
j.has("height") && !j.isNull("height") ? j.getInt("height") : null,
|
||||
j.has("duration_seconds") && !j.isNull("duration_seconds") ? j.getInt("duration_seconds") : null,
|
||||
j.isNull("thumbnail_url") ? null : emptyToNull(j.optString("thumbnail_url", null))
|
||||
);
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject out = new JSONObject()
|
||||
.put("file_url", fileUrl)
|
||||
.put("file_type", fileType);
|
||||
|
||||
out.put("file_name", fileName == null ? JSONObject.NULL : fileName);
|
||||
out.put("file_size", fileSize == null ? JSONObject.NULL : fileSize);
|
||||
out.put("mime_type", mimeType == null ? JSONObject.NULL : mimeType);
|
||||
out.put("width", width == null ? JSONObject.NULL : width);
|
||||
out.put("height", height == null ? JSONObject.NULL : height);
|
||||
out.put("duration_seconds", durationSeconds == null ? JSONObject.NULL : durationSeconds);
|
||||
out.put("thumbnail_url", thumbnailUrl == null ? JSONObject.NULL : thumbnailUrl);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
public boolean isImage() { return "IMAGE".equalsIgnoreCase(fileType) || "GIF".equalsIgnoreCase(fileType); }
|
||||
public boolean isAudio() { return "AUDIO".equalsIgnoreCase(fileType); }
|
||||
public boolean hasDimensions() { return width != null && height != null; }
|
||||
|
||||
private static String emptyToNull(String s) {
|
||||
return (s == null || s.isBlank()) ? null : s;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getFileUrl() { return fileUrl; }
|
||||
public String getFileType() { return fileType; }
|
||||
public String getFileName() { return fileName; }
|
||||
public Long getFileSize() { return fileSize; }
|
||||
public String getMimeType() { return mimeType; }
|
||||
public Integer getWidth() { return width; }
|
||||
public Integer getHeight() { return height; }
|
||||
public Integer getDurationSeconds() { return durationSeconds; }
|
||||
public String getThumbnailUrl() { return thumbnailUrl; }
|
||||
|
||||
// equals/hashCode/toString
|
||||
@Override public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FileAttachment)) return false;
|
||||
FileAttachment that = (FileAttachment) o;
|
||||
return Objects.equals(fileUrl, that.fileUrl) &&
|
||||
Objects.equals(fileType, that.fileType) &&
|
||||
Objects.equals(fileName, that.fileName) &&
|
||||
Objects.equals(fileSize, that.fileSize) &&
|
||||
Objects.equals(mimeType, that.mimeType) &&
|
||||
Objects.equals(width, that.width) &&
|
||||
Objects.equals(height, that.height) &&
|
||||
Objects.equals(durationSeconds, that.durationSeconds) &&
|
||||
Objects.equals(thumbnailUrl, that.thumbnailUrl);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Objects.hash(fileUrl, fileType, fileName, fileSize, mimeType, width, height, durationSeconds, thumbnailUrl);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "FileAttachment{" +
|
||||
"fileUrl='" + fileUrl + '\'' +
|
||||
", fileType='" + fileType + '\'' +
|
||||
", fileName='" + fileName + '\'' +
|
||||
", fileSize=" + fileSize +
|
||||
", mimeType='" + mimeType + '\'' +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
", durationSeconds=" + durationSeconds +
|
||||
", thumbnailUrl='" + thumbnailUrl + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public UUID getAttachmentId() {return attachmentId;
|
||||
}
|
||||
|
||||
public UUID getMediaKey() {return mediaKey;
|
||||
}
|
||||
|
||||
public String getStoragePath() {return storagePath;
|
||||
}
|
||||
|
||||
public void setAttachmentId(UUID attachmentId) {this.attachmentId = attachmentId;
|
||||
}
|
||||
|
||||
public void setMediaKey(UUID mediaKey) {this.mediaKey = mediaKey;
|
||||
}
|
||||
|
||||
|
||||
public void setFileUrl(String fileUrl) {this.fileUrl = fileUrl;
|
||||
}
|
||||
public void setFileType(String fileType){this.fileType = fileType;}
|
||||
public void setFileName(String fileName){this.fileName = fileName;}
|
||||
public void setFileSize(Long fileSize){this.fileSize = fileSize;}
|
||||
public void setMimeType(String mimeType){this.mimeType = mimeType;}
|
||||
public void setWidth(int width){this.width = width;}
|
||||
public void setHeight(int height){this.height = height;}
|
||||
public void setDurationSeconds(Integer durationSeconds){this.durationSeconds = durationSeconds;}
|
||||
public void setThumbnailUrl(String thumbnailUrl){this.thumbnailUrl = thumbnailUrl;}
|
||||
public void setStoragePath(String storagePath) {this.storagePath = storagePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Group {
|
||||
private UUID internal_uuid;
|
||||
private String group_id;
|
||||
private String group_name;
|
||||
private UUID creator_id;
|
||||
private String image_url;
|
||||
private String description;
|
||||
private LocalDateTime created_at;
|
||||
private List<GroupMember> members;
|
||||
|
||||
public Group(UUID internal_uuid, String group_name, UUID creator_id, LocalDateTime created_at){
|
||||
this.internal_uuid = internal_uuid;
|
||||
this.group_name = group_name;
|
||||
this.creator_id = creator_id;
|
||||
this.created_at = created_at;
|
||||
}
|
||||
|
||||
public Group() {
|
||||
|
||||
}
|
||||
|
||||
public void setGroup_id(String group_id){this.group_id = group_id;}
|
||||
public void setCreator_id(UUID creator_id){this.creator_id = creator_id;}
|
||||
public void setGroup_name(String group_name){this.group_name = group_name;}
|
||||
public void setImage_url(String image_url){this.image_url = image_url;}
|
||||
public void setCreated_at(LocalDateTime created_at){this.created_at = created_at;}
|
||||
public void setDescription(String description){this.description =description;}
|
||||
public void setMembers(List<GroupMember> members){this.members = members;}
|
||||
|
||||
public String getGroup_id(){return this.group_id;}
|
||||
public UUID getCreator_id(){return this.creator_id;}
|
||||
public String getGroup_name(){return this.group_name;}
|
||||
public String getImage_url(){return this.image_url;}
|
||||
public LocalDateTime getCreated_at(){return this.created_at;}
|
||||
public String getDescription(){return this.description;}
|
||||
public List<GroupMember> getMembers(){return this.members;}
|
||||
public UUID getInternal_uuid() {
|
||||
return this.internal_uuid;
|
||||
}
|
||||
|
||||
public void setInternal_uuid(UUID internalUuid) { this.internal_uuid = internalUuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GroupMember {
|
||||
private UUID group_id;
|
||||
private UUID user_id;
|
||||
private LocalDateTime join_at;
|
||||
private String role;
|
||||
|
||||
public GroupMember(UUID group_id, UUID user_id, String role){
|
||||
this.group_id = group_id;
|
||||
this.user_id = user_id;
|
||||
this.join_at = LocalDateTime.now();
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public void setGroup_id(UUID group_id){this.group_id = group_id;}
|
||||
public void setUser_id(UUID user_id){this.user_id = user_id;}
|
||||
public void setJoin_at(LocalDateTime join_at){this.join_at = join_at;}
|
||||
public void setRole(String role){this.role = role;}
|
||||
|
||||
|
||||
|
||||
public UUID getGroup_id(){return this.group_id;}
|
||||
public UUID getUser_id(){return this.user_id;}
|
||||
public LocalDateTime getJoin_at(){return this.join_at;}
|
||||
public String getRole(){return this.role;}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class JsonUtil {
|
||||
public static JSONArray contactListToJson(List<Contact> contacts) {
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
for (Contact contact : contacts) {
|
||||
UUID contactId = contact.getContact_id();
|
||||
User contactUser = userDatabase.findByInternalUUID(contactId);
|
||||
if (contactUser == null) continue;
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("contact_id", contactId.toString()); // UUID
|
||||
obj.put("user_id", contactUser.getUser_id()); // public ID
|
||||
obj.put("profile_name", contactUser.getProfile_name());
|
||||
obj.put("image_url", contactUser.getImage_url());
|
||||
obj.put("is_blocked", contact.getIs_blocked());
|
||||
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static JSONArray contactEntryListToJson(List<ContactEntry> contactEntries) {
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
for (ContactEntry entry : contactEntries) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("contact_id", entry.getContactId().toString());
|
||||
obj.put("user_id", entry.getUserId());
|
||||
obj.put("profile_name", entry.getProfileName());
|
||||
obj.put("image_url", entry.getImageUrl());
|
||||
obj.put("is_blocked", entry.isBlocked());
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static JSONObject userToJson(User user) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_uuid", user.getInternal_uuid().toString());
|
||||
obj.put("user_id", user.getUser_id() != null ?user.getUser_id().toString() :JSONObject.NULL);
|
||||
obj.put("username", user.getUsername());
|
||||
obj.put("profile_name", user.getProfile_name());
|
||||
obj.put("bio", user.getBio()!= null ?user.getBio().toString() :JSONObject.NULL);
|
||||
obj.put("image_url", user.getImage_url() != null ?user.getImage_url().toString() :JSONObject.NULL);
|
||||
obj.put("status", user.getStatus());
|
||||
obj.put("last_seen", user.getLast_seen() != null ? user.getLast_seen().toString() : JSONObject.NULL);
|
||||
obj.put("contactList", JsonUtil.contactListToJson(user.getContactList()));
|
||||
obj.put("channelList", JsonUtil.channelListToJson(user.getChannelList()));
|
||||
obj.put("groupList", JsonUtil.groupListToJson(user.getGroupList()));
|
||||
obj.put("unreadMessages", JsonUtil.messageListToJson(user.getUnreadMessages()));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
public static JSONArray messageListToJson(List<Message> messages) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Message message : messages) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("message_id", message.getMessage_id().toString());
|
||||
obj.put("sender_id", message.getSender_id() != null ? message.getSender_id().toString() : JSONObject.NULL);
|
||||
obj.put("receiver_type", message.getReceiver_type());
|
||||
obj.put("receiver_id", message.getReceiver_id().toString());
|
||||
obj.put("content", message.getContent());
|
||||
obj.put("message_type", message.getMessage_type());
|
||||
obj.put("send_at", message.getSend_at().toString());
|
||||
obj.put("status", message.getStatus());
|
||||
obj.put("reply_to_id", message.getReply_to_id() != null ? message.getReply_to_id().toString() : JSONObject.NULL);
|
||||
obj.put("is_edited", message.isIs_edited());
|
||||
obj.put("original_message_id", message.getOriginal_message_id() != null ? message.getOriginal_message_id().toString() : JSONObject.NULL);
|
||||
obj.put("forwarded_by", message.getForwarded_by() != null ? message.getForwarded_by().toString() : JSONObject.NULL);
|
||||
obj.put("forwarded_from", message.getForwarded_from() != null ? message.getForwarded_from().toString() : JSONObject.NULL);
|
||||
array.put(obj);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static JSONArray groupListToJson(List<Group> groups) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Group group : groups) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_uuid", group.getInternal_uuid().toString());
|
||||
obj.put("group_id", group.getGroup_id() != null ?group.getGroup_id().toString() :JSONObject.NULL);
|
||||
obj.put("group_name", group.getGroup_name());
|
||||
obj.put("creator_id", group.getCreator_id().toString());
|
||||
obj.put("image_url",group.getImage_url()!= null ?group.getImage_url().toString() : JSONObject.NULL );
|
||||
obj.put("description", group.getDescription() != null ?group.getDescription().toString() : JSONObject.NULL );
|
||||
obj.put("created_at", group.getCreated_at().toString());
|
||||
obj.put("members", JsonUtil.groupMemberListToJson(group.getMembers()));
|
||||
array.put(obj);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static JSONArray channelListToJson(List<Channel> channels) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Channel channel : channels) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_uuid",channel.getInternal_uuid().toString());
|
||||
obj.put("channel_id", channel.getChannel_id() != null ?channel.getChannel_id().toString() :JSONObject.NULL);
|
||||
obj.put("channel_name", channel.getChannel_name());
|
||||
obj.put("creator_id", channel.getCreator_id().toString());
|
||||
obj.put("image_url",channel.getImage_url()!= null ?channel.getImage_url().toString() : JSONObject.NULL );
|
||||
obj.put("description",channel.getDescription() != null ?channel.getDescription().toString() : JSONObject.NULL );
|
||||
obj.put("created_at",channel.getCreated_at().toString());
|
||||
obj.put("members", JsonUtil.channelSubscribeToJson(channel.getMembers()));
|
||||
array.put(obj);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static JSONArray groupMemberListToJson(List<GroupMember> members) {
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
if (members == null) {
|
||||
return array;
|
||||
}
|
||||
|
||||
for (GroupMember m : members) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("group_id", m.getGroup_id().toString());
|
||||
obj.put("user_id", m.getUser_id().toString());
|
||||
obj.put("joined_at", m.getJoin_at().toString());
|
||||
obj.put("role", m.getRole());
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
public static JSONArray channelSubscribeToJson(List<ChannelSubscribe> subscribes) {
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
if (subscribes == null) {
|
||||
return array;
|
||||
}
|
||||
|
||||
for (ChannelSubscribe s : subscribes) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("channel_id", s.getChannel_id().toString());
|
||||
obj.put("user_id", s.getUser_id().toString());
|
||||
obj.put("Subscribed_at", s.getJoin_at().toString());
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static JSONArray chatListToJson(List<ChatEntry> chatList) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
|
||||
for (ChatEntry entry : chatList) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("internal_id", entry.getId().toString());
|
||||
obj.put("id", entry.getDisplayId());
|
||||
obj.put("name", entry.getName());
|
||||
obj.put("image_url", entry.getImageUrl());
|
||||
obj.put("type", entry.getType());
|
||||
obj.put("last_message_time", entry.getLastMessageTime() == null ? JSONObject.NULL : entry.getLastMessageTime().toString());
|
||||
obj.put("is_owner", entry.isOwner());
|
||||
obj.put("is_admin", entry.isAdmin());
|
||||
obj.put("is_saved_messages", entry.isSavedMessages());
|
||||
obj.put("unread_count", entry.getUnreadCount());
|
||||
obj.put("last_message_preview", entry.getLastMessagePreview());
|
||||
obj.put("last_message_type", entry.getLastMessageType());
|
||||
obj.put("last_message_sender_id",
|
||||
entry.getLastMessageSenderId() != null ? entry.getLastMessageSenderId().toString() : JSONObject.NULL);
|
||||
|
||||
|
||||
jsonArray.put(obj);
|
||||
}
|
||||
|
||||
return jsonArray;
|
||||
}
|
||||
|
||||
|
||||
public static JSONObject chatToJson(ChatEntry chat) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("id", chat.getId());
|
||||
obj.put("name", chat.getName());
|
||||
obj.put("image_url", chat.getImageUrl() != null ? chat.getImageUrl() : JSONObject.NULL);
|
||||
obj.put("type", chat.getType());
|
||||
obj.put("last_message_time", chat.getLastMessageTime() != null ? chat.getLastMessageTime().toString() : JSONObject.NULL);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LoginBootstrapData {
|
||||
private User user;
|
||||
private List<Contact> contacts;
|
||||
private List<Group> groups;
|
||||
private List<Channel> channels;
|
||||
private List<Message> unreadMessages;
|
||||
|
||||
public LoginBootstrapData(User user, List<Contact> contacts, List<Group> groups,
|
||||
List<Channel> channels, List<Message> unreadMessages) {
|
||||
this.user = user;
|
||||
this.contacts = contacts;
|
||||
this.groups = groups;
|
||||
this.channels = channels;
|
||||
this.unreadMessages = unreadMessages;
|
||||
}
|
||||
|
||||
public void setUnreadMessages(List<Message> unreadMessages){this.unreadMessages = unreadMessages;}
|
||||
public void setContacts(List<Contact> contacts){this.contacts =contacts;}
|
||||
public void setGroups(List<Group> groups){this.groups = groups;}
|
||||
public void setChannels(List<Channel> channels){this.channels = channels;}
|
||||
public void setUser(User user){this.user = user;}
|
||||
|
||||
public List<Message> getUnreadMessages(){return unreadMessages;}
|
||||
public List<Group> getGroups(){return groups;}
|
||||
public List<Contact> getContacts(){return contacts;}
|
||||
public List<Channel> getChannels(){return channels;}
|
||||
public User getUser(User user){return user;}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MediaRow {
|
||||
public UUID messageId;
|
||||
public String storagePath;
|
||||
public String fileName;
|
||||
public String mimeType;
|
||||
public Long fileSize;
|
||||
public String receiverType;
|
||||
public UUID receiverId;
|
||||
public UUID senderId;
|
||||
public java.util.UUID attachmentId;
|
||||
public java.util.UUID mediaKey;
|
||||
public String fileType; // IMAGE/AUDIO/...
|
||||
public Integer width;
|
||||
public Integer height;
|
||||
public Integer durationSeconds; //for audio only
|
||||
public String thumbnailUrl;
|
||||
public String fileUrl; //display link
|
||||
public String chatType;
|
||||
public UUID chatId;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Message {
|
||||
private UUID message_id;
|
||||
private UUID sender_id;
|
||||
private String receiver_type;
|
||||
private UUID receiver_id;
|
||||
private String content;
|
||||
private String message_type;
|
||||
private LocalDateTime send_at;
|
||||
private String status;
|
||||
private UUID reply_to_id;
|
||||
private boolean is_edited;
|
||||
private UUID original_message_id;
|
||||
private UUID forwarded_by;
|
||||
private UUID forwarded_from;
|
||||
private List<FileAttachment> attachments;
|
||||
private boolean is_deleted_globally;
|
||||
private LocalDateTime edited_at;
|
||||
private transient String sender_name;
|
||||
private transient String receiver_name;
|
||||
|
||||
|
||||
// ✅ Full Constructor
|
||||
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
|
||||
String message_type, LocalDateTime send_at, String status,
|
||||
UUID reply_to_id, boolean is_edited, boolean is_deleted_globally,
|
||||
UUID original_message_id, UUID forwarded_by, UUID forwarded_from) {
|
||||
this.message_id = message_id;
|
||||
this.sender_id = sender_id;
|
||||
this.receiver_type = receiver_type;
|
||||
this.receiver_id = receiver_id;
|
||||
this.content = content;
|
||||
this.message_type = message_type;
|
||||
this.send_at = send_at;
|
||||
this.status = status;
|
||||
this.reply_to_id = reply_to_id;
|
||||
this.is_edited = is_edited;
|
||||
this.is_deleted_globally = is_deleted_globally;
|
||||
this.original_message_id = original_message_id;
|
||||
this.forwarded_by = forwarded_by;
|
||||
this.forwarded_from = forwarded_from;
|
||||
}
|
||||
|
||||
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
|
||||
String message_type, LocalDateTime send_at, String status,
|
||||
UUID reply_to_id, boolean is_edited, UUID original_message_id,
|
||||
UUID forwarded_by, UUID forwarded_from,boolean is_deleted_globally, LocalDateTime edited_at) {
|
||||
this.message_id = message_id;
|
||||
this.sender_id = sender_id;
|
||||
this.receiver_type = receiver_type;
|
||||
this.receiver_id = receiver_id;
|
||||
this.content = content;
|
||||
this.message_type = message_type;
|
||||
this.send_at = send_at;
|
||||
this.status = status;
|
||||
this.reply_to_id = reply_to_id;
|
||||
this.is_edited = is_edited;
|
||||
this.original_message_id = original_message_id;
|
||||
this.forwarded_by = forwarded_by;
|
||||
this.forwarded_from = forwarded_from;
|
||||
this.is_deleted_globally = is_deleted_globally;
|
||||
this.edited_at = edited_at;
|
||||
}
|
||||
|
||||
|
||||
// ✅ Short Constructors
|
||||
//for normal messages
|
||||
public Message(UUID messageId, UUID senderId, UUID receiverId, String receiverType,
|
||||
String content, String messageType, LocalDateTime sendAt) {
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_id = receiverId;
|
||||
this.receiver_type = receiverType;
|
||||
this.content = content;
|
||||
this.message_type = messageType;
|
||||
this.send_at = sendAt;
|
||||
}
|
||||
|
||||
//for reply messages
|
||||
public Message(UUID messageId, UUID senderId, String receiverType, UUID receiverId,
|
||||
String content, String messageType, LocalDateTime sendAt, String status,
|
||||
UUID replyToId) {
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_type = receiverType;
|
||||
this.receiver_id = receiverId;
|
||||
this.content = content;
|
||||
this.message_type = messageType;
|
||||
this.send_at = sendAt;
|
||||
this.status = status;
|
||||
this.reply_to_id = replyToId;
|
||||
this.is_edited = false;
|
||||
this.is_deleted_globally = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public Message(UUID messageId, UUID senderId, String receiverType, UUID receiverId,
|
||||
String content, String messageType, LocalDateTime sendAt, String status,
|
||||
UUID replyToId, boolean isEdited, boolean isDeletedGlobally,
|
||||
UUID originalMessageId, UUID forwardedBy, UUID forwardedFrom,
|
||||
LocalDateTime editedAt) {
|
||||
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_type = receiverType;
|
||||
this.receiver_id = receiverId;
|
||||
this.content = content;
|
||||
this.message_type = messageType;
|
||||
this.send_at = sendAt;
|
||||
this.status = status;
|
||||
this.reply_to_id = replyToId;
|
||||
this.is_edited = isEdited;
|
||||
this.is_deleted_globally = isDeletedGlobally;
|
||||
this.original_message_id = originalMessageId;
|
||||
this.forwarded_by = forwardedBy;
|
||||
this.forwarded_from = forwardedFrom;
|
||||
this.edited_at = editedAt;
|
||||
}
|
||||
|
||||
public Message() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ✅ Getters & Setters
|
||||
public UUID getMessage_id() { return message_id; }
|
||||
public void setMessage_id(UUID messageId) { this.message_id = messageId; }
|
||||
|
||||
public UUID getSender_id() { return sender_id; }
|
||||
public void setSender_id(UUID sender_id) { this.sender_id = sender_id; }
|
||||
|
||||
public String getReceiver_type() { return receiver_type; }
|
||||
public void setReceiver_type(String receiver_type) { this.receiver_type = receiver_type; }
|
||||
|
||||
public UUID getReceiver_id() { return receiver_id; }
|
||||
public void setReceiver_id(UUID receiver_id) { this.receiver_id = receiver_id; }
|
||||
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
|
||||
public String getMessage_type() { return message_type; }
|
||||
public void setMessage_type(String message_type) { this.message_type = message_type; }
|
||||
|
||||
public LocalDateTime getSend_at() { return send_at; }
|
||||
public void setSend_at(LocalDateTime send_at) { this.send_at = send_at; }
|
||||
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
|
||||
public UUID getReply_to_id() { return reply_to_id; }
|
||||
public void setReply_to_id(UUID reply_to_id) { this.reply_to_id = reply_to_id; }
|
||||
|
||||
public boolean isIs_edited() { return is_edited; }
|
||||
public void setIs_edited(boolean is_edited) { this.is_edited = is_edited; }
|
||||
|
||||
public boolean isIs_deleted_globally() { return is_deleted_globally; }
|
||||
public void setIs_deleted_globally(boolean is_deleted_globally) {
|
||||
this.is_deleted_globally = is_deleted_globally;
|
||||
}
|
||||
|
||||
public UUID getOriginal_message_id() { return original_message_id; }
|
||||
public void setOriginal_message_id(UUID original_message_id) { this.original_message_id = original_message_id; }
|
||||
|
||||
public UUID getForwarded_by() { return forwarded_by; }
|
||||
public void setForwarded_by(UUID forwarded_by) { this.forwarded_by = forwarded_by; }
|
||||
|
||||
public UUID getForwarded_from() { return forwarded_from; }
|
||||
public void setForwarded_from(UUID forwarded_from) { this.forwarded_from = forwarded_from; }
|
||||
|
||||
public List<FileAttachment> getAttachments() { return attachments; }
|
||||
public void setAttachments(List<FileAttachment> attachments) { this.attachments = attachments; }
|
||||
public String getSender_name() {
|
||||
return sender_name;
|
||||
}
|
||||
|
||||
public void setSender_name(String sender_name) {
|
||||
this.sender_name = sender_name;
|
||||
}
|
||||
|
||||
public String getReceiver_name() {
|
||||
return receiver_name;
|
||||
}
|
||||
|
||||
public void setReceiver_name(String receiver_name) {
|
||||
this.receiver_name = receiver_name;
|
||||
}
|
||||
|
||||
public LocalDateTime getEdited_at() {
|
||||
return edited_at;
|
||||
}
|
||||
|
||||
public void setEdited_at(LocalDateTime edited_at) {
|
||||
this.edited_at = edited_at;
|
||||
}
|
||||
|
||||
|
||||
public boolean getIs_deleted_globally() {
|
||||
return is_deleted_globally;
|
||||
}
|
||||
|
||||
public void receiver_id(UUID receiverId) {this.receiver_id = receiverId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MessageReceipt {
|
||||
private UUID messageId;
|
||||
private UUID userId;
|
||||
private LocalDateTime readAt;
|
||||
|
||||
public MessageReceipt(UUID messageId, UUID userId, LocalDateTime readAt) {
|
||||
this.messageId = messageId;
|
||||
this.userId = userId;
|
||||
this.readAt = readAt;
|
||||
}
|
||||
|
||||
// Overloaded constructor if readAt is not provided (use current timestamp)
|
||||
public MessageReceipt(UUID messageId, UUID userId) {
|
||||
this.messageId = messageId;
|
||||
this.userId = userId;
|
||||
this.readAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters
|
||||
public UUID getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public LocalDateTime getReadAt() {
|
||||
return readAt;
|
||||
}
|
||||
|
||||
// Setters
|
||||
public void setMessageId(UUID messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public void setUserId(UUID userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public void setReadAt(LocalDateTime readAt) {
|
||||
this.readAt = readAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MessageReceipt{" +
|
||||
"messageId=" + messageId +
|
||||
", userId=" + userId +
|
||||
", readAt=" + readAt +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PrivateChat {
|
||||
private final UUID chat_id;
|
||||
private boolean user1_deleted;
|
||||
private boolean user2_deleted;
|
||||
private UUID user1_id;
|
||||
private UUID user2_id;
|
||||
private LocalDateTime created_at;
|
||||
|
||||
public PrivateChat(UUID chat_id, UUID user1_id, UUID user2_id){
|
||||
this.chat_id = chat_id;
|
||||
this.user1_id =user1_id;
|
||||
this.user2_id =user2_id;
|
||||
this.created_at =created_at;
|
||||
}
|
||||
|
||||
public PrivateChat(UUID chatId, UUID user1, UUID user2, boolean user1Deleted, boolean user2Deleted, LocalDateTime createdAt) {
|
||||
this.chat_id = chatId;
|
||||
this.user1_id = user1;
|
||||
this.user2_id = user2;
|
||||
this.user1_deleted = user1Deleted;
|
||||
this.user2_deleted = user2Deleted;
|
||||
this.created_at = createdAt;
|
||||
}
|
||||
|
||||
public PrivateChat(UUID chatId, UUID user1, UUID user2, boolean user1Deleted, boolean user2Deleted) {
|
||||
this.chat_id = chatId;
|
||||
this.user1_id = user1;
|
||||
this.user2_id = user2;
|
||||
this.user1_deleted = user1Deleted;
|
||||
this.user2_deleted = user2Deleted;
|
||||
}
|
||||
|
||||
|
||||
public void setUser1_id(UUID user1_id){this.user1_id =user1_id;}
|
||||
public void setUser2_id(UUID user2_id){this.user2_id =user2_id;}
|
||||
public void setCreated_at(LocalDateTime created_at){this.created_at = created_at;}
|
||||
|
||||
public UUID getUser1_id(){return this.user1_id;}
|
||||
public UUID getChat_id(){return this.chat_id;}
|
||||
public UUID getUser2_id(){return this.user2_id;}
|
||||
public LocalDateTime getCreated_at(){return this.created_at;}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
public class RequestModel {
|
||||
private String action;
|
||||
private String user_id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String profile_name;
|
||||
|
||||
public RequestModel(String action, String user_id, String username, String password, String profile_name) {
|
||||
this.action = action;
|
||||
this.user_id = user_id;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.profile_name = profile_name;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return this.action;
|
||||
}
|
||||
|
||||
public String getUser_id() {
|
||||
return this.user_id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public String getProfile_name() {
|
||||
return this.profile_name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class ResponseModel {
|
||||
private String status;
|
||||
private String message;
|
||||
private JSONObject data;
|
||||
private String requestId;
|
||||
|
||||
|
||||
|
||||
public ResponseModel(String status, String message) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public ResponseModel(String status, String message, JSONObject data) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public JSONObject getData() {return this.data;}
|
||||
|
||||
public void setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("status", this.status);
|
||||
json.put("message", this.message);
|
||||
json.put("data", this.data != null ? this.data : JSONObject.NULL);
|
||||
if (this.requestId != null) {
|
||||
json.put("request_id", this.requestId);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class SearchRequestModel {
|
||||
private String action;
|
||||
private String keyword;
|
||||
private String user_id;
|
||||
|
||||
public SearchRequestModel(String action, String keyword, String user_id) {
|
||||
this.action = action;
|
||||
this.keyword = keyword;
|
||||
this.user_id = user_id;
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("action", action);
|
||||
json.put("keyword", keyword);
|
||||
json.put("user_id", user_id);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
public class SearchResultModel {
|
||||
private final String type;
|
||||
private final String id; // ← UUID
|
||||
private final String displayId; // ← user_id یا group_id برای نمایش
|
||||
private String name;
|
||||
private final String content;
|
||||
private final String sender;
|
||||
private final String time;
|
||||
|
||||
public SearchResultModel(String type, String id, String displayId,
|
||||
String content, String sender, String time) {
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.displayId = displayId;
|
||||
this.content = content;
|
||||
this.sender = sender;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
// فقط در صورت نیاز برای user/group/channel
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDisplayId() {
|
||||
return displayId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + type.toUpperCase() + "] " + name + " (ID: " + displayId + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class User {
|
||||
private String user_id;
|
||||
private UUID internal_uuid;
|
||||
private String username;
|
||||
private String password;
|
||||
private String profile_name;
|
||||
private String bio;
|
||||
private String image_url;
|
||||
private String status;
|
||||
private LocalDateTime last_seen;
|
||||
private List<Contact> contactList;
|
||||
private List<Group> groupList;
|
||||
private List<Channel> channelList;
|
||||
private List<Message> unreadMessages;
|
||||
private List<ChatEntry> chatList;
|
||||
|
||||
public User(String user_id, UUID internal_uuid, String username, String password, String profile_name, String bio, String image_url) {
|
||||
this.user_id = user_id;
|
||||
this.internal_uuid = internal_uuid;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.profile_name = profile_name;
|
||||
this.bio = bio;
|
||||
this.image_url = image_url;
|
||||
}
|
||||
|
||||
public void setUser_id(String user_id) {
|
||||
this.user_id = user_id;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setProfile_name(String profile_name) {
|
||||
this.profile_name = profile_name;
|
||||
}
|
||||
|
||||
public void setBio(String bio) {
|
||||
this.bio = bio;
|
||||
}
|
||||
|
||||
public void setImage_url(String image_url) {
|
||||
this.image_url = image_url;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setLast_seen(LocalDateTime last_seen) {
|
||||
this.last_seen = last_seen;
|
||||
}
|
||||
|
||||
public void setContactList(List<Contact> contactList){this.contactList = contactList;}
|
||||
|
||||
public void setChannelList(List<Channel> channelList){this.channelList = channelList;}
|
||||
|
||||
public void setGroupList(List<Group> groupList){this.groupList = groupList;}
|
||||
|
||||
public void setUnreadMessages(List<Message> unreadMessages){this.unreadMessages = unreadMessages;}
|
||||
|
||||
public void setChatList(List<ChatEntry> chatList){this.chatList = chatList;}
|
||||
|
||||
public UUID getInternal_uuid() {
|
||||
return this.internal_uuid;
|
||||
}
|
||||
|
||||
public String getUser_id() {
|
||||
return this.user_id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public String getProfile_name() {
|
||||
return this.profile_name;
|
||||
}
|
||||
|
||||
public String getBio() {
|
||||
return this.bio;
|
||||
}
|
||||
|
||||
public String getImage_url() {
|
||||
return this.image_url;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public LocalDateTime getLast_seen() {
|
||||
return this.last_seen;
|
||||
}
|
||||
|
||||
public List<Contact> getContactList(){return this.contactList;}
|
||||
|
||||
public List<Channel> getChannelList(){return this.channelList;}
|
||||
|
||||
public List<Group> getGroupList(){return this.groupList;}
|
||||
|
||||
public List<Message> getUnreadMessages(){return this.unreadMessages;}
|
||||
|
||||
public List<ChatEntry> getChatList(){return this.chatList;}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.to.telegramfinalproject.Security;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class PasswordHashing {
|
||||
public PasswordHashing() {
|
||||
}
|
||||
|
||||
public static String hash(String password) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hashedBytes = md.digest(password.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b : hashedBytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Hashing algorithm not found!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verify(String password, String hashedPasswordFromDB) {
|
||||
return hash(password).equals(hashedPasswordFromDB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
||||
|
||||
public class AuthService {
|
||||
private final userDatabase userDb = new userDatabase();
|
||||
|
||||
public AuthService() {
|
||||
}
|
||||
|
||||
public boolean register(String user_id, String username, String password, String profile_name) {
|
||||
if (!this.userDb.existsByUsername(username) && !this.userDb.existsByUserId(user_id)) {
|
||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
if (!password.matches(passwordRegex)) {
|
||||
System.out.println("Password doesn't Valid(At list one capital and one special char(!@#$%^&*), minimum 8 char ");
|
||||
return false;
|
||||
} else {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
password = PasswordHashing.hash(password);
|
||||
User user = new User(user_id, uuid, username, password, profile_name, "", "");
|
||||
return this.userDb.save(user);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Username/ user id is already taken");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public User login(String username, String password) {
|
||||
User user = this.userDb.findByUsername(username);
|
||||
if (user == null) {
|
||||
System.out.println("User not found.");
|
||||
return null;
|
||||
} else if (!PasswordHashing.verify(password, user.getPassword())) {
|
||||
System.out.println("Incorrect password");
|
||||
return null;
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean loginCheck(String username , String password){
|
||||
User user = userDb.findByUsername(username);
|
||||
String pass = user.getPassword();
|
||||
password = PasswordHashing.hash(password);
|
||||
String Username = user.getUsername();
|
||||
boolean login = false;
|
||||
if(user!= null && Username.equals(username)&& pass.equals(password)){
|
||||
login = true;
|
||||
}
|
||||
return login;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Models.Channel;
|
||||
import org.to.telegramfinalproject.Database.ChannelDatabase;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelService {
|
||||
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, description,now);
|
||||
|
||||
if (inserted) {
|
||||
ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Database.ContactDatabase;
|
||||
public class ContactService {
|
||||
public static boolean addContact(UUID userId, UUID contactId) {
|
||||
if (userId.equals(contactId)) return false;
|
||||
if (userDatabase.findByInternalUUID(contactId) == null) return false;
|
||||
if (userDatabase.findByInternalUUID(userId) == null) return false;
|
||||
if (ContactDatabase.existsContact(userId, contactId)) return false;
|
||||
|
||||
return ContactDatabase.addContact(userId, contactId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Models.Group;
|
||||
import org.to.telegramfinalproject.Database.GroupDatabase;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GroupService {
|
||||
public static boolean createGroup(String groupId, String groupName, UUID creatorUUID, String imageUrl) {
|
||||
UUID internalUUID = UUID.randomUUID();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
boolean inserted = GroupDatabase.insertGroup(internalUUID, groupId, groupName, creatorUUID, imageUrl, now);
|
||||
|
||||
if (inserted) {
|
||||
GroupDatabase.addMember(internalUUID, creatorUUID, "owner");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
public class MainServer {
|
||||
private static final int PORT = 8080;
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
|
||||
System.out.println("Server started on port " + PORT);
|
||||
userDatabase.setAllUsersOffline();
|
||||
|
||||
|
||||
while (true) {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
System.out.println("New client connected: " + clientSocket.getInetAddress());
|
||||
|
||||
ClientHandler handler = new ClientHandler(clientSocket);
|
||||
new Thread(handler).start();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("Server error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
|
||||
import org.to.telegramfinalproject.Database.ContactDatabase;
|
||||
import org.to.telegramfinalproject.Models.ResponseModel;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PrivateChatService {
|
||||
|
||||
public static ResponseModel deletePrivateChat(UUID currentUserId, UUID targetUserId, boolean both) {
|
||||
boolean success = both ?
|
||||
ContactDatabase.deleteChatBoth(currentUserId, targetUserId) :
|
||||
ContactDatabase.deleteChatOneSide(currentUserId, targetUserId);
|
||||
|
||||
if (success) {
|
||||
return new ResponseModel("success", "Chat deleted successfully");
|
||||
} else {
|
||||
return new ResponseModel("error", "Chat not found or failed to delete");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.*;
|
||||
import org.to.telegramfinalproject.Models.Message;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RealTimeEventDispatcher {
|
||||
|
||||
public static void sendToUser(UUID userId, JSONObject data) {
|
||||
Socket socket = SessionManager.getUserSocket(userId);
|
||||
|
||||
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
try {
|
||||
|
||||
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
|
||||
System.out.println("🚀 Sending to user: " + userId + " → " + data);
|
||||
|
||||
out.println(data.toString());
|
||||
} catch (IOException e) {
|
||||
System.err.println("❌ Error sending to user: " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
System.out.println("⚠️ User " + userId + " is offline. Skipping real-time send.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void broadcastToUsers(List<UUID> userIds, JSONObject data) {
|
||||
for (UUID userId : userIds) {
|
||||
sendToUser(userId, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static JSONObject buildEvent(String action, JSONObject payload) {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("action", action);
|
||||
json.put("data", payload);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
public static void notifyNewMessage(Message msg, User sender) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("sender", sender.getUser_id());
|
||||
data.put("receiver_type", msg.getReceiver_type());
|
||||
data.put("receiver_id", msg.getReceiver_id());
|
||||
data.put("content", msg.getContent());
|
||||
data.put("time", msg.getSend_at().toString());
|
||||
|
||||
JSONObject event = buildEvent("new_message", data);
|
||||
|
||||
switch (msg.getReceiver_type()) {
|
||||
case "private" -> RealTimeEventDispatcher.sendToUser(msg.getReceiver_id(), event);
|
||||
case "group" -> {
|
||||
List<UUID> memberIds = GroupDatabase.getMemberUUIDs(msg.getReceiver_id());
|
||||
memberIds.remove(sender.getInternal_uuid());
|
||||
RealTimeEventDispatcher.broadcastToUsers(memberIds, event);
|
||||
}
|
||||
case "channel" -> {
|
||||
List<UUID> subscriberIds = ChannelDatabase.getSubscriberUUIDs(msg.getReceiver_id());
|
||||
subscriberIds.remove(sender.getInternal_uuid());
|
||||
subscriberIds.remove(sender.getInternal_uuid());
|
||||
RealTimeEventDispatcher.broadcastToUsers(subscriberIds, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void notifyMessageEdited(UUID chatId, UUID messageId, String newContent, LocalDateTime editedAt, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("new_content", newContent);
|
||||
data.put("edited_at", editedAt.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_edited");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void sendNewMessage(Message message, List<UUID> receivers, String kind, JSONObject meta) {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("action", "new_message");
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("id", message.getMessage_id().toString());
|
||||
data.put("chat_id", message.getReceiver_id().toString());
|
||||
data.put("sender_id", message.getSender_id().toString());
|
||||
data.put("receiver_id", message.getReceiver_id().toString());
|
||||
data.put("receiver_type", message.getReceiver_type());
|
||||
data.put("content", message.getContent());
|
||||
data.put("message_type", message.getMessage_type());
|
||||
data.put("send_at", message.getSend_at().toString());
|
||||
|
||||
User sender = userDatabase.findByInternalUUID(message.getSender_id());
|
||||
if (sender != null) data.put("sender_name", sender.getProfile_name());
|
||||
|
||||
data.put("kind", kind == null ? "plain" : kind); // plain|reply|forward
|
||||
if (meta != null) data.put("meta", meta); // reply_to {...} | forwarded_from {...}
|
||||
|
||||
payload.put("data", data);
|
||||
for (UUID userId : receivers) sendToUser(userId, payload);
|
||||
}
|
||||
|
||||
public static void sendNewMessageFiltered(Message m, List<UUID> receivers, UUID senderId, String kind, JSONObject meta) {
|
||||
JSONObject payload = new JSONObject()
|
||||
.put("action", "new_message")
|
||||
.put("data", new JSONObject()
|
||||
.put("id", m.getMessage_id().toString())
|
||||
.put("message_id", m.getMessage_id().toString())
|
||||
.put("sender_id", m.getSender_id().toString())
|
||||
.put("sender_name", userDatabase.findByInternalUUID(m.getSender_id()).getProfile_name())
|
||||
.put("receiver_id", m.getReceiver_id().toString())
|
||||
.put("receiver_type", m.getReceiver_type())
|
||||
.put("content", m.getContent())
|
||||
.put("message_type", m.getMessage_type())
|
||||
.put("send_at", m.getSend_at().toString())
|
||||
.put("kind", kind)
|
||||
.put("meta", meta != null ? meta : JSONObject.NULL)
|
||||
);
|
||||
|
||||
for (UUID uid : receivers) {
|
||||
//If receiver blocked
|
||||
if ("private".equalsIgnoreCase(m.getReceiver_type()) && ContactDatabase.isBlocked(uid, senderId)) continue;
|
||||
sendToUser(uid, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void notifyMessageDeletedGlobal(UUID chatId, UUID messageId, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_deleted_global");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyReactionAdded(UUID chatId, UUID messageId, String emoji,
|
||||
int totalForEmoji, JSONObject countsAll, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("emoji", emoji);
|
||||
data.put("counts", countsAll); // {"❤️":3,"👍":1,...}
|
||||
data.put("count_for_emoji", totalForEmoji);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_reacted");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
public static void notifyReactionRemoved(UUID chatId, UUID messageId, String emoji,
|
||||
int totalForEmoji, JSONObject countsAll, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("emoji", emoji);
|
||||
data.put("counts", countsAll);
|
||||
data.put("count_for_emoji", totalForEmoji);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_unreacted");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void notifyUserUpdated(UUID userId, String newProfileName, String newImageUrl, List<UUID> contactIds) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("user_id", userId.toString());
|
||||
data.put("new_name", newProfileName);
|
||||
data.put("new_image_url", newImageUrl);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "update_user");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(contactIds, event);
|
||||
}
|
||||
|
||||
public static void notifyChatDeleted(String type, UUID id, List<UUID> affectedUsers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type); // private, group, channel
|
||||
data.put("chat_id", id.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "chat_deleted");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(affectedUsers, event);
|
||||
}
|
||||
|
||||
public static void notifyGroupOrChannelUpdated(String type, UUID id, String newName, String newImageUrl, List<UUID> affectedUsers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type); // "group" or "channel"
|
||||
data.put("chat_id", id.toString());
|
||||
data.put("new_name", newName);
|
||||
data.put("new_image_url", newImageUrl);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "update_group_or_channel");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(affectedUsers, event);
|
||||
}
|
||||
|
||||
public static void notifyMediaMessage(Message msg, User sender) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("sender", sender.getUser_id());
|
||||
data.put("receiver_type", msg.getReceiver_type());
|
||||
data.put("receiver_id", msg.getReceiver_id());
|
||||
data.put("file_type", msg.getMessage_type()); // IMAGE, FILE, VIDEO...
|
||||
data.put("time", msg.getSend_at().toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "new_media");
|
||||
event.put("data", data);
|
||||
|
||||
switch (msg.getReceiver_type()) {
|
||||
case "private" -> sendToUser(msg.getReceiver_id(), event);
|
||||
case "group" -> {
|
||||
List<UUID> members = GroupDatabase.getMemberUUIDs(msg.getReceiver_id());
|
||||
members.remove(sender.getInternal_uuid());
|
||||
broadcastToUsers(members, event);
|
||||
}
|
||||
case "channel" -> {
|
||||
List<UUID> subs = ChannelDatabase.getSubscriberUUIDs(msg.getReceiver_id());
|
||||
subs.remove(sender.getInternal_uuid());
|
||||
broadcastToUsers(subs, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void notifyAddedToChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type);
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("chat_name", chatName);
|
||||
data.put("image_url", imageUrl);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", type.equals("group") ? "added_to_group" : "added_to_channel");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(userId, event);
|
||||
}
|
||||
|
||||
public static void notifyRemovedFromChat(String type, UUID chatId, UUID userId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type);
|
||||
data.put("chat_id", chatId.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", type.equals("group") ? "removed_from_group" : "removed_from_channel");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(userId, event);
|
||||
}
|
||||
public static void notifyMessageSeen(UUID messageId, UUID senderId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("seen_at", LocalDateTime.now().toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_seen");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(senderId, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyBlocked(UUID blockerId, UUID blockedUserId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("blocker_id", blockerId.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "blocked_by_user");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(blockedUserId, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyUnblocked(UUID unblockerId, UUID unblockedUserId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("unblocker_id", unblockerId.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "unblocked_by_user");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(unblockedUserId, event);
|
||||
}
|
||||
|
||||
public static void notifyUserStatusChanged(UUID userId, String status, List<UUID> contacts) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("user_id", userId.toString());
|
||||
data.put("status", status); // online | offline
|
||||
data.put("time", LocalDateTime.now().toString());
|
||||
|
||||
JSONObject event = buildEvent("user_status_changed", data);
|
||||
|
||||
broadcastToUsers(contacts, event);
|
||||
}
|
||||
|
||||
public static void sendGroupOrChannelUpdate(String type, UUID chatId, String name, String imageUrl, String description, List<UUID> affectedUsers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type);
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("name", name);
|
||||
data.put("image_url", imageUrl);
|
||||
data.put("description", description != null ? description : "");
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "chat_updated");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(affectedUsers, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyBecameAdmin(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type); // group or channel
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("chat_name", chatName);
|
||||
data.put("image_url", imageUrl);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "became_admin");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(userId, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyRemovedAdminFromChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type);
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("chat_name", chatName);
|
||||
data.put("image_url", imageUrl);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "removed_admin");
|
||||
event.put("data", data);
|
||||
|
||||
sendToUser(userId, event);
|
||||
}
|
||||
|
||||
|
||||
public static void sendOwnershipTransferred(String type, UUID chatId, String chatName, List<UUID> affectedUsers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_type", type); // "group" or "channel"
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("chat_name", chatName);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "ownership_transferred");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(affectedUsers, event);
|
||||
}
|
||||
|
||||
|
||||
public static void sendNewMessage(Message message, List<UUID> receivers) {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("action", "new_message");
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("id", message.getMessage_id().toString());
|
||||
data.put("sender_id", message.getSender_id().toString());
|
||||
data.put("receiver_id", message.getReceiver_id().toString());
|
||||
data.put("receiver_type", message.getReceiver_type());
|
||||
data.put("content", message.getContent());
|
||||
data.put("send_at", message.getSend_at().toString());
|
||||
|
||||
User sender = userDatabase.findByInternalUUID(message.getSender_id());
|
||||
if (sender != null) {
|
||||
data.put("sender_name", sender.getProfile_name());
|
||||
}
|
||||
|
||||
payload.put("data", data);
|
||||
|
||||
for (UUID userId : receivers) {
|
||||
sendToUser(userId, payload);
|
||||
}
|
||||
|
||||
|
||||
for (UUID uid : receivers) {
|
||||
//If receiver blocked
|
||||
if ("private".equalsIgnoreCase(message.getReceiver_type()) && ContactDatabase.isBlocked(uid, message.getSender_id())) continue;
|
||||
sendToUser(uid, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void notifyChatUpdated(UUID chatId, String chatType, Message lastMsg) {
|
||||
if (chatId == null || chatType == null) return;
|
||||
|
||||
final String type = chatType.toLowerCase(Locale.ROOT);
|
||||
|
||||
List<UUID> receivers;
|
||||
switch (type) {
|
||||
case "private":
|
||||
receivers = PrivateChatDatabase.getMembers(chatId);
|
||||
break;
|
||||
|
||||
case "group":
|
||||
receivers = GroupDatabase.getMemberUUIDs(chatId);
|
||||
break;
|
||||
case "channel":
|
||||
receivers = ChannelDatabase.getSubscriberUUIDs(chatId);
|
||||
break;
|
||||
default:
|
||||
receivers = Collections.emptyList();
|
||||
}
|
||||
if (receivers == null || receivers.isEmpty()) return;
|
||||
|
||||
// 2) ساخت خلاصه آخرین پیام برای نمایش در لیست چت
|
||||
String senderName = null;
|
||||
if (lastMsg != null && lastMsg.getSender_id() != null) {
|
||||
User u = userDatabase.findByInternalUUID(lastMsg.getSender_id());
|
||||
if (u != null) senderName = u.getProfile_name();
|
||||
}
|
||||
|
||||
String messageType = lastMsg != null && lastMsg.getMessage_type() != null
|
||||
? lastMsg.getMessage_type().toLowerCase(Locale.ROOT) : "text";
|
||||
|
||||
// preview ساده: برای مدیا، برچسب کوتاه؛ برای متن، کوتاهسازی
|
||||
String preview;
|
||||
if (!"text".equals(messageType)) {
|
||||
switch (messageType) {
|
||||
case "image": preview = "[Photo]"; break;
|
||||
case "video": preview = "[Video]"; break;
|
||||
case "audio": preview = "[Audio]"; break;
|
||||
case "file": preview = "[File]"; break;
|
||||
default: preview = "[Media]";
|
||||
}
|
||||
} else {
|
||||
String t = lastMsg != null ? nullToEmpty(lastMsg.getContent()) : "";
|
||||
preview = t.length() > 80 ? t.substring(0, 80) + "…" : t;
|
||||
}
|
||||
|
||||
String sendAt = (lastMsg != null && lastMsg.getSend_at() != null)
|
||||
? lastMsg.getSend_at().toString()
|
||||
: java.time.OffsetDateTime.now().toString();
|
||||
|
||||
// 3) payload رویداد chat_updated
|
||||
JSONObject payload = new JSONObject()
|
||||
.put("action", "chat_updated")
|
||||
.put("data", new JSONObject()
|
||||
.put("chat_id", chatId.toString())
|
||||
.put("chat_type", type)
|
||||
.put("last_message", new JSONObject()
|
||||
.put("id", lastMsg != null ? lastMsg.getMessage_id().toString() : JSONObject.NULL)
|
||||
.put("sender_id", lastMsg != null ? lastMsg.getSender_id().toString() : JSONObject.NULL)
|
||||
.put("sender_name", senderName != null ? senderName : JSONObject.NULL)
|
||||
.put("message_type", messageType)
|
||||
.put("preview", preview)
|
||||
.put("send_at", sendAt)
|
||||
)
|
||||
.put("last_message_time", sendAt)
|
||||
.put("update_reason", "new_message") // برای کلاینت مفید است
|
||||
);
|
||||
|
||||
// 4) ارسال به همه اعضای چت
|
||||
for (UUID uid : receivers) {
|
||||
sendToUser(uid, payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static String nullToEmpty(String s) { return s == null ? "" : s; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Database.ChannelDatabase;
|
||||
import org.to.telegramfinalproject.Database.GroupDatabase;
|
||||
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Receivers {
|
||||
public static List<UUID> resolveFor(String type, UUID chatId, UUID exclude) {
|
||||
List<UUID> ids = switch (type) {
|
||||
case "private" -> PrivateChatDatabase.getMembers(chatId);
|
||||
case "group" -> GroupDatabase.getMemberUUIDs(chatId);
|
||||
case "channel" -> ChannelDatabase.getSubscriberUUIDs(chatId);
|
||||
default -> List.of();
|
||||
};
|
||||
if (exclude != null) ids.remove(exclude);
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class SessionManager {
|
||||
private static final Map<UUID, Socket> onlineUsers = new ConcurrentHashMap<>(); //catch UUID for find the user socket
|
||||
|
||||
public static void addUser(UUID userId, Socket socket) {
|
||||
onlineUsers.put(userId, socket);
|
||||
}
|
||||
|
||||
public static void removeUser(UUID userId) {
|
||||
onlineUsers.remove(userId);
|
||||
}
|
||||
|
||||
public static boolean isOnline(UUID userId) {
|
||||
return onlineUsers.containsKey(userId);
|
||||
}
|
||||
|
||||
public static Socket getUserSocket(UUID userId) {
|
||||
return onlineUsers.get(userId);
|
||||
}
|
||||
|
||||
public static UUID getUserIdBySocket(Socket socket) {
|
||||
for (Map.Entry<UUID, Socket> entry : onlineUsers.entrySet()) {
|
||||
if (entry.getValue().equals(socket)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean contains(UUID userId) {
|
||||
return onlineUsers.containsKey(userId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.ContactDatabase;
|
||||
import org.to.telegramfinalproject.Database.MessageDatabase;
|
||||
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.ContactEntry;
|
||||
import org.to.telegramfinalproject.Models.Message;
|
||||
import org.to.telegramfinalproject.Models.ResponseModel;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SidebarService {
|
||||
private static userDatabase userDB = new userDatabase();
|
||||
|
||||
// Returns current user's profile data (name, bio, status, etc.)
|
||||
public static JSONObject getUserProfile(UUID userUUID) {
|
||||
User user = userDB.findByInternalUUID(userUUID);
|
||||
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject profile = new JSONObject();
|
||||
profile.put("user_id", user.getUser_id());
|
||||
profile.put("profile_name", user.getProfile_name());
|
||||
profile.put("bio", user.getBio() != null ? user.getBio() : "");
|
||||
profile.put("status", "ONLINE");
|
||||
profile.put("profile_picture_url", user.getImage_url());
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
// Changes the user's profile picture
|
||||
public static ResponseModel updateProfilePicture(UUID userUUID, String newImageUrl) {
|
||||
User user = userDB.findByInternalUUID(userUUID);
|
||||
if (user == null) {
|
||||
return new ResponseModel("error", "User not found.");
|
||||
}
|
||||
|
||||
String currentUrl = user.getImage_url();
|
||||
if (Objects.equals(currentUrl, newImageUrl)) {
|
||||
return new ResponseModel("error", "No changes made. Profile picture is the same.");
|
||||
}
|
||||
|
||||
user.setImage_url(newImageUrl); // Can be null
|
||||
boolean success = userDB.updateByUUID(userUUID, user);
|
||||
|
||||
if (success) {
|
||||
return new ResponseModel("success", "Profile picture updated successfully.");
|
||||
} else {
|
||||
user.setImage_url(currentUrl);
|
||||
return new ResponseModel("error", "Failed to update profile picture.");
|
||||
}
|
||||
}
|
||||
|
||||
// Changes user's bio
|
||||
public static ResponseModel updateBio(UUID userUUID, String newBio) {
|
||||
if (newBio.length() > 70) {
|
||||
return new ResponseModel("error", "Bio is too long.");
|
||||
}
|
||||
|
||||
User user = userDB.findByInternalUUID(userUUID);
|
||||
if (user == null) {
|
||||
return new ResponseModel("error", "User not found.");
|
||||
}
|
||||
|
||||
String currentBio = user.getBio();
|
||||
if (Objects.equals(currentBio, newBio)) {
|
||||
return new ResponseModel("error", "No changes made. Bio is the same.");
|
||||
}
|
||||
|
||||
user.setBio(newBio);
|
||||
boolean success = userDB.updateByUUID(userUUID, user);
|
||||
|
||||
if (success) {
|
||||
return new ResponseModel("success", "Bio updated successfully.");
|
||||
} else {
|
||||
user.setBio(currentBio);
|
||||
return new ResponseModel("error", "Failed to update bio.");
|
||||
}
|
||||
}
|
||||
|
||||
// Changes user-id
|
||||
public static ResponseModel updateUserId(UUID userUUID, String newUserId) {
|
||||
if (newUserId == null || newUserId.trim().isEmpty()) {
|
||||
return new ResponseModel("error", "User ID cannot be empty.");
|
||||
}
|
||||
|
||||
newUserId = newUserId.trim();
|
||||
|
||||
if (newUserId.contains(" ")) {
|
||||
return new ResponseModel("error", "User ID cannot contain spaces.");
|
||||
}
|
||||
|
||||
if (!newUserId.matches("^[a-zA-Z0-9_]+$")) {
|
||||
return new ResponseModel("error", "User ID can only contain letters, digits, and underscores.");
|
||||
}
|
||||
|
||||
if (userDB.findByUserId(newUserId) != null) {
|
||||
return new ResponseModel("error", "This user ID is already taken.");
|
||||
}
|
||||
|
||||
User user = userDB.findByInternalUUID(userUUID);
|
||||
if (user == null) {
|
||||
return new ResponseModel("error", "User not found.");
|
||||
}
|
||||
|
||||
String currentUserId = user.getUser_id();
|
||||
if (Objects.equals(currentUserId, newUserId)) {
|
||||
return new ResponseModel("error", "No changes made. User ID is the same.");
|
||||
}
|
||||
|
||||
user.setUser_id(newUserId);
|
||||
|
||||
boolean saved = userDB.updateByUUID(userUUID, user);
|
||||
if (saved) {
|
||||
return new ResponseModel("success", "User ID updated successfully.");
|
||||
} else {
|
||||
user.setUser_id(currentUserId);
|
||||
return new ResponseModel("error", "Failed to update user ID due to server error.");
|
||||
}
|
||||
}
|
||||
|
||||
// Changes user's profile name
|
||||
public static ResponseModel updateProfileName(UUID userUUID, String newProfileName) {
|
||||
if (newProfileName == null || newProfileName.trim().isEmpty()) {
|
||||
return new ResponseModel("error", "Invalid input."); // Invalid input (empty or just spaces)
|
||||
}
|
||||
|
||||
// Fetch the user from database
|
||||
User user = userDB.findByInternalUUID(userUUID);
|
||||
if (user == null) {
|
||||
return new ResponseModel("error", "User not found."); // User doesn't exist
|
||||
}
|
||||
|
||||
String oldName = user.getProfile_name();
|
||||
if (Objects.equals(oldName, newProfileName)) {
|
||||
return new ResponseModel("error", "No changes made. Profile name is the same.");
|
||||
}
|
||||
|
||||
// Only set the profile name *after* successful DB update
|
||||
user.setProfile_name(newProfileName);
|
||||
|
||||
boolean saved = userDB.updateByUUID(userUUID, user);
|
||||
|
||||
if (saved) {
|
||||
return new ResponseModel("success", "Profile name updated successfully.");
|
||||
} else {
|
||||
user.setProfile_name(oldName);
|
||||
return new ResponseModel("error", "Failed to update profile name.");
|
||||
}
|
||||
}
|
||||
|
||||
// Search in user's contact list
|
||||
public static ResponseModel handleSearchContacts(String userUUID, String searchTerm) {
|
||||
if (searchTerm == null || searchTerm.trim().isEmpty()) {
|
||||
return new ResponseModel("error", "Search term cannot be empty.");
|
||||
}
|
||||
|
||||
List<ContactEntry> searchResult = ContactDatabase.searchContacts(UUID.fromString(userUUID), searchTerm);
|
||||
|
||||
if (searchResult.isEmpty()) {
|
||||
return new ResponseModel("error", "No contacts found.");
|
||||
}
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
JSONArray contacts = new JSONArray();
|
||||
|
||||
for (ContactEntry entry : searchResult) {
|
||||
contacts.put(new JSONObject()
|
||||
.put("contact_id", entry.getContactId())
|
||||
.put("user_id", entry.getUserId())
|
||||
.put("contact_display_id", entry.getContact_displayId())
|
||||
.put("profile_name", entry.getProfileName())
|
||||
.put("image_url", entry.getImageUrl())
|
||||
.put("is_blocked", entry.isBlocked())
|
||||
.put("last_seen", entry.getLastSeenTime())
|
||||
);
|
||||
}
|
||||
data.put("contacts", contacts);
|
||||
|
||||
if (searchResult.isEmpty()) {
|
||||
return new ResponseModel("error", "No contacts found.");
|
||||
}
|
||||
|
||||
return new ResponseModel("success", "Search contacts successfully.", data);
|
||||
}
|
||||
|
||||
// Remove a contact in user's contact list
|
||||
public static ResponseModel handleRemoveContact(UUID userUUID, UUID contactId) {
|
||||
if (userUUID == null || contactId == null) {
|
||||
return new ResponseModel("error", "Invalid input.");
|
||||
}
|
||||
|
||||
boolean removed = ContactDatabase.removeContact(userUUID, contactId);
|
||||
|
||||
if (removed) {
|
||||
return new ResponseModel("success", "Contact removed successfully.");
|
||||
} else {
|
||||
return new ResponseModel("error", "Contact not found.");
|
||||
}
|
||||
}
|
||||
|
||||
// public static ResponseModel handleGetSavedMessages(UUID userId) {
|
||||
// try {
|
||||
//
|
||||
// UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId);
|
||||
// if (chatId == null) {
|
||||
// return new ResponseModel("error", "Failed to create or find saved messages chat.");
|
||||
// }
|
||||
//
|
||||
// List<Message> messages = MessageDatabase.privateChatHistory(chatId, userId);
|
||||
//
|
||||
// JSONArray messageArray = new JSONArray();
|
||||
// if (!messages.isEmpty()) {
|
||||
// for (Message msg : messages) {
|
||||
// JSONObject msgJson = new JSONObject();
|
||||
// msgJson.put("message_id", msg.getMessage_id().toString());
|
||||
// msgJson.put("sender_id", msg.getSender_id().toString());
|
||||
// msgJson.put("receiver_type", msg.getReceiver_type());
|
||||
// msgJson.put("receiver_id", msg.getReceiver_id().toString());
|
||||
// msgJson.put("content", msg.getContent());
|
||||
// msgJson.put("message_type", msg.getMessage_type());
|
||||
// msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime
|
||||
// msgJson.put("status", msg.getStatus());
|
||||
// msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL);
|
||||
// msgJson.put("is_edited", msg.isIs_edited());
|
||||
// msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL);
|
||||
// msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL);
|
||||
// msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL);
|
||||
//
|
||||
// messageArray.put(msgJson);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// JSONObject data = new JSONObject();
|
||||
// data.put("chat_id", chatId.toString());
|
||||
// data.put("messages", messageArray);
|
||||
//
|
||||
// return new ResponseModel("success", "Saved messages retrieved successfully", data);
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// return new ResponseModel("error", "Unexpected server error.");
|
||||
// }
|
||||
// }
|
||||
|
||||
// Save messages to DB
|
||||
// public static ResponseModel handleSendMessage(JSONObject requestJson) {
|
||||
// try {
|
||||
// Message message = new Message(
|
||||
// UUID.fromString(requestJson.getString("message_id")),
|
||||
// UUID.fromString(requestJson.getString("sender_id")),
|
||||
// requestJson.getString("receiver_type"),
|
||||
// UUID.fromString(requestJson.getString("receiver_id")),
|
||||
// requestJson.optString("content", null),
|
||||
// requestJson.optString("message_type", "TEXT"),
|
||||
// LocalDateTime.now(), // send_at
|
||||
// requestJson.optString("status", "SEND"),
|
||||
// requestJson.isNull("reply_to_id") ? null : UUID.fromString(requestJson.getString("reply_to_id")),
|
||||
// requestJson.optBoolean("is_edited", false),
|
||||
// requestJson.isNull("original_message_id") ? null : UUID.fromString(requestJson.getString("original_message_id")),
|
||||
// requestJson.isNull("forwarded_by") ? null : UUID.fromString(requestJson.getString("forwarded_by")),
|
||||
// requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")),
|
||||
// requestJson.optBoolean("is_deleted_globally", false),
|
||||
// requestJson.isNull("edited_at") ? null :
|
||||
// LocalDateTime.ofInstant(
|
||||
// Instant.ofEpochMilli(requestJson.getLong("edited_at")),
|
||||
// ZoneId.systemDefault()
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// MessageDatabase.insertSavedMessage(message);
|
||||
// return new ResponseModel("success", "Message saved successfully.");
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// return new ResponseModel("error", "Unexpected server error.");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class TestServer {
|
||||
private static final int SOCKET_PORT = 8000; // سرور سوکت
|
||||
private static final int HTTP_PORT = 8080; // سرور آپلود
|
||||
private static final String UPLOAD_BASE_DIR = "uploads"; // پوشهی ذخیره فایلها
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 1) استارت HTTP Upload در ترد جدا
|
||||
Thread httpThread = new Thread(() -> {
|
||||
try {
|
||||
UploadHttp.start(HTTP_PORT, UPLOAD_BASE_DIR);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Upload HTTP failed to start: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, "upload-http");
|
||||
httpThread.setDaemon(true);
|
||||
httpThread.start();
|
||||
|
||||
// 2) سرور سوکت با Thread Pool
|
||||
ExecutorService pool = Executors.newCachedThreadPool();
|
||||
try (ServerSocket serverSocket = new ServerSocket(SOCKET_PORT)) {
|
||||
System.out.println("Socket server started on port " + SOCKET_PORT);
|
||||
userDatabase.setAllUsersOffline();
|
||||
|
||||
// 3) Shutdown Hook برای خاموشی تمیز
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
System.out.println("\nShutting down...");
|
||||
try { serverSocket.close(); } catch (IOException ignore) {}
|
||||
pool.shutdownNow();
|
||||
userDatabase.setAllUsersOffline();
|
||||
System.out.println("Goodbye.");
|
||||
}));
|
||||
|
||||
// 4) حلقه پذیرش اتصالها
|
||||
while (!serverSocket.isClosed()) {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
clientSocket.setTcpNoDelay(true);
|
||||
System.out.println("New client connected: " + clientSocket.getInetAddress());
|
||||
pool.submit(new ClientHandler(clientSocket));
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("Socket server error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import static spark.Spark.*;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.MultipartConfigElement;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import javax.sound.sampled.*; // برای WAV
|
||||
|
||||
import org.json.JSONObject;
|
||||
import com.mpatric.mp3agic.Mp3File;
|
||||
|
||||
public class UploadHttp {
|
||||
|
||||
public static void start(int httpPort, String baseDir) throws IOException {
|
||||
port(httpPort);
|
||||
|
||||
Path basePath = Paths.get(baseDir).toAbsolutePath().normalize();
|
||||
Files.createDirectories(basePath);
|
||||
staticFiles.externalLocation(basePath.toString());
|
||||
|
||||
post("/upload", (req, res) -> {
|
||||
res.type("application/json");
|
||||
try {
|
||||
long MAX_FILE = 25L * 1024 * 1024; // 25MB
|
||||
req.attribute("org.eclipse.jetty.multipartConfig",
|
||||
new MultipartConfigElement("/tmp", MAX_FILE, MAX_FILE, 0));
|
||||
|
||||
Part filePart = req.raw().getPart("file");
|
||||
if (filePart == null || filePart.getSize() == 0) {
|
||||
res.status(400);
|
||||
return jsonError("empty file");
|
||||
}
|
||||
if (filePart.getSize() > MAX_FILE) {
|
||||
res.status(413);
|
||||
return jsonError("file too large");
|
||||
}
|
||||
|
||||
String mime = filePart.getContentType();
|
||||
if (mime == null) {
|
||||
res.status(415);
|
||||
return jsonError("unknown mime");
|
||||
}
|
||||
|
||||
String original = filePart.getSubmittedFileName();
|
||||
String ext = guessExt(original, mime);
|
||||
String day = LocalDate.now().toString();
|
||||
String typeDir = subdirFor(mime); // images/audios/files
|
||||
String subdir = typeDir + "/" + day;
|
||||
String name = java.util.UUID.randomUUID() + ext;
|
||||
|
||||
Path dir = basePath.resolve(subdir).normalize();
|
||||
Files.createDirectories(dir);
|
||||
Path target = dir.resolve(name).normalize();
|
||||
|
||||
try (InputStream in = filePart.getInputStream()) {
|
||||
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
} finally {
|
||||
filePart.delete();
|
||||
}
|
||||
|
||||
String fileUrl = "/" + subdir.replace('\\', '/') + "/" + name;
|
||||
String fileType = mapToFileType(mime);
|
||||
|
||||
//Meta deta only for audio and image
|
||||
Integer width = null, height = null, durationSeconds = null;
|
||||
String thumbnailUrl = null;
|
||||
|
||||
if ("IMAGE".equals(fileType) || "GIF".equals(fileType)) {
|
||||
int[] wh = imageSize(target);
|
||||
if (wh != null) { width = wh[0]; height = wh[1]; }
|
||||
} else if ("AUDIO".equals(fileType)) {
|
||||
durationSeconds = audioDurationSeconds(target, mime, ext);
|
||||
}
|
||||
|
||||
res.status(200);
|
||||
return new JSONObject()
|
||||
.put("file_url", fileUrl)
|
||||
.put("file_type", fileType)
|
||||
.put("file_name", original == null ? "" : safeName(original))
|
||||
.put("file_size", Files.size(target))
|
||||
.put("mime_type", mime)
|
||||
.put("width", width == null ? JSONObject.NULL : width)
|
||||
.put("height", height == null ? JSONObject.NULL : height)
|
||||
.put("duration_seconds", durationSeconds == null ? JSONObject.NULL : durationSeconds)
|
||||
.put("thumbnail_url", JSONObject.NULL)
|
||||
.toString();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
res.status(500);
|
||||
return jsonError("internal error");
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
awaitInitialization();
|
||||
System.out.println("Upload HTTP server on http://localhost:" + httpPort + " baseDir=" + basePath);
|
||||
}
|
||||
|
||||
// ---------- Helpers ----------
|
||||
|
||||
private static String jsonError(String msg) {
|
||||
return new JSONObject().put("error", msg).toString();
|
||||
}
|
||||
|
||||
private static String subdirFor(String mime) {
|
||||
String m = mime.toLowerCase();
|
||||
if (m.startsWith("image/")) return "images";
|
||||
if (m.startsWith("audio/")) return "audios";
|
||||
return "files";
|
||||
}
|
||||
|
||||
private static String mapToFileType(String mime) {
|
||||
String m = mime.toLowerCase();
|
||||
if (m.startsWith("image/")) {
|
||||
if (m.contains("gif")) return "GIF";
|
||||
return "IMAGE";
|
||||
}
|
||||
if (m.startsWith("audio/")) return "AUDIO";
|
||||
return "FILE";
|
||||
}
|
||||
|
||||
private static String guessExt(String original, String mime) {
|
||||
if (original != null && original.contains(".")) {
|
||||
String ext = original.substring(original.lastIndexOf('.'));
|
||||
if (ext.length() <= 10) return ext;
|
||||
}
|
||||
if ("image/png".equalsIgnoreCase(mime)) return ".png";
|
||||
if ("image/jpeg".equalsIgnoreCase(mime)) return ".jpg";
|
||||
if ("image/gif".equalsIgnoreCase(mime)) return ".gif";
|
||||
if ("audio/mpeg".equalsIgnoreCase(mime)) return ".mp3";
|
||||
if ("audio/wav".equalsIgnoreCase(mime) || "audio/x-wav".equalsIgnoreCase(mime)) return ".wav";
|
||||
if ("application/pdf".equalsIgnoreCase(mime)) return ".pdf";
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String safeName(String name) {
|
||||
return name.replace("\"", "").replace("\n", "").replace("\r", "");
|
||||
}
|
||||
|
||||
private static int[] imageSize(Path file) {
|
||||
try {
|
||||
BufferedImage bi = ImageIO.read(file.toFile());
|
||||
if (bi != null) return new int[]{bi.getWidth(), bi.getHeight()};
|
||||
} catch (Exception ignore) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
//only audio
|
||||
private static Integer audioDurationSeconds(Path file, String mime, String ext) {
|
||||
try {
|
||||
if ("audio/mpeg".equalsIgnoreCase(mime) || ".mp3".equalsIgnoreCase(ext)) {
|
||||
Mp3File mp3 = new Mp3File(file.toFile());
|
||||
return (int) mp3.getLengthInSeconds();
|
||||
}
|
||||
|
||||
// WAV با javax.sound.sampled
|
||||
if ("audio/wav".equalsIgnoreCase(mime) || "audio/x-wav".equalsIgnoreCase(mime) || ".wav".equalsIgnoreCase(ext)) {
|
||||
try (AudioInputStream ais = AudioSystem.getAudioInputStream(file.toFile())) {
|
||||
AudioFormat format = ais.getFormat();
|
||||
long frames = ais.getFrameLength();
|
||||
if (frames > 0 && format.getFrameRate() > 0) {
|
||||
double seconds = frames / format.getFrameRate();
|
||||
return (int)Math.round(seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedAudioFileException | IOException ignore) {
|
||||
// فرمت صوتی پشتیبانی نشده برای AudioSystem
|
||||
} catch (Exception ignore) {
|
||||
// mp3agic یا سایر استثناها
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AddAdminsController {
|
||||
|
||||
@FXML private VBox addAdminsCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private ScrollPane membersScroll;
|
||||
@FXML private VBox membersList;
|
||||
|
||||
private String groupId;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addAdminsCard.getParent()));
|
||||
|
||||
// Smooth scroll feel
|
||||
membersScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
membersScroll.setPannable(true);
|
||||
membersScroll.setFitToWidth(true);
|
||||
membersScroll.setFitToHeight(false);
|
||||
membersScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
membersScroll.setVvalue(membersScroll.getVvalue() - deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public void setGroupData(String groupId, JSONArray members) {
|
||||
this.groupId = groupId;
|
||||
membersList.getChildren().clear();
|
||||
|
||||
for (int i = 0; i < members.length(); i++) {
|
||||
JSONObject m = members.getJSONObject(i);
|
||||
|
||||
String role = m.optString("role", "member");
|
||||
if ("owner".equalsIgnoreCase(role) || "admin".equalsIgnoreCase(role)) {
|
||||
continue; // skip existing admins/owner
|
||||
}
|
||||
|
||||
addMemberRow(m);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberRow(JSONObject m) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = m.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(m.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(m.optBoolean("is_online", false) ? "online" : "last seen recently");
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
Button promoteBtn = new Button("Promote");
|
||||
promoteBtn.getStyleClass().add("link-btn");
|
||||
promoteBtn.setOnAction(e -> promoteToAdmin(m.optString("internal_uuid"), row));
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer, promoteBtn);
|
||||
membersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void promoteToAdmin(String internalUuid, HBox row) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/set_group_admin_permissions.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
SetGroupAdminPermissionsController controller = loader.getController();
|
||||
controller.setTarget(UUID.fromString(groupId), UUID.fromString(internalUuid), false);
|
||||
|
||||
// Pass a callback to remove row if promotion succeeds
|
||||
controller.setOnSuccess(() -> membersList.getChildren().remove(row));
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load permissions scene.", Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AddMembersController {
|
||||
|
||||
@FXML private VBox contactsList;
|
||||
@FXML private VBox addMembersCard;
|
||||
@FXML private TextField searchField;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private ScrollPane contactsScroll;
|
||||
@FXML private Button searchIcon;
|
||||
@FXML private Label memberCountLabel;
|
||||
@FXML private FlowPane selectedMembersPane;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button createButton;
|
||||
|
||||
private UUID groupInternalId;
|
||||
private String groupName;
|
||||
private String groupDisplayId;
|
||||
private File groupImageFile;
|
||||
|
||||
public enum Mode {
|
||||
CREATE, // from New Group
|
||||
ADD // from Group Info
|
||||
}
|
||||
|
||||
private Mode mode = Mode.CREATE; // default
|
||||
|
||||
private final Set<Contact> selectedContacts = new HashSet<>();
|
||||
private final List<Contact> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Smooth scroll feel
|
||||
contactsScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
contactsScroll.setPannable(true);
|
||||
contactsScroll.setFitToWidth(true);
|
||||
contactsScroll.setFitToHeight(false);
|
||||
contactsScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (addMembersCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||
}
|
||||
});
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||
|
||||
createButton.setOnAction(e -> onAddMembers());
|
||||
|
||||
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
|
||||
|
||||
Platform.runLater(() -> searchField.requestFocus());
|
||||
|
||||
loadContactsFromSession();
|
||||
updateMemberCount();
|
||||
renderContacts(allContacts);
|
||||
}
|
||||
|
||||
// Called when creating a new group from sidebar
|
||||
public void setGroupInfo(UUID internalId, String groupName, String displayId, File groupImageFile) {
|
||||
this.groupInternalId = internalId;
|
||||
this.groupName = groupName;
|
||||
this.groupDisplayId = displayId;
|
||||
this.groupImageFile = groupImageFile;
|
||||
this.mode = Mode.CREATE;
|
||||
updateActionButtonText();
|
||||
}
|
||||
|
||||
// Called when adding to an existing group
|
||||
public void setGroupForAdd(UUID internalId, String groupName) {
|
||||
this.groupInternalId = internalId;
|
||||
this.groupName = groupName;
|
||||
this.mode = Mode.ADD;
|
||||
updateActionButtonText();
|
||||
}
|
||||
|
||||
private void updateActionButtonText() {
|
||||
if (createButton != null) {
|
||||
createButton.setText(mode == Mode.CREATE ? "Create" : "Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadContactsFromSession() {
|
||||
allContacts.clear();
|
||||
var u = Session.currentUser;
|
||||
var arr = (u == null) ? null : u.optJSONArray("contact_list");
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
var c = arr.optJSONObject(i);
|
||||
if (c == null) continue;
|
||||
String name = c.optString("profile_name", "");
|
||||
String id = c.optString("contact_id", ""); // ← internal_uuid مخاطب
|
||||
if (id.isBlank()) continue;
|
||||
String imageUrl = c.optString("image_url", "").trim();
|
||||
if (imageUrl.isEmpty() || imageUrl.equals("null")) {
|
||||
imageUrl = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
String status = Optional.ofNullable(c.optString("last_seen", ""))
|
||||
.filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse("");
|
||||
allContacts.add(new Contact(id, name, status, imageUrl));
|
||||
}
|
||||
}
|
||||
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
|
||||
private void applyFilter(String q) {
|
||||
String f = (q == null) ? "" : q.trim().toLowerCase(Locale.ROOT);
|
||||
List<Contact> filtered = allContacts.stream()
|
||||
.filter(c -> c.getName().toLowerCase(Locale.ROOT).contains(f))
|
||||
.collect(Collectors.toList());
|
||||
renderContacts(filtered);
|
||||
}
|
||||
|
||||
private void renderContacts(List<Contact> contacts) {
|
||||
contactsList.getChildren().clear();
|
||||
|
||||
if (contacts.isEmpty()) {
|
||||
StackPane emptyPane = new StackPane();
|
||||
emptyPane.setPrefHeight(240);
|
||||
emptyPane.setAlignment(Pos.CENTER);
|
||||
Label emptyLabel = new Label("No contacts found");
|
||||
emptyLabel.getStyleClass().add("no-contacts-label");
|
||||
emptyPane.getChildren().add(emptyLabel);
|
||||
contactsList.getChildren().add(emptyPane);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Contact c : contacts) {
|
||||
HBox item = new HBox(10);
|
||||
item.getStyleClass().add("contact-item");
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView(loadAvatar(c.getImageUrl()));
|
||||
avatar.setFitWidth(48);
|
||||
avatar.setFitHeight(48);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
// Details (name + status)
|
||||
VBox details = new VBox(2);
|
||||
Label nameLabel = new Label(c.getName());
|
||||
nameLabel.getStyleClass().add("contact-name");
|
||||
Label statusLabel = new Label(c.getStatus());
|
||||
statusLabel.getStyleClass().add("contact-status");
|
||||
details.getChildren().addAll(nameLabel, statusLabel);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
// Add elements (no checkbox here)
|
||||
item.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Highlight if already selected
|
||||
if (selectedContacts.contains(c)) {
|
||||
item.getStyleClass().add("contact-selected");
|
||||
}
|
||||
|
||||
// Toggle selection by clicking the whole row
|
||||
item.setOnMouseClicked(e -> {
|
||||
if (selectedContacts.contains(c)) {
|
||||
selectedContacts.remove(c);
|
||||
item.getStyleClass().remove("contact-selected");
|
||||
} else {
|
||||
selectedContacts.add(c);
|
||||
item.getStyleClass().add("contact-selected");
|
||||
}
|
||||
updateMemberCount();
|
||||
updateSelectedMembersPane();
|
||||
});
|
||||
|
||||
contactsList.getChildren().add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private Image loadAvatar(String path) {
|
||||
try {
|
||||
var in = getClass().getResourceAsStream(path);
|
||||
if (in != null) return new Image(in);
|
||||
|
||||
return new Image(path, true);
|
||||
} catch (Exception e) {
|
||||
return new Image(
|
||||
Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddMembers() {
|
||||
if (groupInternalId == null) {
|
||||
showToast("Group internal_id is missing. Make sure setGroupInfo(UUID, ...) was called.");
|
||||
return;
|
||||
}
|
||||
if (selectedContacts.isEmpty()) {
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> ids = selectedContacts.stream().map(Contact::getId).toList();
|
||||
|
||||
JSONObject batchReq = new JSONObject()
|
||||
.put("action", "add_members_to_group")
|
||||
.put("group_id", groupInternalId.toString())
|
||||
.put("user_ids", new JSONArray(ids));
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(batchReq);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allOk = true;
|
||||
for (String uid : ids) {
|
||||
JSONObject single = new JSONObject()
|
||||
.put("action", "add_members_to_group")
|
||||
.put("group_id", groupInternalId.toString())
|
||||
.put("user_id", uid);
|
||||
JSONObject r = ActionHandler.sendWithResponse(single);
|
||||
if (r == null || !"success".equalsIgnoreCase(r.optString("status"))) {
|
||||
allOk = false;
|
||||
}
|
||||
}
|
||||
boolean finalAllOk = allOk;
|
||||
Platform.runLater(() -> {
|
||||
if (!finalAllOk) {
|
||||
showToast("Some members failed.");
|
||||
}
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateSelectedMembersPane() {
|
||||
selectedMembersPane.getChildren().clear();
|
||||
for (Contact c : selectedContacts) {
|
||||
Label chip = new Label(c.getName());
|
||||
chip.getStyleClass().add("member-chip");
|
||||
selectedMembersPane.getChildren().add(chip);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMemberCount() {
|
||||
memberCountLabel.setText(selectedContacts.size() + " / 200000");
|
||||
}
|
||||
|
||||
private void updateSearchIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
searchIcon.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void showToast(String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(addMembersCard.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
|
||||
public static class Contact {
|
||||
private final String id; // internal_uuid
|
||||
private final String name;
|
||||
private final String status;
|
||||
private final String imageUrl;
|
||||
|
||||
public Contact(String id, String name, String status, String imageUrl) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.status = status;
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getStatus() { return status; }
|
||||
public String getImageUrl() { return imageUrl; }
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Contact c)) return false;
|
||||
return Objects.equals(id, c.id);
|
||||
}
|
||||
@Override public int hashCode() { return Objects.hash(id); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AddSubscriberController {
|
||||
|
||||
@FXML private VBox contactsList;
|
||||
@FXML private VBox addMembersCard;
|
||||
@FXML private TextField searchField;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private ScrollPane contactsScroll;
|
||||
@FXML private Button searchIcon;
|
||||
@FXML private Label memberCountLabel;
|
||||
@FXML private FlowPane selectedMembersPane;
|
||||
@FXML private Button skipButton;
|
||||
@FXML private Button addButton;
|
||||
|
||||
private UUID channelInternalId;
|
||||
private String channelName;
|
||||
private String channelDisplayId;
|
||||
private File channelImageFile;
|
||||
private String description;
|
||||
|
||||
private final Set<Contact> selected = new HashSet<>();
|
||||
private final List<Contact> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
contactsScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
contactsScroll.setPannable(true);
|
||||
contactsScroll.setFitToWidth(true);
|
||||
contactsScroll.setFitToHeight(false);
|
||||
contactsScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (addMembersCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(addMembersCard.getScene());
|
||||
}
|
||||
});
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, ov, nv) -> updateSearchIcon(nv));
|
||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||
skipButton.setOnAction(e -> MainController.getInstance().closeOverlay(addMembersCard.getParent()));
|
||||
addButton.setOnAction(e -> onAddSubscribers());
|
||||
|
||||
searchField.textProperty().addListener((obs, ov, nv) -> applyFilter(nv));
|
||||
Platform.runLater(() -> searchField.requestFocus());
|
||||
|
||||
loadContactsFromSession();
|
||||
updateCount();
|
||||
renderContacts(allContacts);
|
||||
}
|
||||
|
||||
public void setChannelInfo(UUID internalId, String name, String displayId, File imageFile, String description) {
|
||||
this.channelInternalId = internalId;
|
||||
this.channelName = name;
|
||||
this.channelDisplayId = displayId;
|
||||
this.channelImageFile = imageFile;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setChannelInfo(String name, String id, String description, File image) {
|
||||
this.channelName = name;
|
||||
this.channelDisplayId = id;
|
||||
this.channelImageFile = image;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
private void loadContactsFromSession() {
|
||||
allContacts.clear();
|
||||
var u = Session.currentUser;
|
||||
var arr = (u == null) ? null : u.optJSONArray("contact_list");
|
||||
if (arr != null) {
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
var c = arr.optJSONObject(i);
|
||||
if (c == null) continue;
|
||||
String name = c.optString("profile_name", "");
|
||||
String id = c.optString("contact_id", ""); // internal_uuid
|
||||
if (id.isBlank()) continue;
|
||||
String imageUrl = c.optString("image_url", "").trim();
|
||||
if (imageUrl.isEmpty() || imageUrl.equals("null")) {
|
||||
imageUrl = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
String status = Optional.ofNullable(c.optString("last_seen", ""))
|
||||
.filter(s -> !s.isBlank()).map(s -> "last seen " + s).orElse("");
|
||||
allContacts.add(new Contact(id, name, status, imageUrl));
|
||||
}
|
||||
}
|
||||
allContacts.sort(Comparator.comparing(Contact::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
|
||||
private void applyFilter(String q) {
|
||||
String f = (q == null) ? "" : q.trim().toLowerCase(Locale.ROOT);
|
||||
List<Contact> filtered = allContacts.stream()
|
||||
.filter(c -> c.getName().toLowerCase(Locale.ROOT).contains(f))
|
||||
.collect(Collectors.toList());
|
||||
renderContacts(filtered);
|
||||
}
|
||||
|
||||
private void renderContacts(List<Contact> contacts) {
|
||||
contactsList.getChildren().clear();
|
||||
|
||||
if (contacts.isEmpty()) {
|
||||
StackPane emptyPane = new StackPane();
|
||||
emptyPane.setPrefHeight(240);
|
||||
emptyPane.setAlignment(Pos.CENTER);
|
||||
Label emptyLabel = new Label("No contacts found");
|
||||
emptyLabel.getStyleClass().add("no-contacts-label");
|
||||
emptyPane.getChildren().add(emptyLabel);
|
||||
contactsList.getChildren().add(emptyPane);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Contact c : contacts) {
|
||||
HBox item = new HBox(10);
|
||||
item.getStyleClass().add("contact-item");
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView(loadAvatar(c.getImageUrl()));
|
||||
avatar.setFitWidth(48);
|
||||
avatar.setFitHeight(48);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
// Details
|
||||
VBox details = new VBox(2);
|
||||
Label nameLabel = new Label(c.getName());
|
||||
nameLabel.getStyleClass().add("contact-name");
|
||||
Label statusLabel = new Label(c.getStatus());
|
||||
statusLabel.getStyleClass().add("contact-status");
|
||||
details.getChildren().addAll(nameLabel, statusLabel);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
// Add elements (no checkbox)
|
||||
item.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Highlight if already selected
|
||||
if (selected.contains(c)) {
|
||||
item.getStyleClass().add("contact-selected");
|
||||
}
|
||||
|
||||
// Toggle selection by clicking the row
|
||||
item.setOnMouseClicked(e -> {
|
||||
if (selected.contains(c)) {
|
||||
selected.remove(c);
|
||||
item.getStyleClass().remove("contact-selected");
|
||||
} else {
|
||||
selected.add(c);
|
||||
item.getStyleClass().add("contact-selected");
|
||||
}
|
||||
updateCount();
|
||||
updateSelectedPane();
|
||||
});
|
||||
|
||||
contactsList.getChildren().add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private Image loadAvatar(String path) {
|
||||
try {
|
||||
var in = getClass().getResourceAsStream(path);
|
||||
if (in != null) return new Image(in);
|
||||
return new Image(path, true);
|
||||
} catch (Exception e) {
|
||||
return new Image(
|
||||
Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddSubscribers() {
|
||||
if (channelInternalId == null) {
|
||||
showToast("Channel internal_id is missing. Make sure setChannelInfo(UUID, ...) was called.");
|
||||
return;
|
||||
}
|
||||
if (selected.isEmpty()) {
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> ids = selected.stream().map(Contact::getId).toList();
|
||||
|
||||
JSONObject batchReq = new JSONObject()
|
||||
.put("action", "add_subscribers_to_channel")
|
||||
.put("channel_id", channelInternalId.toString())
|
||||
.put("user_ids", new JSONArray(ids));
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(batchReq);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allOk = true;
|
||||
for (String uid : ids) {
|
||||
JSONObject single = new JSONObject()
|
||||
.put("action", "add_subscriber_to_channel")
|
||||
.put("channel_id", channelInternalId.toString())
|
||||
.put("user_id", uid);
|
||||
JSONObject r = ActionHandler.sendWithResponse(single);
|
||||
if (r == null || !"success".equalsIgnoreCase(r.optString("status"))) {
|
||||
allOk = false;
|
||||
}
|
||||
}
|
||||
boolean finalAllOk = allOk;
|
||||
Platform.runLater(() -> {
|
||||
if (!finalAllOk) {
|
||||
showToast("Some subscribers failed.");
|
||||
}
|
||||
MainController.getInstance().closeOverlay(addMembersCard.getParent());
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateSelectedPane() {
|
||||
selectedMembersPane.getChildren().clear();
|
||||
for (Contact c : selected) {
|
||||
Label chip = new Label(c.getName());
|
||||
chip.getStyleClass().add("member-chip");
|
||||
selectedMembersPane.getChildren().add(chip);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCount() {
|
||||
memberCountLabel.setText(selected.size() + " / 200000");
|
||||
}
|
||||
|
||||
private void updateSearchIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
searchIcon.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void showToast(String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(addMembersCard.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
|
||||
public static class Contact {
|
||||
private final String id; // internal_uuid
|
||||
private final String name;
|
||||
private final String status;
|
||||
private final String imageUrl;
|
||||
|
||||
public Contact(String id, String name, String status, String imageUrl) {
|
||||
this.id = id; this.name = name; this.status = status; this.imageUrl = imageUrl;
|
||||
}
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getStatus() { return status; }
|
||||
public String getImageUrl() { return imageUrl; }
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Contact c)) return false;
|
||||
return Objects.equals(id, c.id);
|
||||
}
|
||||
@Override public int hashCode() { return Objects.hash(id); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// org.to.telegramfinalproject.UI.AppRouter
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public final class AppRouter {
|
||||
private static Stage stage;
|
||||
private static Scene scene;
|
||||
|
||||
private AppRouter() {}
|
||||
|
||||
public static void init(Stage st, Scene sc) {
|
||||
stage = st;
|
||||
scene = sc;
|
||||
}
|
||||
|
||||
public static void showIntro() { setRoot("/org/to/telegramfinalproject/Fxml/intro.fxml"); }
|
||||
public static void showLogin() { setRoot("/org/to/telegramfinalproject/Fxml/login_view.fxml"); }
|
||||
public static void showRegister(){ setRoot("/org/to/telegramfinalproject/Fxml/register_view.fxml"); }
|
||||
public static void showMain() { setRoot("/org/to/telegramfinalproject/Fxml/main.fxml"); }
|
||||
|
||||
private static void setRoot(String fxmlPath) {
|
||||
try {
|
||||
System.out.println("Router: setRoot -> " + fxmlPath);
|
||||
FXMLLoader fx = new FXMLLoader(AppRouter.class.getResource(fxmlPath));
|
||||
Parent root = fx.load();
|
||||
if (scene != null) scene.setRoot(root);
|
||||
else if (stage != null) stage.setScene(new Scene(root, 1480, 820));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
||||
public final class AvatarFX {
|
||||
|
||||
public static void circleClip(ImageView iv, double sizePx) {
|
||||
iv.setFitWidth(sizePx);
|
||||
iv.setFitHeight(sizePx);
|
||||
iv.setPreserveRatio(true);
|
||||
iv.setSmooth(true);
|
||||
|
||||
Circle c = new Circle();
|
||||
c.radiusProperty().bind(Bindings.min(iv.fitWidthProperty(), iv.fitHeightProperty()).divide(2));
|
||||
c.centerXProperty().bind(iv.fitWidthProperty().divide(2));
|
||||
c.centerYProperty().bind(iv.fitHeightProperty().divide(2));
|
||||
iv.setClip(c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class BlockedUsersController {
|
||||
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button backButton;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Label countLabel;
|
||||
@FXML private ListView<HBox> blockedList;
|
||||
@FXML private VBox blockedCard;
|
||||
|
||||
private final Map<UUID, HBox> rowByUserId = new HashMap<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
backButton.setGraphic(makeIcon("/org/to/telegramfinalproject/Icons/back_button_dark.png"));
|
||||
|
||||
loadBlockedUsers();
|
||||
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayBackground.getParent()));
|
||||
backButton.setOnAction(e -> MainController.getInstance().goBack((StackPane) overlayBackground.getParent()));
|
||||
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(blockedCard.getParent()));
|
||||
|
||||
// Theme
|
||||
Platform.runLater(() -> {
|
||||
if (blockedCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(blockedCard.getScene());
|
||||
}
|
||||
});
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateBackIcon(newVal));
|
||||
updateBackIcon(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// Smooth scroll feel
|
||||
blockedList.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
blockedList.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
ScrollBar vBar = (ScrollBar) blockedList.lookup(".scroll-bar:vertical");
|
||||
if (vBar != null) {
|
||||
blockedList.setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
double newValue = vBar.getValue() - deltaY;
|
||||
vBar.setValue(Math.max(0, Math.min(newValue, 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ===================== Networking ===================== */
|
||||
|
||||
private void loadBlockedUsers() {
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject().put("action", "get_blocked_users");
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
System.err.println("get_blocked_users failed: " + (resp != null ? resp.optString("message") : "no response"));
|
||||
Platform.runLater(() -> {
|
||||
blockedList.getItems().clear();
|
||||
updateCount();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
JSONArray arr = data != null ? data.optJSONArray("blocked_users") : null;
|
||||
if (arr == null) arr = new JSONArray();
|
||||
|
||||
final JSONArray finalArr = arr;
|
||||
Platform.runLater(() -> renderBlockedUsers(finalArr));
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void unblockUser(UUID targetId) {
|
||||
String me = Session.currentUser != null ? Session.currentUser.optString("internal_uuid", "") : "";
|
||||
if (me.isBlank() || targetId == null) return;
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "toggle_block")
|
||||
.put("user_id", me) // UUID
|
||||
.put("target_id", targetId.toString()); // UUID
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
boolean ok = resp != null && "success".equalsIgnoreCase(resp.optString("status"));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (ok) {
|
||||
HBox row = rowByUserId.remove(targetId);
|
||||
if (row != null) blockedList.getItems().remove(row);
|
||||
updateCount();
|
||||
} else {
|
||||
String msg = (resp != null ? resp.optString("message", "Failed to unblock.") : "Failed to unblock.");
|
||||
showToast(msg);
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
/* ===================== UI build ===================== */
|
||||
|
||||
private void renderBlockedUsers(JSONArray list) {
|
||||
blockedList.getItems().clear();
|
||||
rowByUserId.clear();
|
||||
|
||||
IntStream.range(0, list.length()).forEach(i -> {
|
||||
JSONObject o = list.optJSONObject(i);
|
||||
if (o == null) return;
|
||||
|
||||
UUID uid = parseUUID(optS(o, "internal_uuid"));
|
||||
if (uid == null) return;
|
||||
|
||||
String name = nz(optS(o, "profile_name", optS(o, "name", "User")));
|
||||
String handle = firstNonEmpty(
|
||||
optS(o, "username"),
|
||||
optS(o, "display_id"),
|
||||
optS(o, "user_name"),
|
||||
""
|
||||
);
|
||||
String avatarUrl = optS(o, "image_url");
|
||||
|
||||
HBox row = buildRow(uid, name, handle, avatarUrl);
|
||||
rowByUserId.put(uid, row);
|
||||
blockedList.getItems().add(row);
|
||||
});
|
||||
|
||||
updateCount();
|
||||
}
|
||||
|
||||
private HBox buildRow(UUID uid, String name, String id, String avatarUrl) {
|
||||
// Avatar
|
||||
Image img = null;
|
||||
if (hasVal(avatarUrl)) {
|
||||
try { img = org.to.telegramfinalproject.Client.AvatarLocalResolver.load(avatarUrl); } catch (Exception ignore) {}
|
||||
}
|
||||
if (img == null) {
|
||||
img = new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png"));
|
||||
}
|
||||
|
||||
ImageView avatar = new ImageView(img);
|
||||
avatar.setFitWidth(60);
|
||||
avatar.setFitHeight(60);
|
||||
avatar.setPreserveRatio(true);
|
||||
avatar.setSmooth(true);
|
||||
avatar.setCache(true);
|
||||
avatar.setClip(new Circle(20, 20, 40));
|
||||
|
||||
// Name + ID
|
||||
VBox info = new VBox(2);
|
||||
Label nameLabel = new Label(name);
|
||||
nameLabel.getStyleClass().add("blocked-name");
|
||||
Label idLabel = new Label(id);
|
||||
idLabel.getStyleClass().add("blocked-id");
|
||||
info.getChildren().addAll(nameLabel, idLabel);
|
||||
|
||||
// Unblock btn
|
||||
Button unblockBtn = new Button("Unblock");
|
||||
unblockBtn.getStyleClass().add("unblock-button");
|
||||
unblockBtn.setOnAction(e -> unblockUser(uid));
|
||||
|
||||
HBox row = new HBox(12, avatar, info, unblockBtn);
|
||||
row.getStyleClass().add("blocked-row");
|
||||
HBox.setHgrow(info, javafx.scene.layout.Priority.ALWAYS);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void updateCount() {
|
||||
countLabel.setText(blockedList.getItems().size() + " blocked users");
|
||||
}
|
||||
|
||||
/* ===================== Helpers ===================== */
|
||||
|
||||
private void updateBackIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/back_button_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/back_button_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
backButton.setGraphic(icon);
|
||||
}
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView(new Image(getClass().getResourceAsStream(path)));
|
||||
iv.setFitWidth(20);
|
||||
iv.setFitHeight(20);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private static String nz(String s){ return s==null? "": s.trim(); }
|
||||
private static boolean hasVal(String s){ return s!=null && !s.trim().isEmpty() && !"null".equalsIgnoreCase(s); }
|
||||
private static String optS(JSONObject j, String k){ return j!=null ? nz(j.optString(k,"")) : ""; }
|
||||
private static String optS(JSONObject j, String k, String fallback){
|
||||
String v = optS(j,k);
|
||||
return hasVal(v) ? v : fallback;
|
||||
}
|
||||
private static String firstNonEmpty(String... vals){
|
||||
for (String v : vals) if (hasVal(v)) return v;
|
||||
return "";
|
||||
}
|
||||
private static UUID parseUUID(String s){
|
||||
try { return UUID.fromString(nz(s)); } catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private void showToast(String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
if (a.getDialogPane().getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(a.getDialogPane().getScene());
|
||||
}
|
||||
a.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
public class ChangeCredentialsController {
|
||||
|
||||
@FXML private VBox credentialsCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private Label usernameLabel;
|
||||
|
||||
@FXML private Label passwordLabel;
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private TextField visiblePasswordField;
|
||||
@FXML private Button togglePasswordBtn;
|
||||
|
||||
@FXML private Label confirmPasswordLabel;
|
||||
@FXML private PasswordField confirmPasswordField;
|
||||
@FXML private TextField visibleConfirmPasswordField;
|
||||
@FXML private Button toggleConfirmBtn;
|
||||
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button saveButton;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
private boolean passwordVisible = false;
|
||||
private boolean confirmVisible = false;
|
||||
|
||||
private String currentPassword;
|
||||
public void setCurrentPassword(String p) { this.currentPassword = p; }
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
// Toggle password visibility
|
||||
togglePasswordBtn.setOnAction(e -> togglePasswordVisibility());
|
||||
toggleConfirmBtn.setOnAction(e -> toggleConfirmVisibility());
|
||||
|
||||
saveButton.setOnAction(e -> saveAndClose());
|
||||
closeButton.setOnAction(e -> saveAndClose());
|
||||
cancelButton.setOnAction(e -> saveAndClose());
|
||||
overlayBackground.setOnMouseClicked(e -> saveAndClose());
|
||||
|
||||
// Reset error when typing
|
||||
usernameField.textProperty().addListener((obs, o, n) -> removeError(usernameField, usernameLabel));
|
||||
|
||||
passwordField.textProperty().addListener((obs, o, n) -> {
|
||||
visiblePasswordField.setText(n); // keep synced
|
||||
removeError(passwordField, passwordLabel);
|
||||
removeError(visiblePasswordField, passwordLabel);
|
||||
});
|
||||
visiblePasswordField.textProperty().addListener((obs, o, n) -> {
|
||||
passwordField.setText(n);
|
||||
removeError(passwordField, passwordLabel);
|
||||
removeError(visiblePasswordField, passwordLabel);
|
||||
});
|
||||
|
||||
confirmPasswordField.textProperty().addListener((obs, o, n) -> {
|
||||
visibleConfirmPasswordField.setText(n);
|
||||
removeError(confirmPasswordField, confirmPasswordLabel);
|
||||
removeError(visibleConfirmPasswordField, confirmPasswordLabel);
|
||||
});
|
||||
visibleConfirmPasswordField.textProperty().addListener((obs, o, n) -> {
|
||||
confirmPasswordField.setText(n);
|
||||
removeError(confirmPasswordField, confirmPasswordLabel);
|
||||
removeError(visibleConfirmPasswordField, confirmPasswordLabel);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> usernameField.requestFocus());
|
||||
}
|
||||
|
||||
public void prefillFromSession() {
|
||||
var u = Session.currentUser;
|
||||
if (u == null) return;
|
||||
String handle = u.optString("user_id",
|
||||
u.optString("username",
|
||||
u.optString("display_id","")));
|
||||
usernameField.setText(handle);
|
||||
}
|
||||
|
||||
private void togglePasswordVisibility() {
|
||||
passwordVisible = !passwordVisible;
|
||||
visiblePasswordField.setText(passwordField.getText());
|
||||
visiblePasswordField.setVisible(passwordVisible);
|
||||
visiblePasswordField.setManaged(passwordVisible);
|
||||
|
||||
passwordField.setVisible(!passwordVisible);
|
||||
passwordField.setManaged(!passwordVisible);
|
||||
|
||||
togglePasswordBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
private void toggleConfirmVisibility() {
|
||||
confirmVisible = !confirmVisible;
|
||||
visibleConfirmPasswordField.setText(confirmPasswordField.getText());
|
||||
visibleConfirmPasswordField.setVisible(confirmVisible);
|
||||
visibleConfirmPasswordField.setManaged(confirmVisible);
|
||||
|
||||
confirmPasswordField.setVisible(!confirmVisible);
|
||||
confirmPasswordField.setManaged(!confirmVisible);
|
||||
|
||||
toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
private void saveAndClose() {
|
||||
boolean anyError = false;
|
||||
boolean sentSomething = false;
|
||||
|
||||
var cu = Session.currentUser;
|
||||
String oldUsername = cu != null ? cu.optString("user_id",
|
||||
cu.optString("username",
|
||||
cu.optString("display_id",""))) : "";
|
||||
|
||||
String newUsername = usernameField.getText().trim();
|
||||
if (!newUsername.isEmpty() && !newUsername.equals(oldUsername)) {
|
||||
sentSomething = true;
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "update_username")
|
||||
.put("current_password", currentPassword)
|
||||
.put("new_username", newUsername);
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
anyError = true;
|
||||
showError("Changing username failed",
|
||||
resp != null ? resp.optString("message","Unknown error") : "No response");
|
||||
} else {
|
||||
if (cu != null) cu.put("user_id", newUsername);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) پسورد
|
||||
String newPwd = (passwordVisible ? visiblePasswordField.getText() : passwordField.getText()).trim();
|
||||
String confirm = (confirmVisible ? visibleConfirmPasswordField.getText() : confirmPasswordField.getText()).trim();
|
||||
|
||||
if (!newPwd.isEmpty()) {
|
||||
if (confirm.isEmpty() || !confirm.equals(newPwd)) {
|
||||
addError(confirmPasswordField, confirmPasswordLabel);
|
||||
addError(visibleConfirmPasswordField, confirmPasswordLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
sentSomething = true;
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "update_password")
|
||||
.put("current_password", currentPassword)
|
||||
.put("new_password", newPwd);
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
anyError = true;
|
||||
showError("Changing password failed",
|
||||
resp != null ? resp.optString("message","Unknown error") : "No response");
|
||||
}
|
||||
}
|
||||
|
||||
if (!sentSomething || !anyError) {
|
||||
var sc = SettingsController.getInstance();
|
||||
if (sc != null) sc.populateFromSession();
|
||||
MainController.getInstance().closeOverlay(credentialsCard.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
private void addError(TextField field, Label label) {
|
||||
if (!field.getStyleClass().contains("error-input")) {
|
||||
field.getStyleClass().add("error-input");
|
||||
}
|
||||
if (!label.getStyleClass().contains("error-label")) {
|
||||
label.getStyleClass().add("error-label");
|
||||
}
|
||||
}
|
||||
|
||||
private void removeError(TextField field, Label label) {
|
||||
field.getStyleClass().remove("error-input");
|
||||
label.getStyleClass().remove("error-label");
|
||||
}
|
||||
|
||||
private void showError(String title, String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.ERROR);
|
||||
a.setTitle(title);
|
||||
a.setHeaderText(null);
|
||||
a.setContentText(msg);
|
||||
if (a.getDialogPane().getScene()!=null) {
|
||||
ThemeManager.getInstance().registerScene(a.getDialogPane().getScene());
|
||||
}
|
||||
a.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelInfoController {
|
||||
|
||||
@FXML private VBox channelCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
@FXML private ImageView channelAvatar;
|
||||
@FXML private Label channelName;
|
||||
@FXML private Label subscriberCount;
|
||||
@FXML private Label channelDescription;
|
||||
|
||||
@FXML private Button infoMoreButton;
|
||||
@FXML private ContextMenu infoMoreMenu;
|
||||
@FXML private MenuItem addMembersBtn;
|
||||
@FXML private MenuItem manageChannelBtn;
|
||||
@FXML private MenuItem deleteChannelBtn;
|
||||
@FXML private ImageView moreIcon;
|
||||
@FXML private ImageView manageChannelIcon;
|
||||
@FXML private ImageView addMemberIcon;
|
||||
|
||||
@FXML private VBox subscribersList;
|
||||
@FXML private Label subscribersHeader;
|
||||
@FXML private Button addSubscriberButton;
|
||||
@FXML private ImageView subscribersIcon;
|
||||
@FXML private ScrollPane subscribersScroll;
|
||||
|
||||
private UUID channelId;
|
||||
private String myRole;
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(channelCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(channelCard.getParent()));
|
||||
|
||||
infoMoreButton.setOnAction(e -> {
|
||||
if (infoMoreMenu != null) infoMoreMenu.show(infoMoreButton, javafx.geometry.Side.BOTTOM, 0, 0);
|
||||
});
|
||||
|
||||
if (manageChannelBtn != null) {
|
||||
manageChannelBtn.setOnAction(e -> openManageChannel());
|
||||
}
|
||||
deleteChannelBtn.setOnAction(e -> handleDeleteChannel());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (channelCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(channelCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateIcons(newVal);
|
||||
});
|
||||
|
||||
updateIcons(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// Smooth scroll feel
|
||||
subscribersScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
subscribersScroll.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
ScrollBar vBar = (ScrollBar) subscribersScroll.lookup(".scroll-bar:vertical");
|
||||
if (vBar != null) {
|
||||
subscribersScroll.setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
double newValue = vBar.getValue() - deltaY;
|
||||
vBar.setValue(Math.max(0, Math.min(newValue, 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (addSubscriberButton != null) {
|
||||
addSubscriberButton.setOnAction(e -> openAddSubscriberScene());
|
||||
}
|
||||
if (addMembersBtn != null) {
|
||||
addMembersBtn.setOnAction(e -> openAddSubscriberScene());
|
||||
}
|
||||
}
|
||||
|
||||
public void setChannelDataFromJson(ChatEntry entry, JSONObject data) {
|
||||
this.channelId = entry.getId();
|
||||
|
||||
channelName.setText(data.optString("channel_name", entry.getName()));
|
||||
subscriberCount.setText(data.optInt("subscriber_count", 0) + " subscribers");
|
||||
channelDescription.setText(data.optString("description", ""));
|
||||
|
||||
// --- Role-based UI ---
|
||||
myRole = data.optString("my_role", "subscriber").toLowerCase();
|
||||
|
||||
deleteChannelBtn.setVisible("owner".equals(myRole));
|
||||
|
||||
boolean canAdd = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
addSubscriberButton.setVisible(canAdd);
|
||||
addSubscriberButton.setManaged(canAdd);
|
||||
if (addMembersBtn != null) {
|
||||
addMembersBtn.setVisible(canAdd);
|
||||
}
|
||||
|
||||
boolean showMore = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
infoMoreButton.setVisible(showMore);
|
||||
infoMoreButton.setManaged(showMore);
|
||||
|
||||
// --- Avatar ---
|
||||
String imgUrl = data.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
try {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) channelAvatar.setImage(img);
|
||||
} catch (Exception ignore) {}
|
||||
} else {
|
||||
channelAvatar.setImage(new Image(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_channel_profile.png")
|
||||
));
|
||||
}
|
||||
|
||||
// --- Subscribers list ---
|
||||
subscribersList.getChildren().clear();
|
||||
var arr = data.optJSONArray("subscribers");
|
||||
if (arr != null) {
|
||||
subscribersHeader.setText(arr.length() + " SUBSCRIBERS");
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject s = arr.getJSONObject(i);
|
||||
addSubscriberRow(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addSubscriberRow(JSONObject s) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
AvatarFX.circleClip(avatar, 36);
|
||||
|
||||
String imgUrl = s.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + status
|
||||
VBox nameBox = new VBox(2);
|
||||
Label name = new Label(s.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
String status;
|
||||
if (s.optBoolean("is_online", false)) {
|
||||
status = "online";
|
||||
} else {
|
||||
status = ChatPageController.getInstance().userStatusText(
|
||||
false,
|
||||
s.optString("last_seen", null)
|
||||
);
|
||||
}
|
||||
Label statusLbl = new Label(status);
|
||||
statusLbl.getStyleClass().add("member-status");
|
||||
|
||||
nameBox.getChildren().addAll(name, statusLbl);
|
||||
|
||||
// Role
|
||||
Label roleLbl = new Label();
|
||||
String roleStr = s.optString("role", "");
|
||||
if (!roleStr.isBlank()) {
|
||||
roleLbl.setText(roleStr.toLowerCase());
|
||||
roleLbl.getStyleClass().add("member-role");
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, nameBox, spacer, roleLbl);
|
||||
subscribersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void openManageChannel() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_channel")
|
||||
.put("channel_id", channelId.toString()); // ✅ matches server case
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
resp != null ? resp.optString("message") : "Server not responding.",
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
if (data == null) {
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Malformed server response.",
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_channel.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageChannelController controller = loader.getController();
|
||||
controller.setChannelData(data); // ✅ pass JSON to controller
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Manage Channel scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Error while fetching channel info: " + e.getMessage(),
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openAddSubscriberScene() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml")); // point to new fxml
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddSubscriberController controller = loader.getController();
|
||||
controller.setChannelInfo(channelId, channelName.getText(), "", null, "");
|
||||
// passing UUID + name, other fields optional
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Add Subscribers scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
private void handleDeleteChannel() {
|
||||
ChatEntry entry = Session.currentChatEntry;
|
||||
|
||||
if (entry == null || !"channel".equalsIgnoreCase(entry.getType())) {
|
||||
alert(Alert.AlertType.INFORMATION, "This action is only available for channels.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm("Delete Channel",
|
||||
"Are you sure you want to DELETE this channel?\nThis action cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "delete_channel")
|
||||
.put("channel_id", entry.getId().toString());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
alert(Alert.AlertType.INFORMATION, "✅ Channel deleted successfully.");
|
||||
MainController.getInstance().refreshChatListUI();
|
||||
AppRouter.showMain();
|
||||
} else {
|
||||
String msg = (res != null) ? res.optString("message", "Failed to delete channel.") : "null response";
|
||||
alert(Alert.AlertType.ERROR, "❌ " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean confirm(String title, String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.CONFIRMATION, msg, ButtonType.OK, ButtonType.CANCEL);
|
||||
a.setTitle(title);
|
||||
return a.showAndWait().filter(btn -> btn == ButtonType.OK).isPresent();
|
||||
}
|
||||
|
||||
private void alert(Alert.AlertType type, String msg) {
|
||||
new Alert(type, msg, ButtonType.OK).show();
|
||||
}
|
||||
|
||||
private void updateIcons(boolean dark) {
|
||||
String suffix = dark ? "_light.png" : "_dark.png";
|
||||
|
||||
moreIcon.setImage(loadImage(ICON_PATH + "more" + suffix));
|
||||
subscribersIcon.setImage(loadImage(ICON_PATH + "channel_subscriber" + suffix));
|
||||
addSubscriberButton.setGraphic(makeIcon(ICON_PATH + "add_member" + suffix));
|
||||
manageChannelIcon.setImage(loadImage(ICON_PATH + "manage" + suffix));
|
||||
addMemberIcon.setImage(loadImage(ICON_PATH + "add_member" + suffix));
|
||||
}
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) return null;
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ChatItemController {
|
||||
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label chatName;
|
||||
@FXML private Label lastMessage;
|
||||
@FXML private Label chatTime;
|
||||
@FXML private Label unreadCount;
|
||||
@FXML private StackPane systemAvatar;
|
||||
@FXML private ImageView profileImageUser;
|
||||
@FXML private ImageView profileImageSystem;
|
||||
@FXML private Circle systemCircle;
|
||||
|
||||
/**
|
||||
* Set chat item data, including a profile image (or default).
|
||||
*
|
||||
* @param name Chat name
|
||||
* @param lastMsg Last message text
|
||||
* @param time Last message time
|
||||
* @param unread Unread message count
|
||||
* @param imageUrl Path/URL of profile image (can be null/empty)
|
||||
*/
|
||||
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl, String chatType) {
|
||||
chatName.setText(name);
|
||||
lastMessage.setText(lastMsg);
|
||||
chatTime.setText(time);
|
||||
|
||||
// Unread count
|
||||
if (unread > 0) {
|
||||
unreadCount.setVisible(true);
|
||||
unreadCount.setText(String.valueOf(unread));
|
||||
} else {
|
||||
unreadCount.setVisible(false);
|
||||
}
|
||||
|
||||
// Reset all avatar states
|
||||
profileImageUser.setVisible(false);
|
||||
profileImageUser.setManaged(false);
|
||||
systemAvatar.setVisible(false);
|
||||
systemAvatar.setManaged(false);
|
||||
|
||||
if ("Saved Messages".equals(name)) {
|
||||
systemCircle.setFill(javafx.scene.paint.Color.web("#2ca4ff")); // Telegram blue
|
||||
profileImageSystem.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Icons/saved_messages_light.png"))
|
||||
));
|
||||
systemAvatar.setVisible(true);
|
||||
systemAvatar.setManaged(true);
|
||||
|
||||
} else if ("Archived Chats".equals(name)) {
|
||||
systemCircle.setFill(javafx.scene.paint.Color.web("#808080")); // gray
|
||||
profileImageSystem.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Icons/archived_chats_light.png"))
|
||||
));
|
||||
systemAvatar.setVisible(true);
|
||||
systemAvatar.setManaged(true);
|
||||
|
||||
} else if (imageUrl != null && !imageUrl.isEmpty()) {
|
||||
// profileImageUser.setImage(new Image(imageUrl, true));
|
||||
// profileImageUser.setVisible(true);
|
||||
// profileImageUser.setManaged(true);
|
||||
|
||||
Image img = AvatarLocalResolver.load(imageUrl);
|
||||
if (img != null) {
|
||||
profileImageUser.setImage(img);
|
||||
AvatarFX.circleClip(profileImageUser, 40);
|
||||
} else {
|
||||
String path;
|
||||
if ("group".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||
} else if ("channel".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||
} else {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
profileImageUser.setImage(new Image(
|
||||
java.util.Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||
));
|
||||
}
|
||||
profileImageUser.setVisible(true);
|
||||
profileImageUser.setManaged(true);
|
||||
|
||||
} else {
|
||||
String path;
|
||||
if ("group".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||
} else if ("channel".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||
} else {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
|
||||
profileImageUser.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||
));
|
||||
profileImageUser.setVisible(true);
|
||||
profileImageUser.setManaged(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnread(int unread) {
|
||||
boolean show = unread > 0;
|
||||
unreadCount.setVisible(show);
|
||||
unreadCount.setManaged(show);
|
||||
if (show) unreadCount.setText(String.valueOf(unread));
|
||||
}
|
||||
|
||||
// ChatItemController.java
|
||||
@FXML private ImageView avatarImage;
|
||||
private String chatType;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private String safeTitle(ChatEntry e) {
|
||||
if (e.getName() != null && !e.getName().isBlank()) return e.getName();
|
||||
if (e.getDisplayId() != null && !e.getDisplayId().isBlank()) return e.getDisplayId();
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
|
||||
//For search handling
|
||||
public enum ChatViewMode {
|
||||
NORMAL, // member/contact; can send messages
|
||||
NEEDS_JOIN, // group/channel preview; show Join button
|
||||
NEEDS_ADD_CONTACT, // private preview; show Add Contact button
|
||||
READ_ONLY,
|
||||
BLOCKED
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class CheckPasswordController {
|
||||
|
||||
@FXML private VBox checkPasswordCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button backButton;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button confirmButton;
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private TextField visiblePasswordField;
|
||||
@FXML private Button toggleVisibilityBtn;
|
||||
@FXML private Label passwordLabel;
|
||||
|
||||
private boolean passwordVisible = false;
|
||||
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Set back button icon
|
||||
ImageView backIcon = new ImageView(
|
||||
new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/back_button_dark.png"))
|
||||
);
|
||||
backIcon.setFitWidth(18);
|
||||
backIcon.setFitHeight(18);
|
||||
backButton.setGraphic(backIcon);
|
||||
|
||||
cancelButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(checkPasswordCard.getParent())
|
||||
);
|
||||
|
||||
closeButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(checkPasswordCard.getParent())
|
||||
);
|
||||
|
||||
backButton.setOnAction(e ->
|
||||
MainController.getInstance().goBack(checkPasswordCard)
|
||||
);
|
||||
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(checkPasswordCard.getParent())
|
||||
);
|
||||
|
||||
confirmButton.setOnAction(e -> validatePassword());
|
||||
|
||||
// Reset error state when user starts typing again
|
||||
passwordField.textProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (!newVal.trim().isEmpty()) {
|
||||
passwordField.getStyleClass().remove("error");
|
||||
passwordLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
|
||||
// Reset error state when user starts typing again
|
||||
visiblePasswordField.textProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (!newVal.trim().isEmpty()) {
|
||||
visiblePasswordField.getStyleClass().remove("error");
|
||||
passwordLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
|
||||
// Auto_focus password field when overlay opens
|
||||
Platform.runLater(() -> passwordField.requestFocus());
|
||||
|
||||
// Keep fields in sync
|
||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||
|
||||
// Toggle button action
|
||||
toggleVisibilityBtn.setOnAction(e -> togglePasswordVisibility());
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (checkPasswordCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(checkPasswordCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// Listener for theme change
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateBackIcon(newVal);
|
||||
});
|
||||
|
||||
// Set initial state
|
||||
updateBackIcon(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
private void validatePassword() {
|
||||
String entered = (passwordVisible ? visiblePasswordField.getText() : passwordField.getText()).trim();
|
||||
if (entered.isEmpty()) {
|
||||
if (!passwordField.getStyleClass().contains("error"))
|
||||
passwordField.getStyleClass().add("error");
|
||||
if (!visiblePasswordField.getStyleClass().contains("error"))
|
||||
visiblePasswordField.getStyleClass().add("error");
|
||||
if (!passwordLabel.getStyleClass().contains("error"))
|
||||
passwordLabel.getStyleClass().add("error");
|
||||
return;
|
||||
}
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject()
|
||||
.put("action", "verify_password")
|
||||
.put("current_password", entered);
|
||||
|
||||
org.json.JSONObject resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||
boolean ok = (resp != null && "success".equalsIgnoreCase(resp.optString("status")));
|
||||
|
||||
if (ok) {
|
||||
passwordField.getStyleClass().remove("error");
|
||||
visiblePasswordField.getStyleClass().remove("error");
|
||||
passwordLabel.getStyleClass().remove("error");
|
||||
|
||||
openChangeCredentials(entered);
|
||||
} else {
|
||||
if (!passwordField.getStyleClass().contains("error"))
|
||||
passwordField.getStyleClass().add("error");
|
||||
if (!visiblePasswordField.getStyleClass().contains("error"))
|
||||
visiblePasswordField.getStyleClass().add("error");
|
||||
if (!passwordLabel.getStyleClass().contains("error"))
|
||||
passwordLabel.getStyleClass().add("error");
|
||||
}
|
||||
}
|
||||
|
||||
private void togglePasswordVisibility() {
|
||||
passwordVisible = !passwordVisible;
|
||||
|
||||
visiblePasswordField.setVisible(passwordVisible);
|
||||
visiblePasswordField.setManaged(passwordVisible);
|
||||
|
||||
passwordField.setVisible(!passwordVisible);
|
||||
passwordField.setManaged(!passwordVisible);
|
||||
|
||||
toggleVisibilityBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
private void updateBackIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/back_button_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/back_button_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
backButton.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void openChangeCredentials(String currentPassword) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/change_credentials.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ChangeCredentialsController c = loader.getController();
|
||||
c.setCurrentPassword(currentPassword);
|
||||
c.prefillFromSession();
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.Models.ContactEntry;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ContactsController {
|
||||
|
||||
@FXML private VBox contactsList;
|
||||
@FXML private VBox contactsCard;
|
||||
@FXML private TextField searchField;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private ScrollPane contactsScroll;
|
||||
@FXML private Button searchIcon;
|
||||
|
||||
private final List<ContactVM> allContacts = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
loadContactsAndRender();
|
||||
|
||||
searchField.textProperty().addListener((obs, ov, nv) -> {
|
||||
String f = nv == null ? "" : nv.trim().toLowerCase();
|
||||
List<ContactVM> filtered = allContacts.stream()
|
||||
.filter(c -> c.profileName.toLowerCase().contains(f)
|
||||
|| (c.userId != null && c.userId.toLowerCase().contains(f)))
|
||||
.sorted(Comparator.comparing(c -> c.profileName.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
renderContacts(filtered);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> searchField.requestFocus());
|
||||
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(contactsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(contactsCard.getParent()));
|
||||
|
||||
contactsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
contactsScroll.setPannable(true);
|
||||
contactsScroll.setFitToWidth(true);
|
||||
contactsScroll.setFitToHeight(false);
|
||||
contactsScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
contactsScroll.setVvalue(contactsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (contactsCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(contactsCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> updateSearchIcon(newVal));
|
||||
updateSearchIcon(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
|
||||
private void loadContactsAndRender() {
|
||||
CompletableFuture
|
||||
.supplyAsync(() -> {
|
||||
try {
|
||||
if (Session.contactEntries != null && !Session.contactEntries.isEmpty()) {
|
||||
return new ArrayList<>(Session.contactEntries);
|
||||
}
|
||||
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_contacts")
|
||||
.put("user_id", Session.getUserUUID());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res == null || !"success".equals(res.optString("status"))) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
JSONObject data = res.optJSONObject("data");
|
||||
JSONArray arr = data != null ? data.optJSONArray("contacts") : null;
|
||||
if (arr == null) return Collections.emptyList();
|
||||
|
||||
List<ContactEntry> fetched = new ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject c = arr.getJSONObject(i);
|
||||
UUID contactId = UUID.fromString(c.getString("contact_id"));
|
||||
String userId = c.optString("user_id", null);
|
||||
String contactDisplay = c.optString("contact_display_id", userId);
|
||||
String profileName = c.optString("profile_name", contactDisplay);
|
||||
String imageUrl = c.optString("image_url", "/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||
boolean isBlocked = c.optBoolean("is_blocked", false);
|
||||
String lastSeenStr = c.optString("last_seen", null);
|
||||
|
||||
LocalDateTime lastSeen = null;
|
||||
if (lastSeenStr != null && !lastSeenStr.isEmpty()) {
|
||||
try { lastSeen = LocalDateTime.parse(lastSeenStr); } catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
fetched.add(new ContactEntry(contactId, userId, contactDisplay, profileName, imageUrl, isBlocked, lastSeen));
|
||||
}
|
||||
if (Session.contactEntries == null) Session.contactEntries = new ArrayList<>();
|
||||
Session.contactEntries.clear();
|
||||
Session.contactEntries.addAll(fetched);
|
||||
return fetched;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return Collections.<ContactEntry>emptyList();
|
||||
}
|
||||
})
|
||||
.thenAccept(entries -> Platform.runLater(() -> {
|
||||
allContacts.clear();
|
||||
for (Object ce : entries) {
|
||||
allContacts.add(ContactVM.from((ContactEntry) ce));
|
||||
}
|
||||
allContacts.sort(Comparator.comparing(vm -> vm.profileName.toLowerCase()));
|
||||
renderContacts(allContacts);
|
||||
}));
|
||||
}
|
||||
|
||||
private void renderContacts(List<ContactVM> contacts) {
|
||||
contactsList.getChildren().clear();
|
||||
|
||||
if (contacts.isEmpty()) {
|
||||
StackPane emptyPane = new StackPane();
|
||||
emptyPane.setPrefHeight(300);
|
||||
emptyPane.setAlignment(Pos.CENTER);
|
||||
Label emptyLabel = new Label("No contacts found");
|
||||
emptyLabel.getStyleClass().add("no-contacts-label");
|
||||
emptyPane.getChildren().add(emptyLabel);
|
||||
contactsList.getChildren().add(emptyPane);
|
||||
return;
|
||||
}
|
||||
|
||||
for (ContactVM c : contacts) {
|
||||
HBox item = new HBox(10);
|
||||
item.getStyleClass().add("contact-item");
|
||||
item.setCursor(Cursor.HAND);
|
||||
|
||||
ImageView avatar = new ImageView(loadAvatarSafe(c.imageUrl));
|
||||
avatar.setFitWidth(58);
|
||||
avatar.setFitHeight(58);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
VBox details = new VBox(2);
|
||||
Label nameLabel = new Label(c.profileName);
|
||||
nameLabel.getStyleClass().add("contact-name");
|
||||
|
||||
|
||||
details.getChildren().addAll(nameLabel);
|
||||
item.getChildren().addAll(avatar, details);
|
||||
|
||||
item.setOnMouseClicked(e -> openOrStartPrivateChat(c));
|
||||
|
||||
contactsList.getChildren().add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private Image loadAvatarSafe(String urlOrResource) {
|
||||
try {
|
||||
if (urlOrResource != null) {
|
||||
if (urlOrResource.startsWith("/")) {
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream(urlOrResource)));
|
||||
}
|
||||
if (urlOrResource.startsWith("http://") ||
|
||||
urlOrResource.startsWith("https://") ||
|
||||
urlOrResource.startsWith("file:")) {
|
||||
return new Image(urlOrResource, true); // true = لود async
|
||||
}
|
||||
}
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
|
||||
} catch (Exception e) {
|
||||
return new Image(Objects.requireNonNull(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Avatars/default_user_profile.png")));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSearchIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/search_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/search_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
searchIcon.setGraphic(icon);
|
||||
}
|
||||
|
||||
|
||||
private void openOrStartPrivateChat(ContactVM contact) {
|
||||
ChatEntry existing = findExistingPrivateChatWith(contact.contactId);
|
||||
if (existing != null) {
|
||||
MainController.getInstance().openChat(existing);
|
||||
MainController.getInstance().closeOverlay(contactsCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture
|
||||
.supplyAsync(() -> {
|
||||
try {
|
||||
UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "get_or_create_private_chat")
|
||||
.put("user1", myId.toString())
|
||||
.put("user2", contact.contactId.toString());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
if (res == null || !"success".equals(res.optString("status"))) {
|
||||
throw new RuntimeException(res != null ? res.optString("message", "Unknown error")
|
||||
: "null response");
|
||||
}
|
||||
JSONObject data = res.getJSONObject("data");
|
||||
UUID chatId = UUID.fromString(data.getString("chat_id"));
|
||||
|
||||
ChatEntry entry = new ChatEntry(
|
||||
chatId,
|
||||
contact.userId,
|
||||
contact.profileName,
|
||||
contact.imageUrl,
|
||||
"private",
|
||||
null,
|
||||
false,
|
||||
false
|
||||
);
|
||||
entry.setOtherUserId(contact.contactId);
|
||||
|
||||
return entry;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
})
|
||||
.thenAccept(entry -> Platform.runLater(() -> {
|
||||
MainController.getInstance().onJoinedOrAdded(entry);
|
||||
MainController.getInstance().openChat(entry);
|
||||
MainController.getInstance().closeOverlay(contactsCard.getParent());
|
||||
}))
|
||||
.exceptionally(err -> {
|
||||
Platform.runLater(() -> {
|
||||
Alert a = new Alert(Alert.AlertType.ERROR, "Failed to start chat: " + err.getMessage(), ButtonType.OK);
|
||||
a.showAndWait();
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private ChatEntry findExistingPrivateChatWith(UUID otherUserUuid) {
|
||||
if (Session.chatList != null) {
|
||||
for (ChatEntry ce : Session.chatList) {
|
||||
if (!"private".equalsIgnoreCase(ce.getType())) continue;
|
||||
UUID stored = ce.getOtherUserId();
|
||||
if (stored != null && stored.equals(otherUserUuid)) return ce;
|
||||
}
|
||||
}
|
||||
if (Session.activeChats != null) {
|
||||
for (ChatEntry ce : Session.activeChats) {
|
||||
if (!"private".equalsIgnoreCase(ce.getType())) continue;
|
||||
UUID stored = ce.getOtherUserId();
|
||||
if (stored != null && stored.equals(otherUserUuid)) return ce;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class ContactVM {
|
||||
final UUID contactId; // internal UUID
|
||||
final String userId; // @id
|
||||
final String profileName;
|
||||
final String imageUrl;
|
||||
|
||||
static ContactVM from(ContactEntry ce) {
|
||||
return new ContactVM(ce.getContactId(), ce.getUserId(), ce.getProfileName(), ce.getImageUrl());
|
||||
}
|
||||
|
||||
ContactVM(UUID contactId, String userId, String profileName, String imageUrl) {
|
||||
this.contactId = contactId;
|
||||
this.userId = userId;
|
||||
this.profileName = profileName != null ? profileName : (userId != null ? userId : "Unknown");
|
||||
this.imageUrl = imageUrl != null ? imageUrl : "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class EditProfileController {
|
||||
|
||||
private File pickedAvatarFile;
|
||||
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private VBox editCard;
|
||||
|
||||
@FXML private Button backButton;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
@FXML private ImageView profileImageView;
|
||||
@FXML private ImageView cameraIcon;
|
||||
@FXML private Button cameraButton;
|
||||
@FXML private Label profileName;
|
||||
@FXML private Label profileStatus;
|
||||
|
||||
@FXML private TextField nameField;
|
||||
@FXML private TextField bioField;
|
||||
@FXML private TextField usernameField;
|
||||
|
||||
// EditProfileController.java
|
||||
private SettingsController parentSettings;
|
||||
public void setParentSettings(SettingsController sc) {
|
||||
this.parentSettings = sc;
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
backButton.setGraphic(new ImageView(
|
||||
new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/back_button_dark.png"))
|
||||
));
|
||||
|
||||
// close on background click
|
||||
// overlayBackground.setOnMouseClicked(e -> {
|
||||
// saveChangesAndClose();
|
||||
// closeEdit();
|
||||
// });
|
||||
//
|
||||
// closeButton.setOnAction(e -> {
|
||||
// saveChangesAndClose();
|
||||
// closeEdit();
|
||||
// });
|
||||
//
|
||||
// backButton.setOnAction(e -> {
|
||||
// saveChangesAndClose();
|
||||
// MainController.getInstance().goBack(overlayBackground);
|
||||
// });
|
||||
|
||||
overlayBackground.setOnMouseClicked(e -> {
|
||||
saveChangesAndClose();
|
||||
closeEdit(); // ← همینه
|
||||
});
|
||||
|
||||
closeButton.setOnAction(e -> {
|
||||
saveChangesAndClose();
|
||||
closeEdit(); // ← همینه
|
||||
});
|
||||
|
||||
backButton.setOnAction(e -> {
|
||||
saveChangesAndClose();
|
||||
MainController.getInstance().goBack(overlayBackground);
|
||||
});
|
||||
|
||||
// Load your camera icon image (white camera icon for visibility)
|
||||
cameraIcon.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Icons/camera.png"))
|
||||
));
|
||||
|
||||
// Handle click to open file chooser
|
||||
cameraButton.setOnMouseClicked(e -> openImageChooser());
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (editCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(editCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// Listener for theme change
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateBackIcon(newVal);
|
||||
});
|
||||
|
||||
// Set initial state
|
||||
updateBackIcon(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
private void updateBackIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/back_button_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/back_button_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
backButton.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void openImageChooser() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg")
|
||||
);
|
||||
File file = fileChooser.showOpenDialog(profileImageView.getScene().getWindow());
|
||||
|
||||
if (file != null) {
|
||||
pickedAvatarFile = file;
|
||||
profileImageView.setImage(new Image(file.toURI().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void closeEdit() {
|
||||
// MainController.getInstance().goBack(overlayBackground);
|
||||
MainController.getInstance().closeOverlay(editCard.getParent());
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void setProfileData(String name, String status, String bio, String userId, Image profileImage) {
|
||||
profileStatus.setText(status);
|
||||
profileName.setText(name);
|
||||
nameField.setText(name);
|
||||
bioField.setText(bio != null ? bio : "");
|
||||
usernameField.setText(userId);
|
||||
profileImageView.setImage(profileImage);
|
||||
}
|
||||
|
||||
private void saveChangesAndClose() {
|
||||
JSONObject currentUser = Session.currentUser;
|
||||
|
||||
String newBio = bioField.getText().trim();
|
||||
String newName = nameField.getText().trim();
|
||||
String newUserId = usernameField.getText().trim();
|
||||
|
||||
boolean anyError = false;
|
||||
|
||||
// if (pickedAvatarFile != null) {
|
||||
// ActionHandler.instance.uploadAvatarFor("user", null, pickedAvatarFile);
|
||||
//
|
||||
// if (ActionHandler.instance.wasSuccess()) {
|
||||
// String url = ActionHandler.instance.getLastMessage();
|
||||
// if (url != null && !url.isBlank()) {
|
||||
// currentUser.put("image_url", url);
|
||||
// }
|
||||
// } else {
|
||||
// anyError = true;
|
||||
// showAlert(
|
||||
// "Avatar upload failed",
|
||||
// ActionHandler.instance.getLastMessage() == null ? "Unknown error" : ActionHandler.instance.getLastMessage(),
|
||||
// Alert.AlertType.ERROR
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
if (pickedAvatarFile != null) {
|
||||
ActionHandler.instance.uploadAvatarFor("user", null, pickedAvatarFile);
|
||||
|
||||
if (ActionHandler.instance.wasSuccess()) {
|
||||
String url = ActionHandler.instance.getLastMessage();
|
||||
if (url != null && !url.isBlank()) {
|
||||
|
||||
|
||||
String extracted = extractImageUrl(url);
|
||||
if (isLikelyImageUrl(extracted)) {
|
||||
String finalUrl = addCacheBusterIfHttp(extracted);
|
||||
Session.currentUser.put("image_url", finalUrl);
|
||||
} else {
|
||||
System.out.println("Upload avatar returned a non-image value: " + url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyError = true;
|
||||
showAlert(
|
||||
"Avatar upload failed",
|
||||
ActionHandler.instance.getLastMessage() == null ? "Unknown error" : ActionHandler.instance.getLastMessage(),
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Update bio ---
|
||||
String oldBio = currentUser.optString("bio", "");
|
||||
if (!newBio.equals(oldBio)) {
|
||||
JSONObject req = new JSONObject().put("action", "edit_bio").put("new_bio", newBio);
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equals(resp.optString("status"))) {
|
||||
anyError = true;
|
||||
showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR);
|
||||
} else {
|
||||
currentUser.put("bio", newBio);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Update profile name ---
|
||||
String oldName = currentUser.optString("profile_name", "");
|
||||
if (!newName.equals(oldName)) {
|
||||
JSONObject req = new JSONObject().put("action", "edit_profile_name").put("new_profile_name", newName);
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equals(resp.optString("status"))) {
|
||||
anyError = true;
|
||||
showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR);
|
||||
} else {
|
||||
currentUser.put("profile_name", newName);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Update user ID (username) ---
|
||||
String oldUserId = currentUser.optString("user_id", "");
|
||||
if (!newUserId.equals(oldUserId)) {
|
||||
JSONObject req = new JSONObject().put("action", "edit_user_id").put("new_user_id", newUserId);
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equals(resp.optString("status"))) {
|
||||
anyError = true;
|
||||
showAlert("Error", resp != null ? resp.optString("message", "Unknown error") : "Unknown error", Alert.AlertType.ERROR);
|
||||
} else {
|
||||
currentUser.put("user_id", newUserId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyError) {
|
||||
var mp = MyProfileController.getInstance();
|
||||
if (mp != null) {
|
||||
mp.setProfileData(
|
||||
currentUser.optString("profile_name"),
|
||||
"online",
|
||||
currentUser.optString("bio"),
|
||||
currentUser.optString("user_id"),
|
||||
currentUser.optString("image_url", "/org/to/telegramfinalproject/Avatars/default_user_profile.png")
|
||||
);
|
||||
}
|
||||
|
||||
if (parentSettings != null) {
|
||||
parentSettings.populateFromSession();
|
||||
}
|
||||
|
||||
MainController.getInstance().refreshSidebarUserFromSession();
|
||||
|
||||
// MainController.getInstance().goBack(overlayBackground);
|
||||
MainController.getInstance().closeOverlay(editCard.getParent());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void showAlert(String title, String message, Alert.AlertType type) {
|
||||
Alert alert = new Alert(type);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null); // no big header, just the message
|
||||
alert.setContentText(message);
|
||||
|
||||
// optional: style it to fit your dark/light theme
|
||||
if (alert.getDialogPane().getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(alert.getDialogPane().getScene());
|
||||
}
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean isLikelyImageUrl(String s) {
|
||||
if (s == null || s.isBlank()) return false;
|
||||
return s.startsWith("http://") || s.startsWith("https://")
|
||||
|| s.startsWith("file:")
|
||||
|| s.startsWith("/")
|
||||
|| s.matches("(?i).+\\.(png|jpe?g|gif|webp)$");
|
||||
}
|
||||
|
||||
private String addCacheBusterIfHttp(String url) {
|
||||
if (url == null) return null;
|
||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||
return url + (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private String extractImageUrl(String raw) {
|
||||
if (raw == null) return null;
|
||||
String s = raw.trim();
|
||||
if (s.startsWith("{") && s.endsWith("}")) {
|
||||
try {
|
||||
org.json.JSONObject j = new org.json.JSONObject(s);
|
||||
if (j.has("display_url")) return j.optString("display_url", null);
|
||||
if (j.has("url")) return j.optString("url", null);
|
||||
if (j.has("data")) {
|
||||
var d = j.optJSONObject("data");
|
||||
if (d != null) {
|
||||
if (d.has("display_url")) return d.optString("display_url", null);
|
||||
if (d.has("url")) return d.optString("url", null);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GroupInfoController {
|
||||
|
||||
@FXML private VBox groupCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
@FXML private ImageView groupImage;
|
||||
@FXML private Label groupName;
|
||||
@FXML private Label memberCount;
|
||||
|
||||
@FXML private Button infoMoreButton;
|
||||
@FXML private ContextMenu infoMoreMenu;
|
||||
@FXML private MenuItem addMemberItem;
|
||||
@FXML private MenuItem manageGroupItem;
|
||||
@FXML private MenuItem deleteGroupItem;
|
||||
@FXML private ImageView moreIcon;
|
||||
@FXML private ImageView manageChannelIcon;
|
||||
@FXML private ImageView addMemberIcon;
|
||||
|
||||
@FXML private VBox membersList;
|
||||
@FXML private Label membersHeader;
|
||||
@FXML private Button addMemberButton;
|
||||
@FXML private ImageView membersIcon;
|
||||
@FXML private ScrollPane membersScroll;
|
||||
|
||||
private String groupId; // the UUID of this group
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(groupCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(groupCard.getParent()));
|
||||
|
||||
infoMoreButton.setOnAction(e -> {
|
||||
if (infoMoreMenu != null) infoMoreMenu.show(infoMoreButton, javafx.geometry.Side.BOTTOM, 0, 0);
|
||||
});
|
||||
|
||||
if (manageGroupItem != null) {
|
||||
manageGroupItem.setOnAction(e -> openManageGroupScene());
|
||||
}
|
||||
|
||||
deleteGroupItem.setOnAction(e -> handleDeleteGroup());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (groupCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(groupCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateIcons(newVal);
|
||||
});
|
||||
|
||||
updateIcons(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// Smooth scroll feel
|
||||
membersScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
membersScroll.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||
if (newSkin != null) {
|
||||
ScrollBar vBar = (ScrollBar) membersScroll.lookup(".scroll-bar:vertical");
|
||||
if (vBar != null) {
|
||||
membersScroll.setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
double newValue = vBar.getValue() - deltaY;
|
||||
vBar.setValue(Math.max(0, Math.min(newValue, 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (addMemberButton != null) {
|
||||
addMemberButton.setOnAction(e -> openAddMemberScene());
|
||||
}
|
||||
if (addMemberItem != null) {
|
||||
addMemberItem.setOnAction(e -> openAddMemberScene());
|
||||
}
|
||||
}
|
||||
|
||||
public void setGroupDataFromJson(ChatEntry entry, JSONObject data) {
|
||||
this.groupId = data.optString("internal_uuid", entry.getId().toString());
|
||||
|
||||
groupName.setText(data.optString("group_name", entry.getName()));
|
||||
memberCount.setText(data.optInt("member_count", 0) + " members");
|
||||
|
||||
// --- Role-based UI ---
|
||||
String myRole = data.optString("my_role", "member").toLowerCase();
|
||||
|
||||
// Delete group → only owner
|
||||
deleteGroupItem.setVisible("owner".equals(myRole));
|
||||
|
||||
// Add member (button + menu item) → owner or admin
|
||||
boolean canAdd = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
addMemberButton.setVisible(canAdd);
|
||||
addMemberButton.setManaged(canAdd);
|
||||
if (addMemberItem != null) {
|
||||
addMemberItem.setVisible(canAdd);
|
||||
}
|
||||
|
||||
// More menu (3-dot) → hide entirely for plain members
|
||||
boolean showMore = "owner".equals(myRole) || "admin".equals(myRole);
|
||||
infoMoreButton.setVisible(showMore);
|
||||
infoMoreButton.setManaged(showMore);
|
||||
|
||||
// --- Group picture ---
|
||||
String imgUrl = data.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
try {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) groupImage.setImage(img);
|
||||
} catch (Exception ignore) {}
|
||||
} else {
|
||||
groupImage.setImage(
|
||||
new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_group_profile.png"))
|
||||
);
|
||||
}
|
||||
|
||||
// --- Members list ---
|
||||
membersList.getChildren().clear();
|
||||
var arr = data.optJSONArray("members");
|
||||
if (arr != null) {
|
||||
membersHeader.setText(arr.length() + " MEMBERS");
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
JSONObject m = arr.getJSONObject(i);
|
||||
addMemberRow(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberRow(JSONObject m) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// === Avatar ===
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
AvatarFX.circleClip(avatar, 36);
|
||||
|
||||
String imgUrl = m.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// === Name + Status ===
|
||||
VBox nameBox = new VBox(2);
|
||||
Label name = new Label(m.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
// Status = online / last seen recently
|
||||
String status;
|
||||
if (m.optBoolean("is_online", false)) {
|
||||
status = "online";
|
||||
} else {
|
||||
status = ChatPageController.getInstance().userStatusText(
|
||||
false,
|
||||
m.optString("last_seen", null)
|
||||
);
|
||||
}
|
||||
Label statusLbl = new Label(status);
|
||||
statusLbl.getStyleClass().add("member-status");
|
||||
|
||||
nameBox.getChildren().addAll(name, statusLbl);
|
||||
|
||||
// === Role (owner/admin/member) ===
|
||||
Label role = new Label();
|
||||
String roleStr = m.optString("role", "");
|
||||
if (!roleStr.isBlank()) {
|
||||
role.setText(roleStr.toLowerCase());
|
||||
role.getStyleClass().add("member-role");
|
||||
}
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, nameBox, spacer, role);
|
||||
membersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void openManageGroupScene() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_group")
|
||||
.put("group_id", groupId)
|
||||
.put("viewer_id", Session.getUserUUID());
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
resp != null ? resp.optString("message") : "Server not responding.",
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
if (data == null) {
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Malformed server response.",
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_group.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageGroupController controller = loader.getController();
|
||||
controller.setGroupData(data); // pass the full JSON
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Manage Group scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Platform.runLater(() -> MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Error while fetching group info: " + e.getMessage(),
|
||||
Alert.AlertType.ERROR
|
||||
));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openAddMemberScene() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddMembersController controller = loader.getController();
|
||||
controller.setGroupForAdd(UUID.fromString(groupId), String.valueOf(groupName));
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Add Member scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeleteGroup() {
|
||||
ChatEntry entry = Session.currentChatEntry;
|
||||
|
||||
// if (entry == null || !"group".equalsIgnoreCase(entry.getType())) {
|
||||
// alert(Alert.AlertType.INFORMATION, "This action is only available for groups.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (!confirm("Delete Group",
|
||||
"Are you sure you want to DELETE this group?\nThis action cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "delete_group")
|
||||
.put("group_id", entry.getId().toString());
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
alert(Alert.AlertType.INFORMATION, "✅ Group deleted successfully.");
|
||||
MainController.getInstance().refreshChatListUI();
|
||||
AppRouter.showMain();
|
||||
} else {
|
||||
String msg = (res != null) ? res.optString("message", "Failed to delete group.") : "null response";
|
||||
alert(Alert.AlertType.ERROR, "❌ " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean confirm(String title, String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.CONFIRMATION, msg, ButtonType.OK, ButtonType.CANCEL);
|
||||
a.setTitle(title);
|
||||
return a.showAndWait().filter(btn -> btn == ButtonType.OK).isPresent();
|
||||
}
|
||||
private void alert(Alert.AlertType type, String msg) {
|
||||
new Alert(type, msg, ButtonType.OK).show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void updateIcons(boolean dark) {
|
||||
String suffix = dark ? "_light.png" : "_dark.png";
|
||||
|
||||
moreIcon.setImage(loadImage(ICON_PATH + "more" + suffix));
|
||||
membersIcon.setImage(loadImage(ICON_PATH + "group" + suffix));
|
||||
addMemberButton.setGraphic(makeIcon(ICON_PATH + "add_member" + suffix));
|
||||
membersIcon.setImage(loadImage(ICON_PATH + "group_member" + suffix));
|
||||
manageChannelIcon.setImage(loadImage(ICON_PATH + "manage" + suffix));
|
||||
addMemberIcon.setImage(loadImage(ICON_PATH + "add_member" + suffix));
|
||||
}
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) return null;
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IntroController {
|
||||
@FXML private StackPane imagePane;
|
||||
@FXML private Label caption;
|
||||
@FXML private HBox dotContainer;
|
||||
@FXML private Button startButton;
|
||||
|
||||
private List<Image> images = new ArrayList<>();
|
||||
private List<String> captions = new ArrayList<>();
|
||||
private int currentIndex = 0;
|
||||
|
||||
private final int width = 200;
|
||||
private final int height = 200;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
loadSlides();
|
||||
setupDots();
|
||||
showSlide(currentIndex);
|
||||
|
||||
startAutoSlide();
|
||||
}
|
||||
|
||||
private void loadSlides() {
|
||||
images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/telegram_icon.png").toExternalForm()));
|
||||
captions.add("🚀 The world's fastest messaging app.\nIt is free and secure.");
|
||||
|
||||
images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/fast.png").toExternalForm()));
|
||||
captions.add("🚀 Telegram delivers messages \nfastest than any other application.");
|
||||
|
||||
images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/free.png").toExternalForm()));
|
||||
captions.add("🎁 Telegram is free forever. No ads.\nNo subscription fees.");
|
||||
|
||||
images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/secure.png").toExternalForm()));
|
||||
captions.add("🔒 Telegram keeps your messages \nsafe from hacker attacks.");
|
||||
}
|
||||
|
||||
private void setupDots() {
|
||||
dotContainer.getChildren().clear();
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
Circle dot = new Circle(5, Color.LIGHTGRAY);
|
||||
dotContainer.getChildren().add(dot);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDots() {
|
||||
for (int i = 0; i < dotContainer.getChildren().size(); i++) {
|
||||
Circle dot = (Circle) dotContainer.getChildren().get(i);
|
||||
if (i == currentIndex) {
|
||||
dot.setFill(getSlideColor(i));
|
||||
} else {
|
||||
dot.setFill(Color.LIGHTGRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Color getSlideColor(int index) {
|
||||
return switch (index) {
|
||||
case 0 -> Color.DODGERBLUE; // Blue for Telegram logo
|
||||
case 1 -> Color.FIREBRICK; // Red slide
|
||||
case 2 -> Color.GOLDENROD; // Yellow slide
|
||||
case 3 -> Color.SEAGREEN; // Green slide
|
||||
default -> Color.GRAY;
|
||||
};
|
||||
}
|
||||
|
||||
private void showSlide(int index) {
|
||||
imagePane.getChildren().clear();
|
||||
|
||||
ImageView imageView = new ImageView(images.get(index));
|
||||
imageView.setFitWidth(180);
|
||||
imageView.setFitHeight(180);
|
||||
imageView.setPreserveRatio(true);
|
||||
|
||||
// Clip the image into a circle
|
||||
Circle clip = new Circle(90, 90, 80); // centerX, centerY, radius
|
||||
imageView.setClip(clip);
|
||||
|
||||
imagePane.getChildren().add(imageView);
|
||||
caption.setText(captions.get(index));
|
||||
updateDots();
|
||||
}
|
||||
|
||||
|
||||
private void startAutoSlide() {
|
||||
Thread slider = new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException ignored) {}
|
||||
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
currentIndex = (currentIndex + 1) % images.size();
|
||||
showSlide(currentIndex);
|
||||
});
|
||||
}
|
||||
});
|
||||
slider.setDaemon(true);
|
||||
slider.start();
|
||||
}
|
||||
//
|
||||
// @FXML
|
||||
// private void handleStartMessaging(ActionEvent event) {
|
||||
// try {
|
||||
// FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/login_view.fxml"));
|
||||
// Scene loginScene = new Scene(loader.load());
|
||||
//
|
||||
// // Get the current stage and its dimensions
|
||||
// Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
|
||||
// double currentWidth = stage.getWidth();
|
||||
// double currentHeight = stage.getHeight();
|
||||
//
|
||||
// // Set the new scene and apply the previous size
|
||||
// stage.setScene(loginScene);
|
||||
// stage.setWidth(currentWidth);
|
||||
// stage.setHeight(currentHeight);
|
||||
// stage.show();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
@FXML
|
||||
private void handleStartMessaging(ActionEvent event) {
|
||||
AppRouter.showLogin();
|
||||
}
|
||||
|
||||
@FXML private void goRegister() { AppRouter.showRegister(); }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Client.TelegramClient;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.to.telegramfinalproject.UI.AppRouter.showRegister;
|
||||
|
||||
public class LoginController {
|
||||
|
||||
@FXML private TextField usernameField;
|
||||
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private TextField visiblePasswordField;
|
||||
|
||||
@FXML private Button toggleVisibilityBtn;
|
||||
@FXML private Label errorLabel;
|
||||
|
||||
private ClientConnection connection;
|
||||
private boolean passwordVisible = false;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Sync hidden and visible password fields
|
||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||
|
||||
try {
|
||||
connection = new ClientConnection("localhost", 8080);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Could not connect to server: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void togglePasswordVisibility() {
|
||||
passwordVisible = !passwordVisible;
|
||||
visiblePasswordField.setVisible(passwordVisible);
|
||||
visiblePasswordField.setManaged(passwordVisible);
|
||||
passwordField.setVisible(!passwordVisible);
|
||||
passwordField.setManaged(!passwordVisible);
|
||||
toggleVisibilityBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
// @FXML
|
||||
// private void handleLogin() {
|
||||
// String username = usernameField.getText();
|
||||
// String password = passwordField.getText();
|
||||
//
|
||||
// // 1. Check for empty fields
|
||||
// if (username.isEmpty() || password.isEmpty()) {
|
||||
// showError("Please fill in all required fields.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. Check if username exists
|
||||
// userDatabase userDb = new userDatabase();
|
||||
// if (!userDb.existsByUsername(username)) {
|
||||
// showError("This username doesn’t exist.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 3. Check if password is correct
|
||||
// User user = userDb.findByUsername(username);
|
||||
// if (!PasswordHashing.verify(password, user.getPassword())) {
|
||||
// showError("Incorrect password.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 4. Attempt to send to server
|
||||
// try {
|
||||
// JSONObject request = new JSONObject();
|
||||
// request.put("action", "login");
|
||||
// request.put("user_id", JSONObject.NULL);
|
||||
// request.put("username", username);
|
||||
// request.put("password", password);
|
||||
// request.put("profile_name", JSONObject.NULL);
|
||||
//
|
||||
// if (connection != null) {
|
||||
// connection.send(request.toString());
|
||||
// String responseStr = connection.receive();
|
||||
// JSONObject response = new JSONObject(responseStr);
|
||||
// System.out.println("Status: " + response.getString("status"));
|
||||
// System.out.println("Message: " + response.getString("message"));
|
||||
//
|
||||
// // Simulate successful login (since main.fxml isn’t ready)
|
||||
// Alert alert = new Alert(Alert.AlertType.INFORMATION, "Login successful!");
|
||||
// alert.show();
|
||||
// }
|
||||
//
|
||||
// } catch (Exception ex) {
|
||||
// showError("Unable to connect to server. Please try again later.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleLogin() {
|
||||
String u = usernameField.getText().trim();
|
||||
String p = passwordField.getText();
|
||||
// 1. Check for empty fields
|
||||
if (u.isEmpty() || p.isEmpty()) {
|
||||
showError("Please fill in all required fields.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Check if username exists
|
||||
userDatabase userDb = new userDatabase();
|
||||
if (!userDb.existsByUsername(u)) {
|
||||
showError("This username doesn’t exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Check if password is correct
|
||||
User user = userDb.findByUsername(u);
|
||||
if (!PasswordHashing.verify(p, user.getPassword())) {
|
||||
showError("Incorrect password.");
|
||||
return;
|
||||
}
|
||||
|
||||
setUiBusy(true);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
var handler = cli.getHandler();
|
||||
|
||||
|
||||
handler.login(u,p);
|
||||
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setUiBusy(false);
|
||||
|
||||
if (!handler.wasSuccess() || Session.currentUser == null) {
|
||||
showError(handler.getLastMessage().isEmpty() ? "Login failed." : handler.getLastMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
goMain();
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setUiBusy(false);
|
||||
showError("Connection error.");
|
||||
});
|
||||
}
|
||||
}, "login-thread").start();
|
||||
}
|
||||
|
||||
private void goMain() {
|
||||
AppRouter.showMain();
|
||||
}
|
||||
|
||||
|
||||
private void setUiBusy(boolean b) {
|
||||
usernameField.setDisable(b);
|
||||
passwordField.setDisable(b);
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void switchToRegister() throws IOException {
|
||||
showRegister();
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
||||
Parent root = loader.load();
|
||||
Stage stage = (Stage) usernameField.getScene().getWindow();
|
||||
stage.setScene(new Scene(root, 500, 700));
|
||||
stage.show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
showError("Could not load scene: " + fxmlFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(String message) {
|
||||
errorLabel.setVisible(false);
|
||||
errorLabel.setText(""); // Clear the label text
|
||||
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||
pause.setOnFinished(event -> {
|
||||
errorLabel.setText(message);
|
||||
errorLabel.setStyle("-fx-text-fill: red;");
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
pause.play();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageAdminsController {
|
||||
|
||||
@FXML private VBox adminsCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Button addAdminButton;
|
||||
|
||||
@FXML private ScrollPane adminsScroll;
|
||||
@FXML private VBox adminsList;
|
||||
|
||||
private String groupId;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
|
||||
addAdminButton.setOnAction(e -> openAddAdminOverlay());
|
||||
|
||||
// Smooth scroll feel
|
||||
adminsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
adminsScroll.setPannable(true);
|
||||
adminsScroll.setFitToWidth(true);
|
||||
adminsScroll.setFitToHeight(false);
|
||||
adminsScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
adminsScroll.setVvalue(adminsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public void setGroupData(String groupId, JSONArray admins) {
|
||||
this.groupId = groupId;
|
||||
|
||||
adminsList.getChildren().clear();
|
||||
|
||||
// Convert to list for sorting
|
||||
List<JSONObject> adminList = new ArrayList<>();
|
||||
for (int i = 0; i < admins.length(); i++) {
|
||||
adminList.add(admins.getJSONObject(i));
|
||||
}
|
||||
|
||||
// Sort: owner first, then admins
|
||||
adminList.sort((a, b) -> {
|
||||
String roleA = a.optString("role", "member");
|
||||
String roleB = b.optString("role", "member");
|
||||
if ("owner".equalsIgnoreCase(roleA) && !"owner".equalsIgnoreCase(roleB)) return -1;
|
||||
if ("owner".equalsIgnoreCase(roleB) && !"owner".equalsIgnoreCase(roleA)) return 1;
|
||||
return 0; // keep relative order for admins
|
||||
});
|
||||
|
||||
// Add rows
|
||||
for (JSONObject a : adminList) {
|
||||
addAdminRow(a);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAdminRow(JSONObject a) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = a.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(a.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(
|
||||
a.optBoolean("is_online", false) ? "online"
|
||||
: "last seen recently"
|
||||
);
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Remove admin (cannot remove owner)
|
||||
String role = a.optString("role", "admin");
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
Button removeBtn = new Button("Remove");
|
||||
removeBtn.getStyleClass().add("link-btn");
|
||||
removeBtn.setOnAction(e -> removeAdmin(a.optString("user_id"), row));
|
||||
row.getChildren().add(removeBtn);
|
||||
}
|
||||
|
||||
adminsList.getChildren().add(row);
|
||||
|
||||
// User clicks on a admin --> open set admin permission's scene
|
||||
row.setOnMouseClicked(e -> {
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
try {
|
||||
// request current permissions from server
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "get_group_admin_permissions")
|
||||
.put("group_id", groupId)
|
||||
.put("admin_id", a.optString("user_id"));
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
JSONObject oldPerms = (resp != null) ? resp.optJSONObject("permissions") : null;
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/set_group_admin_permissions.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
SetGroupAdminPermissionsController controller = loader.getController();
|
||||
controller.setTarget(
|
||||
UUID.fromString(groupId),
|
||||
UUID.fromString(a.optString("user_id")),
|
||||
true,
|
||||
oldPerms
|
||||
);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
MainController.getInstance().showAlert("Error", "Could not open permissions scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeAdmin(String userId, HBox row) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "remove_admin_from_group")
|
||||
.put("group_id", groupId)
|
||||
.put("target_user_id", userId);
|
||||
|
||||
System.out.println("Sending remove_admin_from_group request: " + req.toString(2));
|
||||
|
||||
row.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> adminsList.getChildren().remove(row));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
row.setDisable(false);
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message", "Failed to remove admin.") : "No response from server.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openAddAdminOverlay() {
|
||||
try {
|
||||
// === Query the server for full members list ===
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "view_group_members")
|
||||
.put("group_id", groupId);
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not fetch members.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
JSONArray members = (data != null) ? data.optJSONArray("members") : new JSONArray();
|
||||
|
||||
// === Load Add Admins FXML ===
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_admins.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddAdminsController controller = loader.getController();
|
||||
controller.setGroupData(groupId, members); // send raw members, filtering is done in AddAdminsController
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Add Admins scene.", Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageChannelAdminsController {
|
||||
|
||||
@FXML private VBox adminsCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Button addAdminButton;
|
||||
|
||||
@FXML private ScrollPane adminsScroll;
|
||||
@FXML private VBox adminsList;
|
||||
|
||||
private String channelId; // internal_uuid of channel
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(adminsCard.getParent()));
|
||||
|
||||
//addAdminButton.setOnAction(e -> openAddAdminOverlay());
|
||||
|
||||
// Smooth scroll feel
|
||||
adminsScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
adminsScroll.setPannable(true);
|
||||
adminsScroll.setFitToWidth(true);
|
||||
adminsScroll.setFitToHeight(false);
|
||||
adminsScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
adminsScroll.setVvalue(adminsScroll.getVvalue() - deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public void setChannelData(String channelId, JSONArray admins) {
|
||||
this.channelId = channelId;
|
||||
|
||||
adminsList.getChildren().clear();
|
||||
|
||||
// Convert to list for sorting
|
||||
List<JSONObject> adminList = new ArrayList<>();
|
||||
for (int i = 0; i < admins.length(); i++) {
|
||||
adminList.add(admins.getJSONObject(i));
|
||||
}
|
||||
|
||||
// Sort: owner first, then admins
|
||||
adminList.sort((a, b) -> {
|
||||
String roleA = a.optString("role", "subscriber");
|
||||
String roleB = b.optString("role", "subscriber");
|
||||
if ("owner".equalsIgnoreCase(roleA) && !"owner".equalsIgnoreCase(roleB)) return -1;
|
||||
if ("owner".equalsIgnoreCase(roleB) && !"owner".equalsIgnoreCase(roleA)) return 1;
|
||||
return 0; // keep relative order for admins
|
||||
});
|
||||
|
||||
// Add rows
|
||||
for (JSONObject a : adminList) {
|
||||
addAdminRow(a);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAdminRow(JSONObject a) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = a.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(a.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(
|
||||
a.optBoolean("is_online", false) ? "online"
|
||||
: "last seen recently"
|
||||
);
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Remove admin (cannot remove owner)
|
||||
String role = a.optString("role", "admin");
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
Button removeBtn = new Button("Remove");
|
||||
removeBtn.getStyleClass().add("link-btn");
|
||||
removeBtn.setOnAction(e -> removeAdmin(a.optString("user_id"), row));
|
||||
row.getChildren().add(removeBtn);
|
||||
}
|
||||
|
||||
adminsList.getChildren().add(row);
|
||||
|
||||
// User clicks an admin → open set channel admin permissions scene
|
||||
row.setOnMouseClicked(e -> {
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
try {
|
||||
// request current permissions from server
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "get_channel_admin_permissions")
|
||||
.put("channel_id", channelId)
|
||||
.put("admin_id", a.optString("user_id"));
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
JSONObject oldPerms = (resp != null) ? resp.optJSONObject("permissions") : null;
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/set_channel_admin_permissions.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
// SetChannelAdminPermissionsController controller = loader.getController();
|
||||
// controller.setTarget(
|
||||
// UUID.fromString(channelId),
|
||||
// UUID.fromString(a.optString("user_id")),
|
||||
// true,
|
||||
// oldPerms
|
||||
// );
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
MainController.getInstance().showAlert("Error", "Could not open permissions scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void removeAdmin(String userId, HBox row) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "remove_admin_from_channel")
|
||||
.put("channel_id", channelId)
|
||||
.put("target_user_id", userId);
|
||||
|
||||
System.out.println("Sending remove_admin_from_channel request: " + req.toString(2));
|
||||
|
||||
row.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> adminsList.getChildren().remove(row));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
row.setDisable(false);
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message", "Failed to remove admin.") : "No response from server.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// private void openAddAdminOverlay() {
|
||||
// try {
|
||||
// // === Query the server for full subscribers list ===
|
||||
// JSONObject req = new JSONObject()
|
||||
// .put("action", "view_channel_subscribers")
|
||||
// .put("channel_id", channelId);
|
||||
//
|
||||
// JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
//
|
||||
// if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
// MainController.getInstance().showAlert(
|
||||
// "Error",
|
||||
// "Could not fetch subscribers.",
|
||||
// Alert.AlertType.ERROR
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// JSONObject data = resp.optJSONObject("data");
|
||||
// JSONArray subscribers = (data != null) ? data.optJSONArray("subscribers") : new JSONArray();
|
||||
//
|
||||
// // === Load Add Admins FXML (same FXML reused) ===
|
||||
// FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
// "/org/to/telegramfinalproject/Fxml/add_admins.fxml"));
|
||||
// Node overlay = loader.load();
|
||||
//
|
||||
// AddChannelAdminsController controller = loader.getController();
|
||||
// controller.setChannelData(channelId, subscribers); // send raw subscribers, filtering is done in AddChannelAdminsController
|
||||
//
|
||||
// MainController.getInstance().showOverlay(overlay);
|
||||
//
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// MainController.getInstance().showAlert(
|
||||
// "Error", "Could not load Add Admins scene.", Alert.AlertType.ERROR
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageChannelController {
|
||||
|
||||
@FXML private VBox manageChannelCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton, changePicButton, cancelButton, saveButton;
|
||||
@FXML private ImageView channelImage;
|
||||
@FXML private TextField channelNameField;
|
||||
@FXML private TextField channelIdField;
|
||||
@FXML private TextArea channelDescriptionField;
|
||||
@FXML private Label adminCount, subscriberCount;
|
||||
@FXML private Button manageAdminsButton;
|
||||
@FXML private Button manageSubscribersButton;
|
||||
|
||||
private String channelId; // Internal UUID of the channel
|
||||
private String originalName;
|
||||
private String originalDisplayId;
|
||||
private String originalImageUrl;
|
||||
private String originalDescription;
|
||||
private File selectedImageFile;
|
||||
|
||||
private JSONObject data;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(manageChannelCard.getParent()));
|
||||
|
||||
changePicButton.setOnAction(e -> choosePicture());
|
||||
saveButton.setOnAction(e -> saveChanges());
|
||||
|
||||
manageAdminsButton.setOnAction(e -> openManageAdminsScene(data));
|
||||
manageSubscribersButton.setOnAction(e -> openManageSubscribersScene(data));
|
||||
}
|
||||
|
||||
public void setChannelData(JSONObject data) {
|
||||
this.data = data;
|
||||
this.channelId = data.optString("internal_uuid");
|
||||
|
||||
originalName = data.optString("channel_name", "");
|
||||
originalDisplayId = data.optString("channel_id", "");
|
||||
originalImageUrl = data.optString("image_url", "");
|
||||
originalDescription = data.optString("description", "");
|
||||
|
||||
channelNameField.setText(originalName);
|
||||
channelIdField.setText(originalDisplayId);
|
||||
channelDescriptionField.setText(originalDescription);
|
||||
|
||||
if (!originalImageUrl.isBlank()) {
|
||||
channelImage.setImage(new Image(originalImageUrl, true));
|
||||
}
|
||||
adminCount.setText(String.valueOf(countRole(data, "admin") + 1)); // owner + admins
|
||||
subscriberCount.setText(String.valueOf(countRole(data, "subscriber") + Integer.parseInt(adminCount.getText())));
|
||||
}
|
||||
|
||||
private int countRole(JSONObject channelData, String role) {
|
||||
var arr = channelData.optJSONArray("subscribers");
|
||||
if (arr == null) return 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
if (role.equalsIgnoreCase(arr.getJSONObject(i).optString("role"))) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void choosePicture() {
|
||||
FileChooser fc = new FileChooser();
|
||||
fc.setTitle("Select channel picture");
|
||||
selectedImageFile = fc.showOpenDialog(manageChannelCard.getScene().getWindow());
|
||||
if (selectedImageFile != null) {
|
||||
channelImage.setImage(new Image(selectedImageFile.toURI().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
String newName = channelNameField.getText().trim();
|
||||
String newChannelId = channelIdField.getText().trim();
|
||||
String newDescription = channelDescriptionField.getText().trim();
|
||||
String newImageUrl = (selectedImageFile != null)
|
||||
? selectedImageFile.toURI().toString()
|
||||
: originalImageUrl;
|
||||
|
||||
boolean changed =
|
||||
!Objects.equals(originalName, newName) ||
|
||||
!Objects.equals(originalDisplayId, newChannelId) ||
|
||||
!Objects.equals(originalDescription, newDescription) ||
|
||||
!Objects.equals(originalImageUrl, newImageUrl);
|
||||
|
||||
if (!changed) {
|
||||
MainController.getInstance().closeOverlay(manageChannelCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "edit_channel_info")
|
||||
.put("channel_id", channelId) // internal UUID
|
||||
.put("new_channel_id", newChannelId)
|
||||
.put("name", newName)
|
||||
.put("description", newDescription);
|
||||
|
||||
if (newImageUrl != null && !newImageUrl.isBlank()) {
|
||||
req.put("image_url", newImageUrl);
|
||||
} else {
|
||||
req.put("image_url", JSONObject.NULL);
|
||||
}
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
MainController.getInstance().closeOverlay(manageChannelCard.getParent());
|
||||
} else {
|
||||
String msg = (resp != null)
|
||||
? resp.optString("message", "Failed to update channel")
|
||||
: "No response from server";
|
||||
Alert a = new Alert(Alert.AlertType.ERROR, msg, ButtonType.OK);
|
||||
a.show();
|
||||
}
|
||||
}
|
||||
|
||||
private void openManageAdminsScene(JSONObject data) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_channel_admins.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageChannelAdminsController controller = loader.getController();
|
||||
// Pass channelId and subscribers list from JSON
|
||||
controller.setChannelData(
|
||||
data.optString("internal_uuid"),
|
||||
data.optJSONArray("subscribers")
|
||||
);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Channel Admins scene.", Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void openManageSubscribersScene(JSONObject data) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_subscribers.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageSubscribersController controller = loader.getController();
|
||||
// Pass channelId and subscribers list from the JSON data
|
||||
controller.setChannelData(
|
||||
data.optString("internal_uuid"),
|
||||
data.optJSONArray("subscribers")
|
||||
);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Subscribers scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageGroupController {
|
||||
|
||||
@FXML private VBox manageGroupCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton, changePicButton, cancelButton, saveButton;
|
||||
@FXML private ImageView groupImage;
|
||||
@FXML private TextField groupNameField;
|
||||
@FXML private Label adminCount, memberCount;
|
||||
@FXML private TextField groupIdField;
|
||||
@FXML private Button manageAdminsButton;
|
||||
@FXML private Button manageMembersButton;
|
||||
|
||||
private String groupId; // Internal UUID of the current group
|
||||
private String originalName;
|
||||
private String originalGroupId;
|
||||
private String originalImageUrl;
|
||||
private File selectedImageFile;
|
||||
|
||||
private JSONObject data;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(manageGroupCard.getParent()));
|
||||
|
||||
changePicButton.setOnAction(e -> choosePicture());
|
||||
saveButton.setOnAction(e -> saveChanges());
|
||||
|
||||
manageAdminsButton.setOnAction(e -> openManageAdminsScene(data));
|
||||
manageMembersButton.setOnAction(e -> openManageMembersScene(data));
|
||||
}
|
||||
|
||||
private void openManageAdminsScene(JSONObject data) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_admins.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageAdminsController controller = loader.getController();
|
||||
|
||||
String gid = data.optString("internal_uuid");
|
||||
JSONArray members = data.optJSONArray("members");
|
||||
JSONArray admins = new JSONArray();
|
||||
|
||||
// filter only admins
|
||||
if (members != null) {
|
||||
for (int i = 0; i < members.length(); i++) {
|
||||
JSONObject m = members.getJSONObject(i);
|
||||
String role = m.optString("role", "member");
|
||||
if ("admin".equalsIgnoreCase(role) || "owner".equalsIgnoreCase(role)) {
|
||||
admins.put(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller.setGroupData(gid, admins);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Admins scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void openManageMembersScene(JSONObject data) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/manage_members.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
ManageMembersController controller = loader.getController();
|
||||
// Pass groupId and members list from the JSON data
|
||||
controller.setGroupData(
|
||||
data.optString("internal_uuid"),
|
||||
data.optJSONArray("members")
|
||||
);
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Manage Members scene.", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void setGroupData(JSONObject data) {
|
||||
this.data = data;
|
||||
this.groupId = data.optString("internal_uuid");
|
||||
|
||||
originalName = data.optString("group_name", "");
|
||||
originalGroupId = data.optString("group_id", "");
|
||||
originalImageUrl = data.optString("image_url", "");
|
||||
|
||||
groupNameField.setText(originalName);
|
||||
groupIdField.setText(originalGroupId);
|
||||
|
||||
if (!originalImageUrl.isBlank()) {
|
||||
String uri = resolveLocalUri(originalImageUrl);
|
||||
if (uri != null) {
|
||||
groupImage.setImage(new Image(uri, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
adminCount.setText(String.valueOf(countRole(data, "admin") + 1));
|
||||
memberCount.setText(String.valueOf(countRole(data, "member") + Integer.parseInt(adminCount.getText())));
|
||||
}
|
||||
|
||||
private String resolveLocalUri(String raw) {
|
||||
if (raw == null || raw.isBlank()) return null;
|
||||
if (raw.startsWith("file:/") || raw.startsWith("http")) return raw;
|
||||
|
||||
Path base = Paths.get(System.getProperty("user.dir"), "uploads");
|
||||
Path resolved = base.resolve(raw.startsWith("/") ? raw.substring(1) : raw)
|
||||
.normalize();
|
||||
return resolved.toUri().toString(); // file:///C:/.../uploads/avatars/2025-09-05/...
|
||||
}
|
||||
|
||||
private int countRole(JSONObject groupData, String role) {
|
||||
var arr = groupData.optJSONArray("members");
|
||||
if (arr == null) return 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
if (role.equalsIgnoreCase(arr.getJSONObject(i).optString("role"))) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void choosePicture() {
|
||||
FileChooser fc = new FileChooser();
|
||||
fc.setTitle("Select group picture");
|
||||
selectedImageFile = fc.showOpenDialog(manageGroupCard.getScene().getWindow());
|
||||
if (selectedImageFile != null) {
|
||||
groupImage.setImage(new Image(selectedImageFile.toURI().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
String newName = groupNameField.getText().trim();
|
||||
String newGroupId = groupIdField.getText().trim(); // make sure you added this field
|
||||
String newImageUrl = (selectedImageFile != null)
|
||||
? selectedImageFile.toURI().toString()
|
||||
: originalImageUrl;
|
||||
|
||||
// Check if anything actually changed
|
||||
boolean changed =
|
||||
!Objects.equals(originalName, newName) ||
|
||||
!Objects.equals(originalGroupId, newGroupId) ||
|
||||
!Objects.equals(originalImageUrl, newImageUrl);
|
||||
|
||||
if (!changed) {
|
||||
// Nothing changed → just close overlay
|
||||
MainController.getInstance().closeOverlay(manageGroupCard.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
// Build request with all fields (server expects them)
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "edit_group_info")
|
||||
.put("group_id", groupId) // internal_uuid
|
||||
.put("new_group_id", newGroupId) // display id
|
||||
.put("name", newName);
|
||||
|
||||
if (newImageUrl != null && !newImageUrl.isBlank()) {
|
||||
req.put("image_url", newImageUrl);
|
||||
} else {
|
||||
req.put("image_url", JSONObject.NULL);
|
||||
}
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
MainController.getInstance().closeOverlay(manageGroupCard.getParent());
|
||||
} else {
|
||||
String msg = (resp != null)
|
||||
? resp.optString("message", "Failed to update group")
|
||||
: "No response from server";
|
||||
Alert a = new Alert(Alert.AlertType.ERROR, msg, ButtonType.OK);
|
||||
a.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageMembersController {
|
||||
|
||||
@FXML private VBox membersCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Button addMembersButton;
|
||||
@FXML private ScrollPane membersScroll;
|
||||
@FXML private VBox membersList;
|
||||
|
||||
private String groupId;
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(membersCard.getParent()));
|
||||
|
||||
addMembersButton.setOnAction(e -> {
|
||||
// open add members overlay
|
||||
openAddMembersOverlay(groupId);
|
||||
});
|
||||
|
||||
// Smooth scroll feel
|
||||
membersScroll.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
membersScroll.setPannable(true);
|
||||
membersScroll.setFitToWidth(true);
|
||||
membersScroll.setFitToHeight(false);
|
||||
membersScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
membersScroll.setVvalue(membersScroll.getVvalue() - deltaY);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void setGroupData(String groupId, JSONArray members) {
|
||||
this.groupId = groupId;
|
||||
membersList.getChildren().clear();
|
||||
|
||||
// Convert safely into JSONObject list
|
||||
java.util.List<JSONObject> parsed = new java.util.ArrayList<>();
|
||||
|
||||
for (int i = 0; i < members.length(); i++) {
|
||||
Object raw = members.get(i);
|
||||
|
||||
if (raw instanceof JSONObject obj) {
|
||||
parsed.add(obj);
|
||||
} else if (raw instanceof java.util.Map<?, ?> map) {
|
||||
parsed.add(new JSONObject(map));
|
||||
} else {
|
||||
System.err.println("Skipping invalid member element at index " + i + ": " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: owners first, then others
|
||||
parsed.sort((a, b) -> {
|
||||
boolean aOwner = "owner".equalsIgnoreCase(a.optString("role"));
|
||||
boolean bOwner = "owner".equalsIgnoreCase(b.optString("role"));
|
||||
return Boolean.compare(!aOwner, !bOwner); // false < true → owner first
|
||||
});
|
||||
|
||||
// Add rows
|
||||
for (JSONObject m : parsed) {
|
||||
addMemberRow(m);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMemberRow(JSONObject m) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = m.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(m.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(
|
||||
m.optBoolean("is_online", false) ? "online"
|
||||
: "last seen recently"
|
||||
);
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Only non-owners can be removed
|
||||
String role = m.optString("role", "member");
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
Button removeBtn = new Button("Remove");
|
||||
removeBtn.getStyleClass().add("link-btn");
|
||||
removeBtn.setOnAction(e -> removeMember(m.optString("user_id"), row));
|
||||
row.getChildren().add(removeBtn);
|
||||
}
|
||||
|
||||
membersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void removeMember(String userId, HBox row) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "remove_member_from_group")
|
||||
.put("group_id", groupId) // must be the group's internal_uuid
|
||||
.put("user_id", userId); // target user's internal_uuid
|
||||
|
||||
// Disable the button while request is in progress
|
||||
row.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
membersList.getChildren().remove(row);
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
row.setDisable(false); // re-enable on error
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message", "Failed to remove member.")
|
||||
: "No response from server.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void openAddMembersOverlay(String groupId) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddMembersController controller = loader.getController();
|
||||
// Pass the groupId as UUID
|
||||
controller.setGroupForAdd(UUID.fromString(groupId), "");
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error",
|
||||
"Could not load Add Members scene.",
|
||||
Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ManageSubscribersController {
|
||||
|
||||
@FXML private VBox subscribersCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button closeFooterButton;
|
||||
@FXML private Button addSubscribersButton;
|
||||
@FXML private ScrollPane subscribersScroll;
|
||||
@FXML private VBox subscribersList;
|
||||
|
||||
private String channelId; // internal UUID
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||
closeFooterButton.setOnAction(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(subscribersCard.getParent()));
|
||||
|
||||
addSubscribersButton.setOnAction(e -> openAddSubscribersOverlay(channelId));
|
||||
|
||||
// Smooth scroll
|
||||
subscribersScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
subscribersScroll.setPannable(true);
|
||||
subscribersScroll.setFitToWidth(true);
|
||||
subscribersScroll.setFitToHeight(false);
|
||||
subscribersScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003;
|
||||
subscribersScroll.setVvalue(subscribersScroll.getVvalue() - deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public void setChannelData(String channelId, JSONArray subscribers) {
|
||||
this.channelId = channelId;
|
||||
subscribersList.getChildren().clear();
|
||||
|
||||
for (int i = 0; i < subscribers.length(); i++) {
|
||||
Object raw = subscribers.get(i);
|
||||
JSONObject sub = (raw instanceof JSONObject)
|
||||
? (JSONObject) raw
|
||||
: new JSONObject((java.util.Map<?, ?>) raw);
|
||||
|
||||
addSubscriberRow(sub);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSubscriberRow(JSONObject sub) {
|
||||
HBox row = new HBox(10);
|
||||
row.getStyleClass().add("member-row");
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
// Avatar
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(36);
|
||||
avatar.setFitHeight(36);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
String imgUrl = sub.optString("image_url", "");
|
||||
if (!imgUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imgUrl);
|
||||
if (img != null) avatar.setImage(img);
|
||||
} else {
|
||||
avatar.setImage(new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"
|
||||
)));
|
||||
}
|
||||
|
||||
// Name + Status
|
||||
VBox details = new VBox(2);
|
||||
Label name = new Label(sub.optString("profile_name", "Unknown"));
|
||||
name.getStyleClass().add("member-name");
|
||||
|
||||
Label status = new Label(
|
||||
sub.optBoolean("is_online", false) ? "online" : "last seen recently"
|
||||
);
|
||||
status.getStyleClass().add("member-status");
|
||||
|
||||
details.getChildren().addAll(name, status);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
row.getChildren().addAll(avatar, details, spacer);
|
||||
|
||||
// Only non-owners can be removed
|
||||
String role = sub.optString("role", "subscriber");
|
||||
if (!"owner".equalsIgnoreCase(role)) {
|
||||
Button removeBtn = new Button("Remove");
|
||||
removeBtn.getStyleClass().add("link-btn");
|
||||
removeBtn.setOnAction(e -> removeSubscriber(sub.optString("user_id"), row));
|
||||
row.getChildren().add(removeBtn);
|
||||
}
|
||||
|
||||
subscribersList.getChildren().add(row);
|
||||
}
|
||||
|
||||
private void removeSubscriber(String userId, HBox row) {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "remove_subscriber_from_channel")
|
||||
.put("channel_id", channelId)
|
||||
.put("user_id", userId);
|
||||
|
||||
row.setDisable(true);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> subscribersList.getChildren().remove(row));
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
row.setDisable(false);
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message", "Failed to remove subscriber.")
|
||||
: "No response from server.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void openAddSubscribersOverlay(String channelId) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_subscriber.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
AddSubscriberController controller = loader.getController();
|
||||
controller.setChannelInfo(UUID.fromString(channelId), "", "", null, "");
|
||||
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
MainController.getInstance().showAlert(
|
||||
"Error", "Could not load Add Subscribers scene.", Alert.AlertType.ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class MediaPathResolver {
|
||||
private final Path uploadsRoot;
|
||||
|
||||
public MediaPathResolver(Path uploadsRoot) {
|
||||
this.uploadsRoot = uploadsRoot;
|
||||
}
|
||||
|
||||
public String toFileUri(String raw) {
|
||||
if (raw == null || raw.isBlank()) return null;
|
||||
if (raw.startsWith("file:/")) return raw;
|
||||
|
||||
Path p;
|
||||
if (raw.startsWith("/")) {
|
||||
String sub = raw.substring(1).replace("/", File.separator);
|
||||
p = uploadsRoot.resolve(sub).normalize();
|
||||
} else {
|
||||
p = Paths.get(raw).normalize();
|
||||
}
|
||||
return p.toUri().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MyProfileController {
|
||||
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button editButton;
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label profileName;
|
||||
@FXML private Label profileStatus;
|
||||
@FXML private Label userBio;
|
||||
@FXML private Label userId;
|
||||
@FXML private VBox profileCard;
|
||||
@FXML private VBox bioBlock;
|
||||
|
||||
private String userID;
|
||||
private static MyProfileController instance;
|
||||
|
||||
public MyProfileController() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static MyProfileController getInstance() { return instance;}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
overlayBackground.setOnMouseClicked(e -> closeProfile());
|
||||
closeButton.setOnAction(e -> closeProfile());
|
||||
|
||||
// Round profile picture
|
||||
profileImage.setClip(new Circle(40, 40, 40));
|
||||
|
||||
// Default avatar
|
||||
profileImage.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))
|
||||
));
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (profileCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(profileCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// Listener for theme change
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateEditIcon(newVal);
|
||||
});
|
||||
|
||||
// Set initial state
|
||||
updateEditIcon(ThemeManager.getInstance().isDarkMode());
|
||||
|
||||
// === Open edit profile ===
|
||||
editButton.setOnAction(e -> openEditProfile());
|
||||
|
||||
}
|
||||
|
||||
private void closeProfile() {
|
||||
MainController.getInstance().closeOverlay(profileCard.getParent());
|
||||
}
|
||||
|
||||
// Populate user data from DB or active session
|
||||
public void setProfileData(String name, String status, String bio, String userId, String imageUrl) {
|
||||
profileName.setText(name);
|
||||
profileStatus.setText(status);
|
||||
|
||||
if (bio == null || bio.isBlank()) {
|
||||
bioBlock.setVisible(false);
|
||||
bioBlock.setManaged(false);
|
||||
} else {
|
||||
userBio.setText(bio);
|
||||
bioBlock.setVisible(true);
|
||||
bioBlock.setManaged(true);
|
||||
}
|
||||
|
||||
this.userId.setText("@" + userId);
|
||||
userID = userId;
|
||||
|
||||
if (imageUrl != null && !imageUrl.isBlank()) {
|
||||
Image img = AvatarLocalResolver.load(imageUrl);
|
||||
if (img != null) {
|
||||
profileImage.setImage(img);
|
||||
} else {
|
||||
profileImage.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// === Theme-specific updates ===
|
||||
private void updateEditIcon(boolean darkMode) {
|
||||
String iconPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/edit_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/edit_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(iconPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
editButton.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void openEditProfile() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/edit_profile.fxml"));
|
||||
Node editOverlay = loader.load();
|
||||
|
||||
EditProfileController controller = loader.getController();
|
||||
// Pass current data (so fields are pre-filled)
|
||||
controller.setProfileData(
|
||||
profileName.getText(),
|
||||
profileStatus.getText(),
|
||||
bioBlock.isVisible() ? userBio.getText() : null,
|
||||
userID,
|
||||
profileImage.getImage()
|
||||
);
|
||||
|
||||
// Show new overlay
|
||||
MainController.getInstance().showOverlay(editOverlay);
|
||||
|
||||
// Close the current My Profile overlay
|
||||
closeProfile();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
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.Models.ChatEntry;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class NewChannelController {
|
||||
|
||||
@FXML private VBox newChannelCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private TextField channelNameField;
|
||||
@FXML private Label channelNameLabel;
|
||||
@FXML private TextArea channelDescField;
|
||||
@FXML private Label channelDescLabel;
|
||||
@FXML private Button cameraButton;
|
||||
@FXML private ImageView cameraIcon;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button createButton;
|
||||
@FXML private StackPane overlayRoot;
|
||||
@FXML private Label descCounter;
|
||||
@FXML private Label channelIdLabel;
|
||||
@FXML private TextField channelIdField;
|
||||
|
||||
private File channelImageFile;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cameraIcon.setImage(new Image(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
||||
));
|
||||
|
||||
cameraButton.setOnAction(e -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Choose Channel Picture");
|
||||
chooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg")
|
||||
);
|
||||
File file = chooser.showOpenDialog(cameraButton.getScene().getWindow());
|
||||
if (file != null) {
|
||||
channelImageFile = file;
|
||||
cameraIcon.setImage(new Image(file.toURI().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||
|
||||
createButton.setOnAction(e -> onCreateChannel());
|
||||
|
||||
// Limit description
|
||||
final int MAX_LENGTH = 255;
|
||||
channelDescField.addEventFilter(javafx.scene.input.KeyEvent.KEY_TYPED, e -> {
|
||||
if (channelDescField.getText().length() >= MAX_LENGTH) e.consume();
|
||||
});
|
||||
channelDescField.textProperty().addListener((obs, oldText, newText) -> {
|
||||
if (newText.length() > MAX_LENGTH) {
|
||||
channelDescField.setText(newText.substring(0, MAX_LENGTH));
|
||||
channelDescField.positionCaret(MAX_LENGTH);
|
||||
}
|
||||
int current = channelDescField.getText().length();
|
||||
descCounter.setText(current + " / " + MAX_LENGTH);
|
||||
descCounter.setStyle(current == MAX_LENGTH ? "-fx-text-fill: red;" : "");
|
||||
});
|
||||
descCounter.setText("0 / 255");
|
||||
|
||||
// Reset error when user types again
|
||||
channelNameField.textProperty().addListener((obs, ov, nv) -> {
|
||||
if (!nv.trim().isEmpty()) {
|
||||
channelNameField.getStyleClass().remove("error");
|
||||
channelNameLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
channelIdField.textProperty().addListener((obs, ov, nv) -> {
|
||||
if (!nv.trim().isEmpty()) {
|
||||
channelIdField.getStyleClass().remove("error");
|
||||
channelIdLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
|
||||
Platform.runLater(() -> channelNameField.requestFocus());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (newChannelCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(newChannelCard.getScene());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onCreateChannel() {
|
||||
String name = nz(channelNameField.getText()).trim();
|
||||
String dispId = nz(channelIdField.getText()).trim();
|
||||
String description = nz(channelDescField.getText()).trim();
|
||||
|
||||
boolean ok = true;
|
||||
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 (!ok) return;
|
||||
|
||||
final String me = Session.getUserUUID();
|
||||
if (me == null || me.isBlank()) {
|
||||
showToast("Cannot create channel: current user UUID missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// در مرحله ساخت، image_url را خالی بفرست؛ بعداً آپلود میکنیم
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "create_channel")
|
||||
.put("channel_id", dispId)
|
||||
.put("channel_name", name)
|
||||
.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(() -> {
|
||||
createButton.setDisable(false);
|
||||
channelIdField.getStyleClass().add("error");
|
||||
channelIdLabel.getStyleClass().add("error");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
private void showToast(String msg) {
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(overlayRoot.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
|
||||
private static String nz(String s){ return s==null? "": s; }
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
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.*;
|
||||
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 {
|
||||
|
||||
@FXML private VBox newGroupCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private TextField groupNameField;
|
||||
@FXML private Label groupNameLabel;
|
||||
@FXML private Button cameraButton;
|
||||
@FXML private ImageView cameraIcon;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button nextButton;
|
||||
@FXML private StackPane overlayRoot; // the root
|
||||
@FXML private TextField groupIdField;
|
||||
@FXML private Label groupIdLabel;
|
||||
|
||||
private File groupImageFile;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cameraIcon.setImage(new Image(
|
||||
getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/camera.png")
|
||||
));
|
||||
|
||||
cameraButton.setOnAction(e -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Choose Group Picture");
|
||||
chooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg")
|
||||
);
|
||||
File file = chooser.showOpenDialog(cameraButton.getScene().getWindow());
|
||||
if (file != null) {
|
||||
groupImageFile = file;
|
||||
cameraIcon.setImage(new Image(file.toURI().toString()));
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(overlayRoot));
|
||||
|
||||
nextButton.setOnAction(e -> onNext());
|
||||
|
||||
// clear errors on typing
|
||||
groupNameField.textProperty().addListener((obs, ov, nv) -> {
|
||||
if (!nv.trim().isEmpty()) {
|
||||
groupNameField.getStyleClass().remove("error");
|
||||
groupNameLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
groupIdField.textProperty().addListener((obs, ov, nv) -> {
|
||||
if (!nv.trim().isEmpty()) {
|
||||
groupIdField.getStyleClass().remove("error");
|
||||
groupIdLabel.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
|
||||
Platform.runLater(() -> groupNameField.requestFocus());
|
||||
}
|
||||
|
||||
private void onNext() {
|
||||
String groupName = groupNameField.getText() == null ? "" : groupNameField.getText().trim();
|
||||
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 (!ok) return;
|
||||
|
||||
final String me = Session.getUserUUID();
|
||||
if (me == null || me.isBlank()) {
|
||||
showToast("Cannot create group: current user UUID missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// در مرحلهٔ ساخت، 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)
|
||||
.put("image_url", imageUrl);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
Platform.runLater(() -> {
|
||||
groupIdField.getStyleClass().add("error");
|
||||
groupIdLabel.getStyleClass().add("error");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
if (data == null) {
|
||||
Platform.runLater(() -> showToast("Create failed: empty data."));
|
||||
return;
|
||||
}
|
||||
|
||||
UUID internalId = UUID.fromString(data.optString("internal_id"));
|
||||
String returnedName = data.optString("name", groupName);
|
||||
String returnedDisp = data.optString("id", groupId);
|
||||
String returnedImg = data.optString("image_url", "");
|
||||
|
||||
// اگر عکس انتخاب شده، حالا آپلود کن (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,
|
||||
imageUrlToUse,
|
||||
/*isOwner*/ true,
|
||||
/*isAdmin*/ true
|
||||
);
|
||||
MainController.getInstance().addChatAndSelect(entry);
|
||||
|
||||
// اگر URL جدید داریم، میتونی یک bust-cache بزنی:
|
||||
// MainController.getInstance().refreshChatAvatar(internalId, imageUrlToUse + "?v=" + System.currentTimeMillis());
|
||||
});
|
||||
|
||||
// 2) پنجره Add Members
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/add_member.fxml"));
|
||||
StackPane addMembersOverlay = loader.load();
|
||||
AddMembersController controller = loader.getController();
|
||||
controller.setGroupInfo(internalId, returnedName, returnedDisp, groupImageFile);
|
||||
|
||||
MainController.getInstance().showOverlay(addMembersOverlay);
|
||||
MainController.getInstance().closeOverlay(overlayRoot);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
showToast("Failed to open Add Members.");
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private void showToast(String msg) {
|
||||
// جایگزینش کن با سیستم نوتی شما
|
||||
Alert a = new Alert(Alert.AlertType.INFORMATION, msg, ButtonType.OK);
|
||||
a.initOwner(overlayRoot.getScene().getWindow());
|
||||
a.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class PrivacySecurityController {
|
||||
|
||||
@FXML private VBox privacyCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button backButton;
|
||||
@FXML private Button blockedUsersButton;
|
||||
@FXML private Button changeCredentialsButton;
|
||||
@FXML private ImageView blockIcon;
|
||||
@FXML private ImageView usernameIcon;
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Set back button icon
|
||||
ImageView backIcon = new ImageView(
|
||||
new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/back_button_dark.png"))
|
||||
);
|
||||
backIcon.setFitWidth(18);
|
||||
backIcon.setFitHeight(18);
|
||||
backButton.setGraphic(backIcon);
|
||||
|
||||
changeCredentialsButton.setOnAction(e -> openPasswordCheckOverlay());
|
||||
|
||||
// Blocked users management
|
||||
blockedUsersButton.setOnAction(e -> {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/blocked_users.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
// Show new overlay
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
// Close
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(privacyCard.getParent()));
|
||||
|
||||
// Back
|
||||
backButton.setOnAction(e -> MainController.getInstance().goBack(privacyCard));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(privacyCard.getParent()));
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (privacyCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(privacyCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// Listener for theme change
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateIcons(newVal);
|
||||
});
|
||||
|
||||
// Set initial state
|
||||
updateIcons(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
private void openPasswordCheckOverlay() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/check_password.fxml"));
|
||||
Node overlay = loader.load();
|
||||
|
||||
// Show new overlay
|
||||
MainController.getInstance().showOverlay(overlay);
|
||||
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIcons(boolean darkMode) {
|
||||
String suffix = darkMode ? "_light.png" : "_dark.png";
|
||||
|
||||
backButton.setGraphic(makeIcon(ICON_PATH + "back_button" + suffix));
|
||||
blockIcon.setImage(loadImage(ICON_PATH + "hand" + suffix));
|
||||
usernameIcon.setImage(loadImage(ICON_PATH + "username" + suffix));
|
||||
}
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) {
|
||||
System.err.println("Resource not found: " + path);
|
||||
return null;
|
||||
}
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.to.telegramfinalproject.UI.AppRouter.showLogin;
|
||||
|
||||
public class RegisterController {
|
||||
|
||||
@FXML private TextField userIdField;
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private TextField profileNameField;
|
||||
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private TextField visiblePasswordField;
|
||||
|
||||
@FXML private PasswordField confirmPasswordField;
|
||||
@FXML private TextField visibleConfirmPasswordField;
|
||||
|
||||
@FXML private Button togglePasswordBtn;
|
||||
@FXML private Button toggleConfirmBtn;
|
||||
|
||||
@FXML private Label errorLabel;
|
||||
|
||||
private ClientConnection connection;
|
||||
|
||||
private boolean passwordVisible = false;
|
||||
private boolean confirmVisible = false;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
try {
|
||||
org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||
visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void togglePasswordVisibility() {
|
||||
passwordVisible = !passwordVisible;
|
||||
visiblePasswordField.setVisible(passwordVisible);
|
||||
visiblePasswordField.setManaged(passwordVisible);
|
||||
passwordField.setVisible(!passwordVisible);
|
||||
passwordField.setManaged(!passwordVisible);
|
||||
togglePasswordBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void toggleConfirmPasswordVisibility() {
|
||||
confirmVisible = !confirmVisible;
|
||||
visibleConfirmPasswordField.setVisible(confirmVisible);
|
||||
visibleConfirmPasswordField.setManaged(confirmVisible);
|
||||
confirmPasswordField.setVisible(!confirmVisible);
|
||||
confirmPasswordField.setManaged(!confirmVisible);
|
||||
toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
// @FXML
|
||||
// private void handleRegister() {
|
||||
// String userID = userIdField.getText().trim();
|
||||
// String username = usernameField.getText().trim();
|
||||
// String profileName = profileNameField.getText().trim();
|
||||
// String password = passwordField.getText();
|
||||
// String confirmPass = confirmPasswordField.getText();
|
||||
//
|
||||
// userDatabase userDb = new userDatabase();
|
||||
// String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
//
|
||||
// // 1. Empty fields
|
||||
// if (userID.isEmpty() || username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) {
|
||||
// showError("Please fill in all required fields.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. Username exists
|
||||
// if (userDb.existsByUsername(username)) {
|
||||
// showError("This username is already taken.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 3. User ID exists
|
||||
// if (userDb.existsByUserId(userID)) {
|
||||
// showError("This user ID is already taken.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 4. Password mismatch
|
||||
// if (!password.equals(confirmPass)) {
|
||||
// showError("Passwords do not match.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 5. Weak password
|
||||
// if (!password.matches(passwordRegex)) {
|
||||
// showError("Password must be at least 8 characters, include a capital letter, a number, and a special character.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 6. Attempt registration
|
||||
// try {
|
||||
// JSONObject request = new JSONObject();
|
||||
// request.put("action", "register");
|
||||
// request.put("user_id", userID);
|
||||
// request.put("username", username);
|
||||
// request.put("password", password);
|
||||
// request.put("profile_name", profileName);
|
||||
// connection.send(request.toString());
|
||||
//
|
||||
// // Simulate successful registration (since main.fxml isn’t ready)
|
||||
// Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration successful!");
|
||||
// alert.show();
|
||||
//
|
||||
// } catch (Exception ex) {
|
||||
// showError("Failed to register. Please try again later.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleRegister() {
|
||||
String userID = userIdField.getText().trim();
|
||||
String username = usernameField.getText().trim();
|
||||
String profileName = profileNameField.getText().trim();
|
||||
String password = passwordField.getText();
|
||||
String confirmPass = confirmPasswordField.getText();
|
||||
|
||||
userDatabase userDb = new userDatabase();
|
||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
|
||||
// اعتبارسنجیها
|
||||
if (userID.isEmpty() || username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) {
|
||||
showError("Please fill in all required fields."); return;
|
||||
}
|
||||
if (userDb.existsByUsername(username)) { showError("This username is already taken."); return; }
|
||||
if (userDb.existsByUserId(userID)) { showError("This user ID is already taken."); return; }
|
||||
if (!password.equals(confirmPass)) { showError("Passwords do not match."); return; }
|
||||
if (!password.matches(passwordRegex)) { showError("Password must be at least 8 characters, include a capital letter, a number, and a special character."); return; }
|
||||
|
||||
// UI را موقتاً disable کن (اختیاری)
|
||||
setBusy(true);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// 1) مطمئن شو کانکشن/لیسنر روشن است
|
||||
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
var handler = cli.getHandler();
|
||||
|
||||
// 2) Register
|
||||
var regReq = new org.json.JSONObject()
|
||||
.put("action", "register")
|
||||
.put("user_id", userID)
|
||||
.put("username", username)
|
||||
.put("password", password)
|
||||
.put("profile_name", profileName);
|
||||
|
||||
var regResp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(regReq);
|
||||
if (regResp == null || !"success".equalsIgnoreCase(regResp.optString("status"))) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
showError(regResp != null ? regResp.optString("message","Register failed.") : "Register failed.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) Auto-Login با همان کانکشن
|
||||
handler.login(username, password);
|
||||
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
if (org.to.telegramfinalproject.Client.Session.currentUser == null || !handler.wasSuccess()) {
|
||||
showError(handler.getLastMessage().isEmpty() ? "Login failed." : handler.getLastMessage());
|
||||
return;
|
||||
}
|
||||
org.to.telegramfinalproject.UI.AppRouter.showMain();
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
showError("Failed to register/login: " + ex.getMessage());
|
||||
});
|
||||
}
|
||||
}, "register-thread").start();
|
||||
}
|
||||
|
||||
private void setBusy(boolean b){
|
||||
userIdField.setDisable(b);
|
||||
usernameField.setDisable(b);
|
||||
profileNameField.setDisable(b);
|
||||
passwordField.setDisable(b);
|
||||
visiblePasswordField.setDisable(b);
|
||||
confirmPasswordField.setDisable(b);
|
||||
visibleConfirmPasswordField.setDisable(b);
|
||||
togglePasswordBtn.setDisable(b);
|
||||
toggleConfirmBtn.setDisable(b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void switchToLogin() throws IOException {
|
||||
showLogin();
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
||||
Parent root = loader.load();
|
||||
Stage stage = (Stage) usernameField.getScene().getWindow();
|
||||
stage.setScene(new Scene(root, 500, 700));
|
||||
stage.show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
showError("Could not load scene: " + fxmlFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(String message) {
|
||||
errorLabel.setVisible(false);
|
||||
errorLabel.setText(""); // Clear the label text
|
||||
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||
pause.setOnFinished(event -> {
|
||||
errorLabel.setText(message);
|
||||
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
pause.play();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SetGroupAdminPermissionsController {
|
||||
|
||||
@FXML private VBox permissionsCard;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
@FXML private Button cancelButton;
|
||||
@FXML private Button saveButton;
|
||||
|
||||
@FXML private CheckBox canAddMember;
|
||||
@FXML private CheckBox canRemoveMember;
|
||||
@FXML private CheckBox canAddAdmin;
|
||||
@FXML private CheckBox canRemoveAdmin;
|
||||
@FXML private CheckBox canEditGroup;
|
||||
|
||||
private UUID groupId;
|
||||
private UUID userId;
|
||||
private Runnable onSuccess;
|
||||
|
||||
private JSONObject oldPermissions; // store existing permissions
|
||||
private boolean isEditMode = false; // true if editing existing admin
|
||||
|
||||
public void setTarget(UUID groupId, UUID userId, boolean isEditMode) {
|
||||
this.groupId = groupId;
|
||||
this.userId = userId;
|
||||
this.isEditMode = isEditMode;
|
||||
|
||||
if (isEditMode) {
|
||||
loadCurrentPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTarget(UUID groupId, UUID userId, boolean isEditMode, JSONObject oldPermissions) {
|
||||
this.groupId = groupId;
|
||||
this.userId = userId;
|
||||
this.isEditMode = isEditMode;
|
||||
this.oldPermissions = oldPermissions;
|
||||
}
|
||||
|
||||
public void setOnSuccess(Runnable r) {
|
||||
this.onSuccess = r;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
closeButton.setOnAction(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||
cancelButton.setOnAction(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||
overlayBackground.setOnMouseClicked(e -> MainController.getInstance().closeOverlay(permissionsCard.getParent()));
|
||||
|
||||
saveButton.setOnAction(e -> savePermissions());
|
||||
|
||||
// Register for dark mode updates
|
||||
Platform.runLater(() -> {
|
||||
if (permissionsCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(permissionsCard.getScene());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadCurrentPermissions() {
|
||||
JSONObject req = new JSONObject()
|
||||
.put("action", "get_group_admin_permissions")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("admin_id", userId.toString());
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
System.out.println("Permissions response: " + resp); // full server response
|
||||
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
// "data" is an object containing { "permissions": { ... } }
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
System.out.println("Data object: " + data);
|
||||
|
||||
if (data != null) {
|
||||
JSONObject perms = data.optJSONObject("permissions");
|
||||
System.out.println("Extracted permissions object: " + perms);
|
||||
|
||||
if (perms != null) {
|
||||
oldPermissions = perms;
|
||||
|
||||
// Update checkboxes safely on FX thread
|
||||
Platform.runLater(() -> {
|
||||
canAddMember.setSelected(perms.optBoolean("can_add_member", false));
|
||||
canRemoveMember.setSelected(perms.optBoolean("can_remove_member", false));
|
||||
canAddAdmin.setSelected(perms.optBoolean("can_add_admin", false));
|
||||
canRemoveAdmin.setSelected(perms.optBoolean("can_remove_admin", false));
|
||||
canEditGroup.setSelected(perms.optBoolean("can_edit_group", false));
|
||||
});
|
||||
} else {
|
||||
System.out.println("⚠️ Permissions object was null inside data.");
|
||||
}
|
||||
} else {
|
||||
System.out.println("⚠️ Data object was null in response.");
|
||||
}
|
||||
} else {
|
||||
System.out.println("⚠️ Response was null or status != success");
|
||||
}
|
||||
}
|
||||
|
||||
private void savePermissions() {
|
||||
JSONObject newPermissions = new JSONObject()
|
||||
.put("can_add_member", canAddMember.isSelected())
|
||||
.put("can_remove_member", canRemoveMember.isSelected())
|
||||
.put("can_add_admin", canAddAdmin.isSelected())
|
||||
.put("can_remove_admin", canRemoveAdmin.isSelected())
|
||||
.put("can_edit_group", canEditGroup.isSelected());
|
||||
|
||||
JSONObject req;
|
||||
if (isEditMode) {
|
||||
// --- Editing existing admin permissions ---
|
||||
req = new JSONObject()
|
||||
.put("action", "edit_admin_permissions")
|
||||
.put("chat_id", groupId.toString())
|
||||
.put("chat_type", "group") // since this controller is only for groups
|
||||
.put("admin_id", userId.toString())
|
||||
.put("permissions", newPermissions);
|
||||
} else {
|
||||
// --- Adding a new admin ---
|
||||
req = new JSONObject()
|
||||
.put("action", "add_admin_to_group")
|
||||
.put("group_id", groupId.toString())
|
||||
.put("user_id", userId.toString())
|
||||
.put("permissions", newPermissions);
|
||||
}
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
MainController.getInstance().closeOverlay(permissionsCard.getParent());
|
||||
if (onSuccess != null) onSuccess.run(); // update UI after success
|
||||
} else {
|
||||
Alert a = new Alert(Alert.AlertType.ERROR,
|
||||
resp != null ? resp.optString("message") : "Failed to save admin permissions.",
|
||||
ButtonType.OK);
|
||||
a.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
public class SettingsController {
|
||||
|
||||
@FXML private VBox settingsCard;
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label profileName;
|
||||
@FXML private Label profileId;
|
||||
@FXML private MenuButton menuButton;
|
||||
@FXML private MenuItem editProfileItem;
|
||||
@FXML private MenuItem logoutItem;
|
||||
@FXML private Button myAccountButton;
|
||||
@FXML private Button privacyButton;
|
||||
@FXML private Button faqButton;
|
||||
@FXML private Button featuresButton;
|
||||
@FXML private Pane overlayBackground;
|
||||
@FXML private Button closeButton;
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
private Image cachedAvatar;
|
||||
private String cachedName = "";
|
||||
private String cachedUsername = "";
|
||||
private String cachedStatus = "";
|
||||
private String cachedBio = "";
|
||||
|
||||
private static String nz(String s){ return s==null? "": s.trim(); }
|
||||
private static boolean hasVal(String s){ return s!=null && !s.trim().isEmpty() && !"null".equalsIgnoreCase(s); }
|
||||
|
||||
private static SettingsController instance;
|
||||
public static SettingsController getInstance() { return instance; }
|
||||
@FXML
|
||||
public void initialize() {
|
||||
instance = this;
|
||||
|
||||
editProfileItem.setOnAction(e -> openEditProfile());
|
||||
logoutItem.setOnAction(e -> onLogoutClicked());
|
||||
|
||||
populateFromSession();
|
||||
|
||||
// Make it circular
|
||||
Circle clip = new Circle(40, 30, 38); // centerX, centerY, radius
|
||||
profileImage.setClip(clip);
|
||||
|
||||
// Buttons
|
||||
myAccountButton.setOnAction(e -> openEditProfile());
|
||||
|
||||
privacyButton.setOnAction(e ->
|
||||
{
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/privacy_security.fxml"));
|
||||
Node privacyOverlay = loader.load();
|
||||
|
||||
MainController.getInstance().showOverlay(privacyOverlay);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
faqButton.setOnAction(e -> System.out.println("Telegram Q&A"));
|
||||
featuresButton.setOnAction(e -> System.out.println("Telegram Features"));
|
||||
|
||||
// Close overlay on background click
|
||||
overlayBackground.setOnMouseClicked(e ->
|
||||
MainController.getInstance().closeOverlay(settingsCard.getParent()));
|
||||
|
||||
closeButton.setOnAction(e ->
|
||||
MainController.getInstance().closeOverlay(settingsCard.getParent())
|
||||
);
|
||||
|
||||
// Register scene for ThemeManager → stylesheet swap will handle colors/icons
|
||||
Platform.runLater(() -> {
|
||||
if (settingsCard.getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(settingsCard.getScene());
|
||||
}
|
||||
});
|
||||
|
||||
// Listener for theme change
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
updateEditIcon(newVal);
|
||||
updateMoreIcon(newVal);
|
||||
updateButtonsIcons(newVal);
|
||||
});
|
||||
|
||||
// Set initial state
|
||||
updateEditIcon(ThemeManager.getInstance().isDarkMode());
|
||||
updateMoreIcon(ThemeManager.getInstance().isDarkMode());
|
||||
updateButtonsIcons(ThemeManager.getInstance().isDarkMode());
|
||||
}
|
||||
|
||||
private void openEditProfile() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/edit_profile.fxml"));
|
||||
Node editOverlay = loader.load();
|
||||
|
||||
EditProfileController controller = loader.getController();
|
||||
controller.setProfileData(
|
||||
cachedName,
|
||||
cachedStatus,
|
||||
cachedBio,
|
||||
cachedUsername,
|
||||
cachedAvatar
|
||||
);
|
||||
controller.setParentSettings(this);
|
||||
MainController.getInstance().showOverlay(editOverlay);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateEditIcon(boolean darkMode) {
|
||||
String editPath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/edit_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/edit_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(editPath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
editProfileItem.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void updateMoreIcon(boolean darkMode) {
|
||||
String morePath = darkMode
|
||||
? "/org/to/telegramfinalproject/Icons/more_light.png"
|
||||
: "/org/to/telegramfinalproject/Icons/more_dark.png";
|
||||
|
||||
ImageView icon = new ImageView(new Image(getClass().getResourceAsStream(morePath)));
|
||||
icon.setFitWidth(16);
|
||||
icon.setFitHeight(16);
|
||||
menuButton.setGraphic(icon);
|
||||
}
|
||||
|
||||
private void updateButtonsIcons(boolean darkMode) {
|
||||
String suffix = darkMode ? "_light.png" : "_dark.png";
|
||||
myAccountButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix));
|
||||
privacyButton.setGraphic(makeIcon(ICON_PATH + "lock" + suffix));
|
||||
faqButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix));
|
||||
featuresButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix));
|
||||
}
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) {
|
||||
System.err.println("Resource not found: " + path);
|
||||
return null;
|
||||
}
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void populateFromSession() {
|
||||
var u = org.to.telegramfinalproject.Client.Session.currentUser;
|
||||
if (u == null) return;
|
||||
|
||||
// نام
|
||||
String name = nz(u.optString("profile_name",
|
||||
u.optString("name",
|
||||
u.optString("first_name",""))));
|
||||
|
||||
// یوزرنیم/آیدی نمایشی
|
||||
String handle = nz(u.optString("username",
|
||||
u.optString("display_id",
|
||||
u.optString("user_name",""))));
|
||||
// وضعیت
|
||||
String status = u.optBoolean("online", false) ? "online" : "online";
|
||||
String lastSeen = nz(u.optString("last_seen", ""));
|
||||
if (!u.optBoolean("online", false) && hasVal(lastSeen)) status = "online"; // ساده
|
||||
|
||||
// بیو (اگر داری)
|
||||
String bio = nz(u.optString("bio",""));
|
||||
|
||||
// آواتار
|
||||
Image avatar = null;
|
||||
String imageUrl = nz(u.optString("image_url",""));
|
||||
if (hasVal(imageUrl)) {
|
||||
try { avatar = org.to.telegramfinalproject.Client.AvatarLocalResolver.load(imageUrl); }
|
||||
catch (Exception ignore) {}
|
||||
}
|
||||
if (avatar == null) {
|
||||
avatar = new Image(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"));
|
||||
}
|
||||
|
||||
// کش محلی برای استفاده در صفحهی ویرایش
|
||||
cachedName = hasVal(name) ? name : "User";
|
||||
cachedUsername = hasVal(handle) ? handle : "";
|
||||
cachedStatus = status;
|
||||
cachedBio = bio;
|
||||
cachedAvatar = avatar;
|
||||
|
||||
// ست کردن در UI
|
||||
profileName.setText(cachedName);
|
||||
profileId.setText("@" + cachedUsername);
|
||||
profileImage.setImage(cachedAvatar);
|
||||
|
||||
// گرد کردن تصویر (با توجه به اندازهی واقعی)
|
||||
Circle clip = new Circle(40, 30, 38); // centerX, centerY, radius
|
||||
profileImage.setClip(clip);
|
||||
}
|
||||
|
||||
// جایی مثل SidebarMenuController یا MainController
|
||||
private void onLogoutClicked() {
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject().put("action","logout"); // user_id لازم نیست
|
||||
req.put("user_id",Session.getUserUUID()); // user_id لازم نیست
|
||||
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (res != null && "success".equalsIgnoreCase(res.optString("status"))) {
|
||||
try {
|
||||
// قطع ارتباط/لیسنر (اگر متد داری)
|
||||
// TelegramClient.disconnect();
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
// پاکسازی امن سشن (ترجیحاً clear بهجای null)
|
||||
Session.currentUser = null;
|
||||
Session.chatList = null;
|
||||
AppRouter.showIntro(); // intro.fxml
|
||||
} else {
|
||||
new Alert(Alert.AlertType.ERROR,
|
||||
"Logout not successful: " + (res != null ? res.optString("message") : "No response")
|
||||
).showAndWait();
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SidebarMenuController {
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
|
||||
|
||||
|
||||
@FXML private VBox sidebarRoot;
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label usernameLabel;
|
||||
|
||||
@FXML private Button myProfileButton;
|
||||
@FXML private Button newGroupButton;
|
||||
@FXML private Button newChannelButton;
|
||||
@FXML private Button contactsButton;
|
||||
@FXML private Button savedMessagesButton;
|
||||
@FXML private Button settingsButton;
|
||||
@FXML private Button telegramFeaturesButton;
|
||||
@FXML private Button telegramQnAButton;
|
||||
|
||||
// Custom Telegram-style toggle
|
||||
@FXML private HBox nightModeToggle;
|
||||
@FXML private Region toggleThumb;
|
||||
@FXML private ImageView nightModeIcon;
|
||||
|
||||
// convenience handle
|
||||
private final ThemeManager themeManager = ThemeManager.getInstance();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Load default profile image
|
||||
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||
if (profile != null) profileImage.setImage(profile);
|
||||
AvatarFX.circleClip(profileImage, 56);
|
||||
|
||||
|
||||
setupButtonActions();
|
||||
setupToggleAction();
|
||||
|
||||
// When the Scene is ready:
|
||||
sidebarRoot.sceneProperty().addListener((obs, oldScene, newScene) -> {
|
||||
if (newScene != null) {
|
||||
// 1) Register with ThemeManager so this scene auto-updates on theme changes
|
||||
themeManager.registerScene(newScene);
|
||||
|
||||
// 2) Make sure we start in LIGHT mode
|
||||
themeManager.setDarkMode(false);
|
||||
|
||||
// 3) Sync icons & toggle with current mode
|
||||
boolean dark = themeManager.isDarkMode();
|
||||
updateIcons(dark);
|
||||
syncToggleVisual(dark, /*animate=*/true);
|
||||
|
||||
// Ensure the thumb is correctly positioned after layout pass
|
||||
Platform.runLater(() -> syncToggleVisual(themeManager.isDarkMode(), false));
|
||||
}
|
||||
});
|
||||
|
||||
// If theme is changed from somewhere else (another screen), keep sidebar in sync
|
||||
themeManager.darkModeProperty().addListener((o, wasDark, isDark) -> {
|
||||
updateIcons(isDark);
|
||||
syncToggleVisual(isDark, /*animate=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupButtonActions() {
|
||||
myProfileButton.setOnAction(e -> openMyProfile());
|
||||
newGroupButton.setOnAction(e -> createNewGroup());
|
||||
newChannelButton.setOnAction(e -> createNewChannel());
|
||||
contactsButton.setOnAction(e -> openContacts());
|
||||
savedMessagesButton.setOnAction(e -> openSavedMessages());
|
||||
settingsButton.setOnAction(e -> openSettings());
|
||||
telegramFeaturesButton.setOnAction(e -> openTelegramFeatures());
|
||||
telegramQnAButton.setOnAction(e -> openTelegramQnA());
|
||||
}
|
||||
|
||||
private void setupToggleAction() {
|
||||
nightModeToggle.setOnMouseClicked(e -> {
|
||||
boolean newDark = !themeManager.isDarkMode();
|
||||
|
||||
// Update the global theme via ThemeManager
|
||||
themeManager.setDarkMode(newDark);
|
||||
themeManager.applyThemeToAll();
|
||||
|
||||
// Animate the thumb to its new position (listener will handle icons & final position sync)
|
||||
syncToggleVisual(newDark, /*animate=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
/** Update all button icons to match theme.
|
||||
* darkMode == true => white icons => use *_light.png
|
||||
* darkMode == false => dark icons => use *_dark.png
|
||||
*/
|
||||
private void updateIcons(boolean darkMode) {
|
||||
String suffix = darkMode ? "_light.png" : "_dark.png";
|
||||
myProfileButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix));
|
||||
newGroupButton.setGraphic(makeIcon(ICON_PATH + "new_group" + suffix));
|
||||
newChannelButton.setGraphic(makeIcon(ICON_PATH + "new_channel" + suffix));
|
||||
contactsButton.setGraphic(makeIcon(ICON_PATH + "contacts" + suffix));
|
||||
savedMessagesButton.setGraphic(makeIcon(ICON_PATH + "saved_messages" + suffix));
|
||||
settingsButton.setGraphic(makeIcon(ICON_PATH + "settings" + suffix));
|
||||
telegramFeaturesButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix));
|
||||
telegramQnAButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix));
|
||||
|
||||
// Night-mode moon icon
|
||||
Image moon = loadImage(ICON_PATH + "night_mode" + suffix);
|
||||
if (moon != null) nightModeIcon.setImage(moon);
|
||||
}
|
||||
|
||||
/** Keep the toggle’s CSS class and thumb position in sync with the current mode. */
|
||||
private void syncToggleVisual(boolean darkMode, boolean animate) {
|
||||
// CSS class "on" on the track
|
||||
if (darkMode) {
|
||||
if (!nightModeToggle.getStyleClass().contains("on")) {
|
||||
nightModeToggle.getStyleClass().add("on");
|
||||
}
|
||||
} else {
|
||||
nightModeToggle.getStyleClass().remove("on");
|
||||
}
|
||||
|
||||
// Compute target X for the thumb
|
||||
double offX = 2;
|
||||
double onX = Math.max(2, nightModeToggle.getWidth() - toggleThumb.getWidth() - 4);
|
||||
double targetX = darkMode ? onX : offX;
|
||||
|
||||
if (animate) {
|
||||
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
|
||||
tt.setToX(targetX);
|
||||
tt.play();
|
||||
} else {
|
||||
toggleThumb.setTranslateX(targetX);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) {
|
||||
System.err.println("Resource not found: " + path);
|
||||
return null;
|
||||
}
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
// Sidebar actions
|
||||
private void openMyProfile() {
|
||||
try {
|
||||
// 1. Ask server for profile info
|
||||
JSONObject request = new JSONObject().put("action", "get_user_profile");
|
||||
JSONObject response = ActionHandler.sendWithResponse(request);
|
||||
|
||||
if (response == null || !"success".equals(response.optString("status"))) {
|
||||
showAlert("Error", "Failed to load profile", Alert.AlertType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject profile = response.optJSONObject("data");
|
||||
if (profile == null) {
|
||||
showAlert("Error", "Malformed profile data", Alert.AlertType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract fields
|
||||
String profileName = profile.optString("profile_name", "Unknown");
|
||||
String bio = profile.optString("bio", "");
|
||||
String userId = profile.optString("user_id", "");
|
||||
String imageUrl = profile.optString("profile_picture_url", null);
|
||||
|
||||
// 2. Load overlay FXML
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/my_profile.fxml"));
|
||||
Node profileOverlay = loader.load();
|
||||
|
||||
// 3. Pass real data to controller
|
||||
MyProfileController controller = loader.getController();
|
||||
controller.setProfileData(profileName, "online", bio, userId, imageUrl);
|
||||
|
||||
// 4. Show overlay
|
||||
MainController.getInstance().showOverlay(profileOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
showAlert("Error", "Error opening profile", Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewGroup() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/new_group.fxml"));
|
||||
Node groupOverlay = loader.load();
|
||||
|
||||
// Show overlay (like MyProfile, Contacts, etc.)
|
||||
MainController.getInstance().showOverlay(groupOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewChannel() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/new_channel.fxml"));
|
||||
Node channelOverlay = loader.load();
|
||||
|
||||
// Show overlay (like MyProfile, Contacts, etc.)
|
||||
MainController.getInstance().showOverlay(channelOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void openContacts() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/contacts.fxml"));
|
||||
Node contactsOverlay = loader.load();
|
||||
|
||||
// Show overlay on top of everything
|
||||
MainController.getInstance().showOverlay(contactsOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void openSavedMessages() {
|
||||
try {
|
||||
// 1) درخواست به سرور
|
||||
JSONObject req = new JSONObject().put("action", "get_or_create_saved_messages");
|
||||
JSONObject res = ActionHandler.sendWithResponse(req);
|
||||
|
||||
if (res == null || !"success".equals(res.optString("status"))) {
|
||||
String msg = (res != null) ? res.optString("message", "Unknown error") : "No response";
|
||||
showAlert("Saved Messages", "Failed: " + msg, Alert.AlertType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject data = res.optJSONObject("data");
|
||||
if (data == null) {
|
||||
showAlert("Saved Messages", "Malformed response.", Alert.AlertType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) دادهها
|
||||
String chatIdStr = data.optString("chat_id", null);
|
||||
if (chatIdStr == null || chatIdStr.isBlank()) {
|
||||
showAlert("Saved Messages", "Missing chat_id.", Alert.AlertType.ERROR);
|
||||
return;
|
||||
}
|
||||
UUID chatId = UUID.fromString(chatIdStr);
|
||||
String name = data.optString("name", "Saved Messages");
|
||||
String chatType = data.optString("chat_type", "private");
|
||||
boolean isSaved = data.optBoolean("is_saved_messages", true);
|
||||
|
||||
// اگر آیکن اختصاصی برای Saved داری، این مسیر رو بده؛ وگرنه null بذار:
|
||||
String savedIcon = ICON_PATH + (themeManager.isDarkMode() ? "saved_messages_light.png" : "saved_messages_dark.png");
|
||||
// اگر چنین آیکنی نداری، میتونی null بدی تا همان image_url خالی بماند:
|
||||
// String savedIcon = null;
|
||||
|
||||
// 3) درج/بهروزرسانی در Session و بیاور اول لیست
|
||||
ChatEntry entry = org.to.telegramfinalproject.Client.Session
|
||||
.upsertSavedMessages(chatId, name, chatType, savedIcon);
|
||||
|
||||
// 4) وضعیتهای فعلی سشن برای ناوبری
|
||||
org.to.telegramfinalproject.Client.Session.currentChatId = chatId.toString();
|
||||
org.to.telegramfinalproject.Client.Session.currentChatType = chatType;
|
||||
org.to.telegramfinalproject.Client.Session.currentChatEntry = entry;
|
||||
org.to.telegramfinalproject.Client.Session.backToChatList = false;
|
||||
|
||||
// 5) ریفِرش ظاهری لیست چتها (اگر متدی برای این داری، صدا بزن)
|
||||
try {
|
||||
MainController.getInstance().refreshChatListUI(); // اگر متد دیگری داری عوضش کن
|
||||
} catch (Throwable ignore) { }
|
||||
|
||||
// 6) باز کردن چت
|
||||
// --- مسیر اول: اگر متدی داری که با ChatEntry باز میکند:
|
||||
boolean opened = false;
|
||||
try {
|
||||
MainController.getInstance().openChat(entry);
|
||||
MainController.getInstance().closeSidebar();
|
||||
opened = true;
|
||||
} catch (Throwable t) {
|
||||
// مسیر دوم: اگر با id/type باز میکنی، یا اول info میگیری:
|
||||
try {
|
||||
ActionHandler.requestChatInfo(String.valueOf(chatId), chatType);
|
||||
// اگر متد آشکار برای باز کردن با id داری، اینجا صدا بزن:
|
||||
// MainController.getInstance().openChatById(chatId, chatType);
|
||||
opened = true;
|
||||
} catch (Throwable t2) {
|
||||
t2.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (!opened) {
|
||||
// اگر هیچکدام نداشت، حداقل پیغام بده که چت ساخته و آماده است:
|
||||
System.out.println("Saved Messages ready. Open manually with currentChatId/currentChatType.");
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
showAlert("Saved Messages", "Error: " + ex.getMessage(), Alert.AlertType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void openSettings() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/settings.fxml"));
|
||||
Node settingsOverlay = loader.load();
|
||||
|
||||
// Show overlay on top of everything
|
||||
MainController.getInstance().showOverlay(settingsOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void openTelegramFeatures() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/telegram_features.fxml"));
|
||||
Node featuresOverlay = loader.load();
|
||||
|
||||
MainController.getInstance().showOverlay(featuresOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void openTelegramQnA() {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource(
|
||||
"/org/to/telegramfinalproject/Fxml/telegram_qna.fxml"));
|
||||
Node qnaOverlay = loader.load();
|
||||
|
||||
MainController.getInstance().showOverlay(qnaOverlay);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserFromSession(JSONObject user) {
|
||||
if (user == null) return;
|
||||
|
||||
// نام نمایشی (اول profile_name بعد username)
|
||||
String displayName = user.optString("profile_name",
|
||||
user.optString("username", ""));
|
||||
usernameLabel.setText(displayName);
|
||||
|
||||
// عکس پروفایل: هم URL اینترنتی هم مسیر ریسورس را پشتیبانی کن
|
||||
String img = user.optString("image_url", "");
|
||||
Image pic = tryLoadImage(img);
|
||||
if (pic == null) {
|
||||
pic = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||
}
|
||||
if (pic != null) profileImage.setImage(pic);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private Image tryLoadImage(String src) {
|
||||
if (src == null || src.isBlank()) return null;
|
||||
try {
|
||||
// اگر مسیر داخل resources است
|
||||
var res = getClass().getResource(src);
|
||||
if (res != null) return new Image(res.toExternalForm(), true);
|
||||
|
||||
// اگر مسیر نسبی سرور (مثل /avatars/...) است
|
||||
String fileUri = AvatarLocalResolver.resolve(src);
|
||||
if (fileUri != null) return new Image(fileUri, true);
|
||||
|
||||
// در غیر این صورت، فرض URL کامل
|
||||
return new Image(src, true);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlert(String title, String message, Alert.AlertType type) {
|
||||
Alert alert = new Alert(type);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null); // no big header, just the message
|
||||
alert.setContentText(message);
|
||||
|
||||
// optional: style it to fit your dark/light theme
|
||||
if (alert.getDialogPane().getScene() != null) {
|
||||
ThemeManager.getInstance().registerScene(alert.getDialogPane().getScene());
|
||||
}
|
||||
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.to.telegramfinalproject.UI;//package org.to.telegramfinalproject.UI;
|
||||
//
|
||||
//
|
||||
//import javafx.application.Application;
|
||||
//import javafx.fxml.FXMLLoader;
|
||||
//import javafx.scene.Scene;
|
||||
//import javafx.scene.image.Image;
|
||||
//import javafx.stage.Stage;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//
|
||||
//public class TelegramApplication extends Application {
|
||||
// @Override
|
||||
// public void start(Stage stage) throws IOException {
|
||||
// FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/main.fxml"));
|
||||
// Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
|
||||
//
|
||||
// scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm());
|
||||
//
|
||||
// stage.setTitle("Telegram");
|
||||
// stage.setScene(scene);
|
||||
//
|
||||
// // Add icon to the stage
|
||||
// Image icon = new Image(TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png"));
|
||||
// stage.getIcons().add(icon);
|
||||
//
|
||||
// stage.show();
|
||||
// }
|
||||
// public static void main(String[] args) {
|
||||
// launch();
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.to.telegramfinalproject.UI.AppRouter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class TelegramApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
// First: intro.fxml
|
||||
FXMLLoader fx = new FXMLLoader(
|
||||
TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/intro.fxml"));
|
||||
Scene scene = new Scene(fx.load(), 1480, 820);
|
||||
|
||||
scene.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm()
|
||||
);
|
||||
|
||||
stage.setTitle("Telegram");
|
||||
stage.getIcons().add(new Image(
|
||||
TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png")
|
||||
));
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
AppRouter.init(stage, scene);
|
||||
}
|
||||
|
||||
public static void main(String[] args) { launch(); }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user