Merge branch 'Connection-UI' into Main-UI
# Conflicts: # src/main/java/org/to/telegramfinalproject/UI/MainController.java # src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css # src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css
@@ -27,13 +27,36 @@ public class ActionHandler {
|
|||||||
public static volatile boolean forceExitChat = false;
|
public static volatile boolean forceExitChat = false;
|
||||||
public static ActionHandler instance;
|
public static ActionHandler instance;
|
||||||
|
|
||||||
|
//use for UI
|
||||||
|
private volatile String lastStatus = "error"; // success | error
|
||||||
|
private volatile String lastMessage = "";
|
||||||
|
// ===== UI hook for search results =====
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public String getLastStatus() { return lastStatus; }
|
||||||
|
public String getLastMessage() { return lastMessage; }
|
||||||
|
public boolean wasSuccess() { return "success".equalsIgnoreCase(lastStatus); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// private void handleRealTime(JSONObject json) throws IOException {
|
||||||
|
// IncomingMessageListener listener = new IncomingMessageListener(this.in);
|
||||||
|
// listener.handleRealTimeEvent (json);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ActionHandler.java
|
||||||
private void handleRealTime(JSONObject json) throws IOException {
|
private void handleRealTime(JSONObject json) throws IOException {
|
||||||
IncomingMessageListener listener = new IncomingMessageListener(this.in);
|
IncomingMessageListener listener = TelegramClient.getInstance().getListener();
|
||||||
|
if (listener != null) {
|
||||||
listener.handleRealTimeEvent(json);
|
listener.handleRealTimeEvent(json);
|
||||||
|
} else {
|
||||||
|
System.err.println("[RT] Listener not ready; dropping RT event: " + json);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ActionHandler(PrintWriter out, BufferedReader in, Scanner scanner) {
|
public ActionHandler(PrintWriter out, BufferedReader in, Scanner scanner) {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
@@ -42,6 +65,17 @@ public class ActionHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void login(String username , String password){
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.send(request);
|
||||||
|
|
||||||
|
}
|
||||||
public void loginHandler() {
|
public void loginHandler() {
|
||||||
System.out.println("Login form: \n");
|
System.out.println("Login form: \n");
|
||||||
System.out.print("Username: ");
|
System.out.print("Username: ");
|
||||||
@@ -122,6 +156,18 @@ public class ActionHandler {
|
|||||||
send(model.toJson());
|
send(model.toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void searchUI(String keyword) {
|
||||||
|
|
||||||
|
if (Session.currentUser == null || !Session.currentUser.has("user_id")) {
|
||||||
|
System.out.println("You must be logged in to search.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String userId = Session.currentUser.getString("user_id");
|
||||||
|
SearchRequestModel model = new SearchRequestModel("search", keyword, userId);
|
||||||
|
send(model.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void searchInUsers(){
|
public void searchInUsers(){
|
||||||
System.out.println("Enter keyword to search: ");
|
System.out.println("Enter keyword to search: ");
|
||||||
@@ -197,7 +243,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addContact(UUID contactId) {
|
public void addContact(UUID contactId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "add_contact");
|
req.put("action", "add_contact");
|
||||||
req.put("user_id", Session.currentUser.getString("user_id"));
|
req.put("user_id", Session.currentUser.getString("user_id"));
|
||||||
@@ -206,7 +252,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void joinGroupOrChannel(String type, String uuid) {
|
public void joinGroupOrChannel(String type, String uuid) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "join_" + type);
|
req.put("action", "join_" + type);
|
||||||
req.put("user_id", Session.getUserUUID());
|
req.put("user_id", Session.getUserUUID());
|
||||||
@@ -216,7 +262,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private ChatEntry fetchChatInfo(String receiverId, String receiverType) {
|
public ChatEntry fetchChatInfo(String receiverId, String receiverType) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", receiverId);
|
req.put("receiver_id", receiverId);
|
||||||
@@ -256,7 +302,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void refreshChatList() {
|
public void refreshChatList() {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_list");
|
req.put("action", "get_chat_list");
|
||||||
req.put("user_id", Session.currentUser.getString("user_id"));
|
req.put("user_id", Session.currentUser.getString("user_id"));
|
||||||
@@ -325,21 +371,38 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChatEntry parseChatEntry(JSONObject chat) {
|
private ChatEntry parseChatEntry(JSONObject chat) {
|
||||||
ChatEntry entry = new ChatEntry(
|
UUID internalId = null;
|
||||||
UUID.fromString(chat.getString("internal_id")),
|
try { internalId = UUID.fromString(chat.getString("internal_id")); } catch (Exception ignored) {}
|
||||||
chat.getString("id"),
|
|
||||||
chat.getString("name"),
|
|
||||||
chat.optString("image_url", ""),
|
|
||||||
chat.getString("type"),
|
|
||||||
chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")),
|
|
||||||
chat.optBoolean("is_owner", false),
|
|
||||||
chat.optBoolean("is_admin", false)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (chat.has("other_user_id") && !chat.isNull("other_user_id")) {
|
String id = chat.optString("id", "");
|
||||||
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id")));
|
String name = chat.optString("name", "");
|
||||||
|
String imageUrl = chat.optString("image_url", "");
|
||||||
|
String type = chat.optString("type", "");
|
||||||
|
boolean isOwner = chat.optBoolean("is_owner", false);
|
||||||
|
boolean isAdmin = chat.optBoolean("is_admin", false);
|
||||||
|
|
||||||
|
LocalDateTime lastTime = null;
|
||||||
|
String lts = chat.optString("last_message_time", null);
|
||||||
|
if (lts != null && !"null".equalsIgnoreCase(lts)) {
|
||||||
|
try { lastTime = LocalDateTime.parse(lts); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatEntry entry = new ChatEntry(internalId, id, name, imageUrl, type, lastTime, isOwner, isAdmin);
|
||||||
|
|
||||||
|
entry.setUnreadCount(chat.optInt("unread_count", 0));
|
||||||
|
String preview = chat.optString("last_message_preview", null);
|
||||||
|
if (preview != null && !"null".equalsIgnoreCase(preview)) {
|
||||||
|
entry.setLastMessagePreview(preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
String other = chat.optString("other_user_id", null);
|
||||||
|
if (other != null && !"null".equalsIgnoreCase(other)) {
|
||||||
|
try { entry.setOtherUserId(UUID.fromString(other)); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chat.has("is_saved_messages")) {
|
||||||
entry.setSavedMessages(chat.optBoolean("is_saved_messages", false));
|
entry.setSavedMessages(chat.optBoolean("is_saved_messages", false));
|
||||||
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -433,7 +496,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void send(JSONObject request) {
|
public void send(JSONObject request) {
|
||||||
try {
|
try {
|
||||||
if (!request.has("action") || request.isNull("action")) {
|
if (!request.has("action") || request.isNull("action")) {
|
||||||
System.err.println("❌ Invalid request: missing action.");
|
System.err.println("❌ Invalid request: missing action.");
|
||||||
@@ -471,6 +534,12 @@ public class ActionHandler {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
status = response.optString("status","error");
|
||||||
|
String message = response.optString("message","");
|
||||||
|
this.lastStatus = status;
|
||||||
|
this.lastMessage = message;
|
||||||
|
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "login":
|
case "login":
|
||||||
case "register":
|
case "register":
|
||||||
@@ -622,6 +691,8 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "search" :
|
case "search" :
|
||||||
|
|
||||||
|
|
||||||
JSONArray results = response.getJSONObject("data").getJSONArray("results");
|
JSONArray results = response.getJSONObject("data").getJSONArray("results");
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
@@ -1057,7 +1128,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void viewProfile(UUID targetId) {
|
public void viewProfile(UUID targetId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_profile");
|
req.put("action", "view_profile");
|
||||||
req.put("target_id", targetId.toString());
|
req.put("target_id", targetId.toString());
|
||||||
@@ -1085,7 +1156,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void startPrivateChat(ContactEntry contact) {
|
public void startPrivateChat(ContactEntry contact) {
|
||||||
UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||||
UUID contactId = contact.getContactId();
|
UUID contactId = contact.getContactId();
|
||||||
|
|
||||||
@@ -1115,10 +1186,10 @@ public class ActionHandler {
|
|||||||
);
|
);
|
||||||
entry.setOtherUserId(contactId);
|
entry.setOtherUserId(contactId);
|
||||||
|
|
||||||
Session.chatList.add(0, entry); // اضافه به اول لیست
|
Session.chatList.add(0, entry);
|
||||||
System.out.println("✅ Chat with " + contact.getProfileName() + " started.");
|
System.out.println("✅ Chat with " + contact.getProfileName() + " started.");
|
||||||
|
|
||||||
openChat(entry); // 👈 مستقیم وارد چت شو (اختیاری)
|
openChat(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1239,7 +1310,7 @@ public class ActionHandler {
|
|||||||
System.out.println("\nYour Chats:");
|
System.out.println("\nYour Chats:");
|
||||||
System.out.println("0. 📦 Archived Chats");
|
System.out.println("0. 📦 Archived Chats");
|
||||||
|
|
||||||
// پیدا کردن Saved در لیست
|
//find save messages chat
|
||||||
Integer savedIdxInActive = null;
|
Integer savedIdxInActive = null;
|
||||||
for (int i = 0; i < Session.activeChats.size(); i++) {
|
for (int i = 0; i < Session.activeChats.size(); i++) {
|
||||||
if (Session.activeChats.get(i).isSavedMessages()) {
|
if (Session.activeChats.get(i).isSavedMessages()) {
|
||||||
@@ -1248,23 +1319,19 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// مپ شمارهٔ نمایش → ایندکس واقعی در activeChats
|
|
||||||
Map<Integer, Integer> displayToActive = new HashMap<>();
|
Map<Integer, Integer> displayToActive = new HashMap<>();
|
||||||
|
|
||||||
int displayIndex = 1;
|
int displayIndex = 1;
|
||||||
|
|
||||||
// چاپ Saved (در صورت وجود) و ثبت در مپ
|
|
||||||
if (savedIdxInActive != null) {
|
if (savedIdxInActive != null) {
|
||||||
ChatEntry saved = Session.activeChats.get(savedIdxInActive);
|
ChatEntry saved = Session.activeChats.get(savedIdxInActive);
|
||||||
String last = (saved.getLastMessageTime() == null) ? "No messages yet" : saved.getLastMessageTime().toString();
|
String last = (saved.getLastMessageTime() == null) ? "No messages yet" : saved.getLastMessageTime().toString();
|
||||||
System.out.println(displayIndex + ". 💾 Saved Messages - Last: " + last);
|
System.out.println(displayIndex + ". 💾 Saved Messages - Last: " + last);
|
||||||
|
|
||||||
// نکتهٔ کلیدی: مپ کن تا مثل بقیه با openChat باز شود
|
|
||||||
displayToActive.put(displayIndex, savedIdxInActive);
|
displayToActive.put(displayIndex, savedIdxInActive);
|
||||||
displayIndex++;
|
displayIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// چاپ بقیهٔ چتها + ثبت مپ
|
|
||||||
for (int i = 0; i < Session.activeChats.size(); i++) {
|
for (int i = 0; i < Session.activeChats.size(); i++) {
|
||||||
if (savedIdxInActive != null && i == savedIdxInActive) continue;
|
if (savedIdxInActive != null && i == savedIdxInActive) continue;
|
||||||
|
|
||||||
@@ -1276,7 +1343,6 @@ public class ActionHandler {
|
|||||||
displayIndex++;
|
displayIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// انتخاب
|
|
||||||
System.out.print("Select a chat by number: ");
|
System.out.print("Select a chat by number: ");
|
||||||
int choice;
|
int choice;
|
||||||
try {
|
try {
|
||||||
@@ -1298,11 +1364,11 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChatEntry selected = Session.activeChats.get(activeIdx);
|
ChatEntry selected = Session.activeChats.get(activeIdx);
|
||||||
openChat(selected); // برای Saved هم همین مسیر اجرا میشود
|
openChat(selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void showArchivedChats() {
|
public void showArchivedChats() {
|
||||||
if (Session.archivedChats == null || Session.archivedChats.isEmpty()) {
|
if (Session.archivedChats == null || Session.archivedChats.isEmpty()) {
|
||||||
System.out.println("📭 No archived chats.");
|
System.out.println("📭 No archived chats.");
|
||||||
return;
|
return;
|
||||||
@@ -1340,7 +1406,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void openChat(ChatEntry chat) {
|
public void openChat(ChatEntry chat) {
|
||||||
|
|
||||||
//for private chats only
|
//for private chats only
|
||||||
if (chat.getType().equalsIgnoreCase("private")) {
|
if (chat.getType().equalsIgnoreCase("private")) {
|
||||||
@@ -1573,7 +1639,7 @@ public class ActionHandler {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
private boolean showPrivateChatMenu(ChatEntry chat) {
|
public boolean showPrivateChatMenu(ChatEntry chat) {
|
||||||
|
|
||||||
if (forceExitChat) {
|
if (forceExitChat) {
|
||||||
forceExitChat = false;
|
forceExitChat = false;
|
||||||
@@ -1677,7 +1743,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JSONObject getGroupPermissions(UUID groupId) {
|
public JSONObject getGroupPermissions(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_group_permissions");
|
req.put("action", "get_group_permissions");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -1691,7 +1757,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private boolean showGroupChatMenu(ChatEntry chat) {
|
public boolean showGroupChatMenu(ChatEntry chat) {
|
||||||
if (forceExitChat) {
|
if (forceExitChat) {
|
||||||
forceExitChat = false;
|
forceExitChat = false;
|
||||||
System.out.println("🚪 Exiting chat due to real-time update.");
|
System.out.println("🚪 Exiting chat due to real-time update.");
|
||||||
@@ -1804,7 +1870,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private boolean showChannelChatMenu(ChatEntry chat) {
|
public boolean showChannelChatMenu(ChatEntry chat) {
|
||||||
if (forceExitChat) {
|
if (forceExitChat) {
|
||||||
forceExitChat = false;
|
forceExitChat = false;
|
||||||
System.out.println("🚪 Exiting chat due to real-time update.");
|
System.out.println("🚪 Exiting chat due to real-time update.");
|
||||||
@@ -1962,7 +2028,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void transferOwnershipAndLeave(UUID groupId) {
|
public void transferOwnershipAndLeave(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_group_admins");
|
req.put("action", "view_group_admins");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2042,7 +2108,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeMemberFromGroup(UUID groupId) {
|
public void removeMemberFromGroup(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_group_members");
|
req.put("action", "view_group_members");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2103,7 +2169,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void addAdminToChannel(UUID channelId) {
|
public void addAdminToChannel(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_subscribers");
|
req.put("action", "view_channel_subscribers");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2180,7 +2246,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void addAdminToGroup(UUID groupId) {
|
public void addAdminToGroup(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_group_members");
|
req.put("action", "view_group_members");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2242,7 +2308,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void viewGroupMembers(UUID groupId) {
|
public void viewGroupMembers(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_group_members");
|
req.put("action", "view_group_members");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2270,7 +2336,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void removeSubscriberFromChannel(UUID channelId) {
|
public void removeSubscriberFromChannel(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_subscribers");
|
req.put("action", "view_channel_subscribers");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2330,7 +2396,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void transferChannelOwnershipAndLeave(UUID channelId) {
|
public void transferChannelOwnershipAndLeave(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_admins");
|
req.put("action", "view_channel_admins");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2413,7 +2479,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void deleteChannel(UUID channelId) {
|
public void deleteChannel(UUID channelId) {
|
||||||
System.out.print("Are you sure you want to delete the channel? (yes/no): ");
|
System.out.print("Are you sure you want to delete the channel? (yes/no): ");
|
||||||
String confirm = scanner.nextLine().trim().toLowerCase();
|
String confirm = scanner.nextLine().trim().toLowerCase();
|
||||||
|
|
||||||
@@ -2436,7 +2502,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void deleteGroup(UUID groupId) {
|
public void deleteGroup(UUID groupId) {
|
||||||
System.out.print("Are you sure you want to delete the group? (yes/no): ");
|
System.out.print("Are you sure you want to delete the group? (yes/no): ");
|
||||||
String confirm = scanner.nextLine().trim().toLowerCase();
|
String confirm = scanner.nextLine().trim().toLowerCase();
|
||||||
|
|
||||||
@@ -2458,7 +2524,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteChat(UUID targetId, boolean both) {
|
public void deleteChat(UUID targetId, boolean both) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "delete_private_chat");
|
req.put("action", "delete_private_chat");
|
||||||
req.put("chat_id", targetId.toString());
|
req.put("chat_id", targetId.toString());
|
||||||
@@ -2475,7 +2541,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void toggleBlock(UUID userId) {
|
public void toggleBlock(UUID userId) {
|
||||||
|
|
||||||
|
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
@@ -2497,7 +2563,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void leaveChat(UUID id, String type) {
|
public void leaveChat(UUID id, String type) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "leave_chat");
|
req.put("action", "leave_chat");
|
||||||
req.put("user_id", Session.getUserUUID());
|
req.put("user_id", Session.getUserUUID());
|
||||||
@@ -2518,7 +2584,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeAdminFromGroup(UUID groupId) {
|
public void removeAdminFromGroup(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_group_admins");
|
req.put("action", "view_group_admins");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2578,7 +2644,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeAdminFromChannel(UUID channelId) {
|
public void removeAdminFromChannel(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_admins");
|
req.put("action", "view_channel_admins");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2642,7 +2708,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void addMemberToGroup(UUID groupId, UUID userId) {
|
public void addMemberToGroup(UUID groupId, UUID userId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "add_member_to_group");
|
req.put("action", "add_member_to_group");
|
||||||
req.put("group_id", groupId.toString());
|
req.put("group_id", groupId.toString());
|
||||||
@@ -2654,7 +2720,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSubscriberToChannel(UUID channelId, UUID targetUserId) {
|
public void addSubscriberToChannel(UUID channelId, UUID targetUserId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "add_subscriber_to_channel");
|
req.put("action", "add_subscriber_to_channel");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2670,7 +2736,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GroupInfo(UUID groupId){
|
public void GroupInfo(UUID groupId){
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", groupId.toString());
|
req.put("receiver_id", groupId.toString());
|
||||||
@@ -2699,7 +2765,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void editGroupInfo(UUID groupId) {
|
public void editGroupInfo(UUID groupId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", groupId.toString());
|
req.put("receiver_id", groupId.toString());
|
||||||
@@ -2775,7 +2841,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void viewChannelSubscribers(UUID channelId) {
|
public void viewChannelSubscribers(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_subscribers");
|
req.put("action", "view_channel_subscribers");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -2797,7 +2863,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChannelInfo(UUID channelInternalId){
|
public void ChannelInfo(UUID channelInternalId){
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", channelInternalId.toString());
|
req.put("receiver_id", channelInternalId.toString());
|
||||||
@@ -2826,7 +2892,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void editChannelInfo(UUID channelInternalId) {
|
public void editChannelInfo(UUID channelInternalId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", channelInternalId.toString());
|
req.put("receiver_id", channelInternalId.toString());
|
||||||
@@ -2901,7 +2967,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JSONObject getResponse() {
|
public JSONObject getResponse() {
|
||||||
try {
|
try {
|
||||||
return TelegramClient.responseQueue.take();
|
return TelegramClient.responseQueue.take();
|
||||||
|
|
||||||
@@ -2931,7 +2997,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void editAdminPermissions(UUID chatId, String chatType) {
|
public void editAdminPermissions(UUID chatId, String chatType) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", chatType.equals("group") ? "view_group_admins" : "view_channel_admins");
|
req.put("action", chatType.equals("group") ? "view_group_admins" : "view_channel_admins");
|
||||||
req.put(chatType + "_id", chatId.toString());
|
req.put(chatType + "_id", chatId.toString());
|
||||||
@@ -3028,7 +3094,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void viewChannelAdmins(UUID channelId) {
|
public void viewChannelAdmins(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_channel_admins");
|
req.put("action", "view_channel_admins");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -3053,7 +3119,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JSONObject getChannelPermissions(UUID channelId) {
|
public JSONObject getChannelPermissions(UUID channelId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_channel_permissions");
|
req.put("action", "get_channel_permissions");
|
||||||
req.put("channel_id", channelId.toString());
|
req.put("channel_id", channelId.toString());
|
||||||
@@ -3353,7 +3419,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void startMenuRefresherThread() {
|
public void startMenuRefresherThread() {
|
||||||
Thread refresher = new Thread(() -> {
|
Thread refresher = new Thread(() -> {
|
||||||
System.out.println("🟡 Refresher tick. refreshCurrentChatMenu = " + Session.refreshCurrentChatMenu);
|
System.out.println("🟡 Refresher tick. refreshCurrentChatMenu = " + Session.refreshCurrentChatMenu);
|
||||||
|
|
||||||
@@ -3382,7 +3448,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void openForeignChat(ChatEntry chat) {
|
public void openForeignChat(ChatEntry chat) {
|
||||||
System.out.println("🔍 Opening " + chat.getType() + " chat (not in your chat list)");
|
System.out.println("🔍 Opening " + chat.getType() + " chat (not in your chat list)");
|
||||||
|
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
@@ -3446,7 +3512,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void viewUserProfile(UUID userId) {
|
public void viewUserProfile(UUID userId) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "view_profile");
|
req.put("action", "view_profile");
|
||||||
req.put("target_id", userId.toString());
|
req.put("target_id", userId.toString());
|
||||||
@@ -3471,7 +3537,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void viewGroupOrChannelInfo(UUID id, String type) {
|
public void viewGroupOrChannelInfo(UUID id, String type) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_chat_info");
|
req.put("action", "get_chat_info");
|
||||||
req.put("receiver_id", id.toString());
|
req.put("receiver_id", id.toString());
|
||||||
@@ -3496,7 +3562,7 @@ public class ActionHandler {
|
|||||||
|
|
||||||
|
|
||||||
public void toggleArchive(UUID chatId, String chatType) {
|
public void toggleArchive(UUID chatId, String chatType) {
|
||||||
// پیدا کردن چت از Session.chatList
|
|
||||||
Optional<ChatEntry> optional = Session.chatList.stream()
|
Optional<ChatEntry> optional = Session.chatList.stream()
|
||||||
.filter(c -> c.getId().equals(chatId))
|
.filter(c -> c.getId().equals(chatId))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
@@ -3522,7 +3588,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void archiveChat(UUID chatId, String chatType) {
|
public void archiveChat(UUID chatId, String chatType) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "archive_chat");
|
req.put("action", "archive_chat");
|
||||||
req.put("chat_id", chatId.toString());
|
req.put("chat_id", chatId.toString());
|
||||||
@@ -3531,7 +3597,7 @@ public class ActionHandler {
|
|||||||
if (res != null) System.out.println(res.getString("message"));
|
if (res != null) System.out.println(res.getString("message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unarchiveChat(UUID chatId, String chatType) {
|
public void unarchiveChat(UUID chatId, String chatType) {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "unarchive_chat");
|
req.put("action", "unarchive_chat");
|
||||||
req.put("chat_id", chatId.toString());
|
req.put("chat_id", chatId.toString());
|
||||||
@@ -3663,7 +3729,6 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 فقط ارسال پیام با chat_id و receiver_type
|
|
||||||
JSONObject messageJson = new JSONObject();
|
JSONObject messageJson = new JSONObject();
|
||||||
messageJson.put("action", "send_message");
|
messageJson.put("action", "send_message");
|
||||||
messageJson.put("receiver_type", receiverType);
|
messageJson.put("receiver_type", receiverType);
|
||||||
@@ -3683,7 +3748,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshContactList() {
|
public void refreshContactList() {
|
||||||
JSONObject req = new JSONObject();
|
JSONObject req = new JSONObject();
|
||||||
req.put("action", "get_contact_list");
|
req.put("action", "get_contact_list");
|
||||||
req.put("user_id", Session.currentUser.getString("user_id"));
|
req.put("user_id", Session.currentUser.getString("user_id"));
|
||||||
@@ -3741,7 +3806,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void viewMessagesInChat(ChatEntry chat) {
|
public void viewMessagesInChat(ChatEntry chat) {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
int limit = 10;
|
int limit = 10;
|
||||||
|
|
||||||
@@ -3910,7 +3975,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void editMessage(UUID messageId) {
|
public void editMessage(UUID messageId) {
|
||||||
System.out.print("📝 Enter new content: ");
|
System.out.print("📝 Enter new content: ");
|
||||||
String newContent = scanner.nextLine().trim();
|
String newContent = scanner.nextLine().trim();
|
||||||
|
|
||||||
@@ -3934,7 +3999,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void reactToMessage(UUID messageId) {
|
public void reactToMessage(UUID messageId) {
|
||||||
System.out.print("😊 Enter your reaction (e.g., ❤️, 👍, 😂): ");
|
System.out.print("😊 Enter your reaction (e.g., ❤️, 👍, 😂): ");
|
||||||
String reaction = scanner.nextLine().trim();
|
String reaction = scanner.nextLine().trim();
|
||||||
|
|
||||||
@@ -3957,7 +4022,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void forwardMessage(UUID originalMessageId) {
|
public void forwardMessage(UUID originalMessageId) {
|
||||||
System.out.println("\n📤 Select a chat to forward this message to:");
|
System.out.println("\n📤 Select a chat to forward this message to:");
|
||||||
|
|
||||||
List<ChatEntry> chatList = Session.chatList;
|
List<ChatEntry> chatList = Session.chatList;
|
||||||
@@ -4001,7 +4066,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void replyToMessage(UUID messageId) {
|
public void replyToMessage(UUID messageId) {
|
||||||
System.out.print("💬 Enter your reply: ");
|
System.out.print("💬 Enter your reply: ");
|
||||||
String content = scanner.nextLine().trim();
|
String content = scanner.nextLine().trim();
|
||||||
|
|
||||||
@@ -4030,7 +4095,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void deleteMessage(UUID messageId) {
|
public void deleteMessage(UUID messageId) {
|
||||||
System.out.println("\n🗑️ Delete Message Options:");
|
System.out.println("\n🗑️ Delete Message Options:");
|
||||||
System.out.println("1. Delete for yourself (one-sided)");
|
System.out.println("1. Delete for yourself (one-sided)");
|
||||||
System.out.println("2. Delete for everyone (global) [only if allowed]");
|
System.out.println("2. Delete for everyone (global) [only if allowed]");
|
||||||
@@ -4078,7 +4143,7 @@ public class ActionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSidebarMenu() {
|
public void showSidebarMenu() {
|
||||||
System.out.println("\n--- Sidebar Menu ---");
|
System.out.println("\n--- Sidebar Menu ---");
|
||||||
System.out.println("1. View Profile");
|
System.out.println("1. View Profile");
|
||||||
System.out.println("2. New Group");
|
System.out.println("2. New Group");
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.to.telegramfinalproject.Client;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
public final class AvatarLocalResolver {
|
||||||
|
private static final Path UPLOADS_ROOT = Paths.get(
|
||||||
|
System.getProperty("app.uploads.root", "uploads")
|
||||||
|
).toAbsolutePath().normalize();
|
||||||
|
|
||||||
|
private static boolean isHttp(String s) {
|
||||||
|
return s.startsWith("http://") || s.startsWith("https://");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolve(String serverValue) {
|
||||||
|
if (serverValue == null || serverValue.isBlank()) return null;
|
||||||
|
if (isHttp(serverValue) || serverValue.startsWith("file:")) return serverValue;
|
||||||
|
|
||||||
|
String rel = serverValue.startsWith("/") ? serverValue.substring(1) : serverValue; // "avatars/..."
|
||||||
|
Path p = UPLOADS_ROOT.resolve(rel).normalize();
|
||||||
|
if (!p.startsWith(UPLOADS_ROOT)) return null; // امنیت مسیر
|
||||||
|
if (!Files.exists(p)) { // مهم: واقعاً وجود دارد؟
|
||||||
|
System.err.println("Avatar resolve: NOT FOUND -> " + p);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String url = p.toUri().toString(); // file:///.../uploads/avatars/....
|
||||||
|
System.out.println("Avatar resolve: " + serverValue + " -> " + url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Image load(String serverValue) {
|
||||||
|
String url = resolve(serverValue);
|
||||||
|
if (url == null) return null;
|
||||||
|
|
||||||
|
// فقط برای http/https کشبریکر
|
||||||
|
if (isHttp(url)) {
|
||||||
|
url += (url.contains("?") ? "&" : "?") + "v=" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
Image img = new Image(url, false); // sync load تا خطا را همانجا بفهمیم
|
||||||
|
if (img.isError()) {
|
||||||
|
System.err.println("Avatar load failed: " + url + " -> " + img.getException());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,36 @@
|
|||||||
package org.to.telegramfinalproject.Client;
|
package org.to.telegramfinalproject.Client;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
import org.to.telegramfinalproject.UI.MainController;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
public class IncomingMessageListener implements Runnable {
|
public class IncomingMessageListener implements Runnable {
|
||||||
private final BufferedReader in;
|
private final BufferedReader in;
|
||||||
|
public enum UIMode { CONSOLE, UI }
|
||||||
|
private final UIMode uiMode; // runtime mode
|
||||||
|
|
||||||
public IncomingMessageListener(BufferedReader in) {
|
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.in = in;
|
||||||
|
this.uiMode = uiMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// public IncomingMessageListener(BufferedReader in) {
|
||||||
|
// this.in = in;
|
||||||
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
@@ -79,7 +94,7 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
"update_group_or_channel", "chat_deleted",
|
"update_group_or_channel", "chat_deleted",
|
||||||
"blocked_by_user", "unblocked_by_user", "message_seen",
|
"blocked_by_user", "unblocked_by_user", "message_seen",
|
||||||
"removed_from_group", "removed_from_channel",
|
"removed_from_group", "removed_from_channel",
|
||||||
"became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated","created_private_chat" , "message_reacted" , "message_unreacted" -> true;
|
"became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated","created_private_chat" , "message_reacted" , "message_unreacted","chat_updated" -> true;
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -88,13 +103,15 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
String action = response.getString("action");
|
String action = response.getString("action");
|
||||||
JSONObject msg = response.has("data") ? response.getJSONObject("data") : new JSONObject();
|
JSONObject msg = response.has("data") ? response.getJSONObject("data") : new JSONObject();
|
||||||
|
|
||||||
|
JSONObject finalMsg = msg;
|
||||||
|
JSONObject finalMsg1 = msg;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "added_to_group", "added_to_channel",
|
case "added_to_group", "added_to_channel",
|
||||||
"removed_from_group", "removed_from_channel", "chat_deleted","created_private_chat" -> {
|
"removed_from_group", "removed_from_channel",
|
||||||
|
"chat_deleted", "created_private_chat" -> {
|
||||||
|
// این قسمت مستقل از UI/کنسول است
|
||||||
System.out.println("🔄 Chat list changed. Updating...");
|
System.out.println("🔄 Chat list changed. Updating...");
|
||||||
Session.forceRefreshChatList = true;
|
Session.forceRefreshChatList = true;
|
||||||
System.out.println("🧪 Calling requestChatList() after being added");
|
|
||||||
|
|
||||||
String chatId = msg.getString("chat_id");
|
String chatId = msg.getString("chat_id");
|
||||||
String chatType = msg.getString("chat_type");
|
String chatType = msg.getString("chat_type");
|
||||||
@@ -106,36 +123,81 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "chat_updated" -> {
|
// case "chat_updated" -> {
|
||||||
System.out.println("\n🔄 Chat info updated.");
|
// if (uiMode == UIMode.UI) {
|
||||||
|
// bumpChatListFromUpdate(msg); // برای UI (سایدبار و سورت)
|
||||||
if (msg.has("last_message_time")) {
|
// } else {
|
||||||
updateLastMessageTime(msg);
|
// updateLastMessageTime(msg); // برای کنسول (لیستهای Session)
|
||||||
} else {
|
// }
|
||||||
new Thread(() -> {
|
// }
|
||||||
try {
|
|
||||||
handleAdminRoleChanged(msg);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case "became_admin", "removed_admin", "ownership_transferred", "admin_permissions_updated" -> {
|
case "became_admin", "removed_admin", "ownership_transferred", "admin_permissions_updated" -> {
|
||||||
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
|
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
handleAdminRoleChanged(msg); //new thread
|
handleAdminRoleChanged(finalMsg1);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "new_message" -> {
|
||||||
|
JSONObject data = response.optJSONObject("data");
|
||||||
|
if (data == null) break;
|
||||||
|
|
||||||
default -> displayRealTimeMessage(action, msg);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case "chat_updated" -> {
|
||||||
|
var data = response.getJSONObject("data");
|
||||||
|
bumpChatListFromUpdate(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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","")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
case "message_edited", "message_deleted_global", "message_reacted", "message_unreacted"
|
||||||
|
, "blocked_by_user", "unblocked_by_user", "message_seen" -> {
|
||||||
|
displayRealTimeMessage(action, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
default -> {
|
||||||
|
System.out.println("\n❓ Unknown real-time action: " + action);
|
||||||
|
System.out.println(msg.toString(2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.print(">> ");
|
System.out.print(">> ");
|
||||||
@@ -362,4 +424,58 @@ public class IncomingMessageListener implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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()); }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,129 @@
|
|||||||
|
//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;
|
package org.to.telegramfinalproject.Client;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -7,6 +133,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -17,46 +144,112 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
public class TelegramClient {
|
public class TelegramClient {
|
||||||
private static final String SERVER_HOST = "localhost";
|
private static final String SERVER_HOST = "localhost";
|
||||||
private static final int SERVER_PORT = 8000;
|
private static final int SERVER_PORT = 8000;
|
||||||
|
|
||||||
|
private static TelegramClient instance;
|
||||||
|
|
||||||
private static Socket socket;
|
private static Socket socket;
|
||||||
private BufferedReader in;
|
private BufferedReader in;
|
||||||
private PrintWriter out;
|
private PrintWriter out;
|
||||||
|
|
||||||
|
|
||||||
private final Scanner scanner;
|
private final Scanner scanner;
|
||||||
private ActionHandler handler;
|
private ActionHandler handler;
|
||||||
public static BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>();
|
|
||||||
public static UUID loggedInUserId = null;
|
public static final BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>();
|
||||||
public static final Map<String, BlockingQueue<JSONObject>> pendingResponses = new ConcurrentHashMap<>();
|
public static final Map<String, BlockingQueue<JSONObject>> pendingResponses = new ConcurrentHashMap<>();
|
||||||
|
public static UUID loggedInUserId = null;
|
||||||
|
|
||||||
|
private volatile boolean listenerStarted = false;
|
||||||
private static TelegramClient instance;
|
|
||||||
|
|
||||||
public TelegramClient() {
|
public TelegramClient() {
|
||||||
this.scanner = new Scanner(System.in);
|
this.scanner = new Scanner(System.in);
|
||||||
instance = this;
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TelegramClient getInstance() {
|
public static synchronized TelegramClient getInstance() {
|
||||||
|
if (instance == null) instance = new TelegramClient();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
// public void startConsole() {
|
||||||
|
// try {
|
||||||
|
// connectIfNeeded();
|
||||||
|
// initHandlerIfNeeded();
|
||||||
|
// startListenerOnce();
|
||||||
|
// showMainMenu();
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// System.err.println("❌ Error connecting to server: " + e.getMessage());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void startConsole() {
|
||||||
try {
|
try {
|
||||||
socket = new Socket(SERVER_HOST, SERVER_PORT);
|
connectIfNeeded();
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
initHandlerIfNeeded();
|
||||||
out = new PrintWriter(socket.getOutputStream(), true);
|
startListenerOnce(IncomingMessageListener.UIMode.CONSOLE); // ← کنسول
|
||||||
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();
|
showMainMenu();
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("❌ Error connecting to server: " + e.getMessage());
|
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);
|
||||||
|
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
out = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
System.out.println("✅ Connected to Telegram Server");
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void initHandlerIfNeeded() {
|
||||||
|
if (handler == null) {
|
||||||
|
handler = new ActionHandler(out, in, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
//console
|
||||||
private void showMainMenu() throws IOException {
|
private void showMainMenu() throws IOException {
|
||||||
while (true) {
|
while (true) {
|
||||||
System.out.println("Main Menu:");
|
System.out.println("Main Menu:");
|
||||||
@@ -73,8 +266,6 @@ public class TelegramClient {
|
|||||||
if (Session.currentUser != null) {
|
if (Session.currentUser != null) {
|
||||||
System.out.println("✅ Login successful.");
|
System.out.println("✅ Login successful.");
|
||||||
new Thread(new ActionHandler.ChatStateMonitor(out)).start();
|
new Thread(new ActionHandler.ChatStateMonitor(out)).start();
|
||||||
// new Thread(new ActionHandler.CurrentChatMenuRefresher(this.handler)).start();
|
|
||||||
|
|
||||||
|
|
||||||
UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||||
loggedInUserId = internalId;
|
loggedInUserId = internalId;
|
||||||
@@ -95,28 +286,29 @@ public class TelegramClient {
|
|||||||
|
|
||||||
public static void send(JSONObject req) {
|
public static void send(JSONObject req) {
|
||||||
try {
|
try {
|
||||||
responseQueue.clear(); // optional: clear old responses
|
responseQueue.clear();
|
||||||
getInstance().out.println(req.toString());
|
getInstance().out.println(req.toString());
|
||||||
System.out.println("📤 [SEND] " + req.toString(2));
|
System.out.println("📤 [SEND] " + req.toString(2));
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("❌ Error sending request: " + e.getMessage());
|
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() {
|
public static Socket getSocket() { return socket; }
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
new TelegramClient().start();
|
new TelegramClient().startConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrintWriter getOut() {
|
// TelegramClient.java
|
||||||
return out;
|
private IncomingMessageListener listener;
|
||||||
|
|
||||||
|
public IncomingMessageListener getListener() {
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -381,72 +381,177 @@ public class MessageDatabase {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public static List<Message> getUnreadMessages(UUID userId) {
|
||||||
|
// List<Message> messages = new ArrayList<>();
|
||||||
|
//
|
||||||
|
// String sql = """
|
||||||
|
// SELECT m.*
|
||||||
|
// FROM messages m
|
||||||
|
// LEFT JOIN message_receipts r ON m.message_id = r.message_id AND r.user_id = ?
|
||||||
|
// LEFT JOIN deleted_messages d ON m.message_id = d.message_id AND d.user_id = ?
|
||||||
|
// WHERE r.user_id IS NULL
|
||||||
|
// AND d.message_id IS NULL
|
||||||
|
// AND m.is_deleted_globally = FALSE
|
||||||
|
// AND (
|
||||||
|
// (m.receiver_type = 'private' AND m.receiver_id = ?)
|
||||||
|
// OR
|
||||||
|
// (m.receiver_type = 'group' AND EXISTS (
|
||||||
|
// SELECT 1 FROM group_members gm WHERE gm.group_id = m.receiver_id AND gm.user_id = ?
|
||||||
|
// ))
|
||||||
|
// OR
|
||||||
|
// (m.receiver_type = 'channel' AND EXISTS (
|
||||||
|
// SELECT 1 FROM channel_subscribers cs WHERE cs.channel_id = m.receiver_id AND cs.user_id = ?
|
||||||
|
// ))
|
||||||
|
// )
|
||||||
|
// ORDER BY m.send_at DESC
|
||||||
|
// """;
|
||||||
|
//
|
||||||
|
// try (Connection conn = ConnectionDb.connect();
|
||||||
|
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||||
|
//
|
||||||
|
// stmt.setObject(1, userId); // for message_receipts
|
||||||
|
// stmt.setObject(2, userId); // for deleted_messages
|
||||||
|
// stmt.setObject(3, userId); // for private messages
|
||||||
|
// stmt.setObject(4, userId); // for group members
|
||||||
|
// stmt.setObject(5, userId); // for channel subscribers
|
||||||
|
//
|
||||||
|
// ResultSet rs = stmt.executeQuery();
|
||||||
|
// while (rs.next()) {
|
||||||
|
// Message message = new Message(
|
||||||
|
// UUID.fromString(rs.getString("message_id")),
|
||||||
|
// rs.getObject("sender_id") != null ? UUID.fromString(rs.getString("sender_id")) : null,
|
||||||
|
// rs.getString("receiver_type"),
|
||||||
|
// UUID.fromString(rs.getString("receiver_id")),
|
||||||
|
// rs.getString("content"),
|
||||||
|
// rs.getString("message_type"),
|
||||||
|
// rs.getTimestamp("send_at").toLocalDateTime(),
|
||||||
|
// rs.getString("status"),
|
||||||
|
// rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
||||||
|
// rs.getBoolean("is_edited"),
|
||||||
|
// rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
||||||
|
// rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
||||||
|
// rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null,
|
||||||
|
// rs.getBoolean("is_deleted_globally"),
|
||||||
|
// rs.getTimestamp("edited_at") != null ? rs.getTimestamp("edited_at").toLocalDateTime() : null
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// messages.add(message);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// } catch (SQLException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return messages;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static List<Message> getUnreadMessages(UUID userId) {
|
public static List<Message> getUnreadMessages(UUID userId) {
|
||||||
|
return getUnreadMessages(userId, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Message> getUnreadMessages(UUID userId, int limit) {
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
|
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT m.*
|
SELECT m.*
|
||||||
FROM messages m
|
FROM messages m
|
||||||
LEFT JOIN message_receipts r ON m.message_id = r.message_id AND r.user_id = ?
|
LEFT JOIN message_receipts r
|
||||||
LEFT JOIN deleted_messages d ON m.message_id = d.message_id AND d.user_id = ?
|
ON r.message_id = m.message_id AND r.user_id = ?
|
||||||
WHERE r.user_id IS NULL
|
LEFT JOIN deleted_messages d
|
||||||
|
ON d.message_id = m.message_id AND d.user_id = ?
|
||||||
|
WHERE r.message_id IS NULL
|
||||||
AND d.message_id IS NULL
|
AND d.message_id IS NULL
|
||||||
AND m.is_deleted_globally = FALSE
|
AND m.is_deleted_globally = FALSE
|
||||||
|
AND m.sender_id <> ?
|
||||||
AND (
|
AND (
|
||||||
(m.receiver_type = 'private' AND m.receiver_id = ?)
|
(m.receiver_type = 'private' AND EXISTS (
|
||||||
OR
|
SELECT 1
|
||||||
(m.receiver_type = 'group' AND EXISTS (
|
FROM private_chat pc
|
||||||
SELECT 1 FROM group_members gm WHERE gm.group_id = m.receiver_id AND gm.user_id = ?
|
WHERE pc.chat_id = m.receiver_id
|
||||||
|
AND (pc.user1_id = ? OR pc.user2_id = ?)
|
||||||
))
|
))
|
||||||
OR
|
OR (m.receiver_type = 'group' AND EXISTS (
|
||||||
(m.receiver_type = 'channel' AND EXISTS (
|
SELECT 1
|
||||||
SELECT 1 FROM channel_subscribers cs WHERE cs.channel_id = m.receiver_id AND cs.user_id = ?
|
FROM group_members gm
|
||||||
|
WHERE gm.group_id = m.receiver_id
|
||||||
|
AND gm.user_id = ?
|
||||||
|
))
|
||||||
|
OR (m.receiver_type = 'channel' AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM channel_subscribers cs
|
||||||
|
WHERE cs.channel_id = m.receiver_id
|
||||||
|
AND cs.user_id = ?
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
ORDER BY m.send_at DESC
|
ORDER BY m.send_at ASC
|
||||||
|
LIMIT ?
|
||||||
""";
|
""";
|
||||||
|
|
||||||
try (Connection conn = ConnectionDb.connect();
|
try (Connection conn = ConnectionDb.connect();
|
||||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
|
||||||
stmt.setObject(1, userId); // for message_receipts
|
int i = 1;
|
||||||
stmt.setObject(2, userId); // for deleted_messages
|
ps.setObject(i++, userId); // r.user_id
|
||||||
stmt.setObject(3, userId); // for private messages
|
ps.setObject(i++, userId); // d.user_id
|
||||||
stmt.setObject(4, userId); // for group members
|
ps.setObject(i++, userId); // m.sender_id <> ?
|
||||||
stmt.setObject(5, userId); // for channel subscribers
|
ps.setObject(i++, userId); // pc.user1_id
|
||||||
|
ps.setObject(i++, userId); // pc.user2_id
|
||||||
|
ps.setObject(i++, userId); // gm.user_id
|
||||||
|
ps.setObject(i++, userId); // cs.user_id
|
||||||
|
ps.setInt(i, Math.max(1, limit));
|
||||||
|
|
||||||
ResultSet rs = stmt.executeQuery();
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
while (rs.next()) {
|
while (rs.next()) messages.add(mapMessage(rs));
|
||||||
Message message = new Message(
|
|
||||||
UUID.fromString(rs.getString("message_id")),
|
|
||||||
rs.getObject("sender_id") != null ? UUID.fromString(rs.getString("sender_id")) : null,
|
|
||||||
rs.getString("receiver_type"),
|
|
||||||
UUID.fromString(rs.getString("receiver_id")),
|
|
||||||
rs.getString("content"),
|
|
||||||
rs.getString("message_type"),
|
|
||||||
rs.getTimestamp("send_at").toLocalDateTime(),
|
|
||||||
rs.getString("status"),
|
|
||||||
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
|
||||||
rs.getBoolean("is_edited"),
|
|
||||||
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
|
||||||
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
|
||||||
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null,
|
|
||||||
rs.getBoolean("is_deleted_globally"),
|
|
||||||
rs.getTimestamp("edited_at") != null ? rs.getTimestamp("edited_at").toLocalDateTime() : null
|
|
||||||
);
|
|
||||||
|
|
||||||
messages.add(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static java.util.UUID readUUID(ResultSet rs, String col) throws SQLException {
|
||||||
|
Object o = rs.getObject(col);
|
||||||
|
if (o == null) return null;
|
||||||
|
if (o instanceof java.util.UUID) return (java.util.UUID) o;
|
||||||
|
return java.util.UUID.fromString(o.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static java.time.LocalDateTime readLdt(ResultSet rs, String col) throws SQLException {
|
||||||
|
java.sql.Timestamp ts = rs.getTimestamp(col);
|
||||||
|
return (ts == null) ? null : ts.toLocalDateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean readBool(ResultSet rs, String col) throws SQLException {
|
||||||
|
boolean v = rs.getBoolean(col);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message mapMessage(ResultSet rs) throws SQLException {
|
||||||
|
Message m = new Message();
|
||||||
|
|
||||||
|
m.setMessage_id( readUUID(rs, "message_id") );
|
||||||
|
m.setSender_id( readUUID(rs, "sender_id") );
|
||||||
|
m.setReceiver_type( rs.getString("receiver_type") );
|
||||||
|
m.setReceiver_id( readUUID(rs, "receiver_id") );
|
||||||
|
m.setContent( rs.getString("content") );
|
||||||
|
m.setMessage_type( rs.getString("message_type") );
|
||||||
|
m.setSend_at( readLdt(rs, "send_at") );
|
||||||
|
m.setStatus( rs.getString("status") );
|
||||||
|
m.setReply_to_id( readUUID(rs, "reply_to_id") );
|
||||||
|
m.setIs_edited( readBool(rs, "is_edited") );
|
||||||
|
m.setEdited_at( readLdt(rs, "edited_at") );
|
||||||
|
m.setOriginal_message_id( readUUID(rs, "original_message_id") );
|
||||||
|
m.setForwarded_by( readUUID(rs, "forwarded_by") );
|
||||||
|
m.setForwarded_from( readUUID(rs, "forwarded_from") );
|
||||||
|
m.setIs_deleted_globally( readBool(rs, "is_deleted_globally") );
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
public static List<FileAttachment> getAttachments(UUID messageId) {
|
public static List<FileAttachment> getAttachments(UUID messageId) {
|
||||||
List<FileAttachment> attachments = new ArrayList<>();
|
List<FileAttachment> attachments = new ArrayList<>();
|
||||||
String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?";
|
String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?";
|
||||||
@@ -1057,4 +1162,181 @@ public class MessageDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Message getLastMessage(UUID targetId, String type) {
|
||||||
|
final String sql =
|
||||||
|
"SELECT * FROM messages " +
|
||||||
|
"WHERE receiver_type = ? AND receiver_id = ? " +
|
||||||
|
"ORDER BY send_at DESC LIMIT 1";
|
||||||
|
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
|
||||||
|
ps.setString(1, type.toLowerCase()); // "private" | "group" | "channel"
|
||||||
|
ps.setObject(2, targetId);
|
||||||
|
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? mapRow(rs) : null;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static int getUnreadCount(UUID me, UUID targetId, String type) {
|
||||||
|
// final String sql =
|
||||||
|
// "SELECT COUNT(*) FROM messages " +
|
||||||
|
// "WHERE receiver_type = ? AND receiver_id = ? " +
|
||||||
|
// "AND sender_id <> ? " +
|
||||||
|
// "AND (status IS NULL OR status <> 'SEEN')";
|
||||||
|
//
|
||||||
|
// try (Connection conn = ConnectionDb.connect();
|
||||||
|
// PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
//
|
||||||
|
// ps.setString(1, type.toLowerCase());
|
||||||
|
// ps.setObject(2, targetId);
|
||||||
|
// ps.setObject(3, me);
|
||||||
|
//
|
||||||
|
// try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
// return rs.next() ? rs.getInt(1) : 0;
|
||||||
|
// }
|
||||||
|
// } catch (SQLException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static Message mapRow(ResultSet rs) throws SQLException {
|
||||||
|
Message m = new Message();
|
||||||
|
m.setMessage_id((UUID) rs.getObject("message_id"));
|
||||||
|
m.setSender_id((UUID) rs.getObject("sender_id"));
|
||||||
|
m.setReceiver_id((UUID) rs.getObject("receiver_id"));
|
||||||
|
m.setReceiver_type(rs.getString("receiver_type"));
|
||||||
|
if ("private".equalsIgnoreCase(m.getReceiver_type())) {
|
||||||
|
m.setReceiver_id(m.getReceiver_id());
|
||||||
|
} else {
|
||||||
|
m.setReceiver_id(null);
|
||||||
|
}
|
||||||
|
m.setMessage_type(rs.getString("message_type"));
|
||||||
|
m.setContent(rs.getString("content"));
|
||||||
|
Timestamp ts = rs.getTimestamp("send_at");
|
||||||
|
if (ts != null) m.setSend_at(ts.toLocalDateTime());
|
||||||
|
m.setIs_edited(rs.getBoolean("is_edited"));
|
||||||
|
m.setStatus(rs.getString("status"));
|
||||||
|
Object rtid = rs.getObject("reply_to_id");
|
||||||
|
m.setReply_to_id(rtid == null ? null : (UUID) rtid);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static List<UUID> getUnreadMessageIds(UUID me, UUID chatId, String chatType, int limit) {
|
||||||
|
String sql = """
|
||||||
|
SELECT m.message_id
|
||||||
|
FROM messages m
|
||||||
|
WHERE m.receiver_id = ? AND m.receiver_type = ?
|
||||||
|
AND m.sender_id <> ?
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM message_receipts r
|
||||||
|
WHERE r.message_id = m.message_id
|
||||||
|
AND r.user_id = ?
|
||||||
|
)
|
||||||
|
ORDER BY m.send_at ASC
|
||||||
|
LIMIT ?
|
||||||
|
""";
|
||||||
|
List<UUID> ids = new ArrayList<>();
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setObject(1, chatId);
|
||||||
|
ps.setString(2, chatType);
|
||||||
|
ps.setObject(3, me);
|
||||||
|
ps.setObject(4, me);
|
||||||
|
ps.setInt(5, limit);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
ids.add((UUID) rs.getObject(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// تعداد ناخواندهها (برای چتلیست)
|
||||||
|
public static int getUnreadCount(UUID me, UUID chatId, String chatType) {
|
||||||
|
String sql = """
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM messages m
|
||||||
|
WHERE m.receiver_id = ?
|
||||||
|
AND m.receiver_type = ?
|
||||||
|
AND m.sender_id <> ?
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM message_receipts r
|
||||||
|
WHERE r.message_id = m.message_id
|
||||||
|
AND r.user_id = ?
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setObject(1, chatId);
|
||||||
|
ps.setString(2, chatType);
|
||||||
|
ps.setObject(3, me);
|
||||||
|
ps.setObject(4, me);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
return rs.next() ? rs.getInt(1) : 0;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int insertReceiptIfAbsent(UUID messageId, UUID userId) {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO message_receipts(message_id, user_id, read_at)
|
||||||
|
VALUES (?, ?, now())
|
||||||
|
ON CONFLICT (message_id, user_id) DO NOTHING
|
||||||
|
""";
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setObject(1, messageId);
|
||||||
|
ps.setObject(2, userId);
|
||||||
|
return ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read status if it's first reader
|
||||||
|
public static int setMessageReadIfNeeded(UUID messageId, UUID viewerId) {
|
||||||
|
String sql = """
|
||||||
|
UPDATE messages m
|
||||||
|
SET status = 'READ'
|
||||||
|
WHERE m.message_id = ?
|
||||||
|
AND m.sender_id <> ?
|
||||||
|
AND m.status <> 'READ'
|
||||||
|
""";
|
||||||
|
try (Connection conn = ConnectionDb.connect();
|
||||||
|
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||||
|
ps.setObject(1, messageId);
|
||||||
|
ps.setObject(2, viewerId);
|
||||||
|
return ps.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ public class ChatEntry {
|
|||||||
private JSONObject permissions;
|
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) {
|
public ChatEntry(UUID internalId, String displayId, String name, String imageUrl, String type, LocalDateTime lastMessageTime) {
|
||||||
this.internalId = internalId;
|
this.internalId = internalId;
|
||||||
this.displayId = displayId;
|
this.displayId = displayId;
|
||||||
@@ -117,8 +128,6 @@ public class ChatEntry {
|
|||||||
this.archived = archived;
|
this.archived = archived;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public void setLastMessageTime(String newTime) {this.lastMessageTime = LocalDateTime.parse(newTime);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
public void setLastMessageTime(String newTime) {
|
public void setLastMessageTime(String newTime) {
|
||||||
@@ -149,4 +158,26 @@ public class ChatEntry {
|
|||||||
public void setSavedMessages(boolean savedMessages) {
|
public void setSavedMessages(boolean savedMessages) {
|
||||||
this.savedMessages = 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ public class JsonUtil {
|
|||||||
obj.put("is_owner", entry.isOwner());
|
obj.put("is_owner", entry.isOwner());
|
||||||
obj.put("is_admin", entry.isAdmin());
|
obj.put("is_admin", entry.isAdmin());
|
||||||
obj.put("is_saved_messages", entry.isSavedMessages());
|
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);
|
jsonArray.put(obj);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,9 @@ public class Message {
|
|||||||
this.edited_at = editedAt;
|
this.edited_at = editedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Message() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ✅ Getters & Setters
|
// ✅ Getters & Setters
|
||||||
@@ -203,4 +206,6 @@ public class Message {
|
|||||||
return is_deleted_globally;
|
return is_deleted_globally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void receiver_id(UUID receiverId) {this.receiver_id = receiverId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.to.telegramfinalproject.Utils.GroupPermissionUtil;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.sql.Connection;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -125,78 +126,6 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// for (Contact contact : contacts) {
|
|
||||||
// User target = userDatabase.findByInternalUUID(contact.getContact_id());
|
|
||||||
// if (target == null) continue;
|
|
||||||
// LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(user.getInternal_uuid(), target.getInternal_uuid(), "private");
|
|
||||||
//
|
|
||||||
//// chatList.add(new ChatEntry(
|
|
||||||
//// target.getInternal_uuid(), // internal UUID
|
|
||||||
//// target.getUser_id(), // public display ID
|
|
||||||
//// target.getProfile_name(),
|
|
||||||
//// target.getImage_url(),
|
|
||||||
//// "private",
|
|
||||||
//// last,
|
|
||||||
//// false,
|
|
||||||
//// false
|
|
||||||
//// ));
|
|
||||||
//
|
|
||||||
// UUID targetId = target.getInternal_uuid();
|
|
||||||
//
|
|
||||||
// ChatEntry entry = new ChatEntry(
|
|
||||||
// targetId,
|
|
||||||
// target.getUser_id(),
|
|
||||||
// target.getProfile_name(),
|
|
||||||
// target.getImage_url(),
|
|
||||||
// "private",
|
|
||||||
// last,
|
|
||||||
// false,
|
|
||||||
// false
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
|
|
||||||
// for (PrivateChat chat : privateChats) {
|
|
||||||
// UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ?
|
|
||||||
// chat.getUser2_id() : chat.getUser1_id();
|
|
||||||
//
|
|
||||||
// User otherUser = userDatabase.findByInternalUUID(otherId);
|
|
||||||
// if (otherUser == null) continue;
|
|
||||||
//
|
|
||||||
// LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private");
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// ChatEntry entry = new ChatEntry(
|
|
||||||
// chat.getChat_id(),
|
|
||||||
// otherUser.getUser_id(),
|
|
||||||
// otherUser.getProfile_name(),
|
|
||||||
// otherUser.getImage_url(),
|
|
||||||
// "private",
|
|
||||||
// lastMessageTime,
|
|
||||||
// false,
|
|
||||||
// false
|
|
||||||
// );
|
|
||||||
// entry.setOtherUserId(otherId);
|
|
||||||
//
|
|
||||||
// if (currentUser.getInternal_uuid() == otherId) {
|
|
||||||
// entry.setSavedMessages(true);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (archivedChatIds.contains(chat.getChat_id())) {
|
|
||||||
// archivedChatList.add(entry);
|
|
||||||
// chatList.add(entry);
|
|
||||||
// } else {
|
|
||||||
// activeChatList.add(entry);
|
|
||||||
// chatList.add(entry);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
|
List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
|
||||||
|
|
||||||
ChatEntry savedEntry = null;
|
ChatEntry savedEntry = null;
|
||||||
@@ -229,10 +158,12 @@ public class ClientHandler implements Runnable {
|
|||||||
);
|
);
|
||||||
entry.setOtherUserId(otherId);
|
entry.setOtherUserId(otherId);
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
entry.setSavedMessages(true); // فلگ مهم
|
entry.setSavedMessages(true);
|
||||||
savedEntry = entry; // برای بردن به اول لیست
|
savedEntry = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enrichChatEntry(entry, user.getInternal_uuid());
|
||||||
|
|
||||||
if (!isSelf && archivedChatIds.contains(chat.getChat_id())) {
|
if (!isSelf && archivedChatIds.contains(chat.getChat_id())) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
} else {
|
} else {
|
||||||
@@ -270,6 +201,7 @@ public class ClientHandler implements Runnable {
|
|||||||
isOwner,
|
isOwner,
|
||||||
isAdmin
|
isAdmin
|
||||||
);
|
);
|
||||||
|
enrichChatEntry(entry, user.getInternal_uuid());
|
||||||
|
|
||||||
if (archivedChatIds.contains(group.getInternal_uuid())) {
|
if (archivedChatIds.contains(group.getInternal_uuid())) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
@@ -309,6 +241,8 @@ public class ClientHandler implements Runnable {
|
|||||||
isAdmin
|
isAdmin
|
||||||
);
|
);
|
||||||
|
|
||||||
|
enrichChatEntry(entry, user.getInternal_uuid());
|
||||||
|
|
||||||
if (archivedChatIds.contains(channel.getInternal_uuid())) {
|
if (archivedChatIds.contains(channel.getInternal_uuid())) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
chatList.add(entry);
|
chatList.add(entry);
|
||||||
@@ -774,9 +708,9 @@ public class ClientHandler implements Runnable {
|
|||||||
|
|
||||||
ChatEntry entry = new ChatEntry(
|
ChatEntry entry = new ChatEntry(
|
||||||
chat.getChat_id(),
|
chat.getChat_id(),
|
||||||
otherUser.getUser_id(),
|
isSavedMessages ? "Saved Messages" : otherUser.getUser_id(),
|
||||||
otherUser.getProfile_name(),
|
isSavedMessages ? "Saved Messages" : otherUser.getProfile_name(),
|
||||||
otherUser.getImage_url(),
|
isSavedMessages ? null : otherUser.getImage_url(),
|
||||||
"private",
|
"private",
|
||||||
lastMessageTime,
|
lastMessageTime,
|
||||||
false,
|
false,
|
||||||
@@ -784,18 +718,20 @@ public class ClientHandler implements Runnable {
|
|||||||
);
|
);
|
||||||
entry.setOtherUserId(otherId);
|
entry.setOtherUserId(otherId);
|
||||||
|
|
||||||
|
|
||||||
// Mark it as saved messages if it's the special self-chat
|
// Mark it as saved messages if it's the special self-chat
|
||||||
if (isSavedMessages) {
|
if (isSavedMessages) {
|
||||||
entry.setSavedMessages(true);
|
entry.setSavedMessages(true);
|
||||||
}
|
}
|
||||||
|
enrichChatEntry(entry, currentUser.getInternal_uuid());
|
||||||
|
|
||||||
if (archivedChatIds.contains(chat.getChat_id())) {
|
if (archivedChatIds.contains(chat.getChat_id()) && !isSavedMessages) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
chatList.add(entry);
|
|
||||||
} else {
|
} else {
|
||||||
activeChatList.add(entry);
|
activeChatList.add(entry);
|
||||||
chatList.add(entry);
|
|
||||||
}
|
}
|
||||||
|
chatList.add(entry);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -826,6 +762,8 @@ public class ClientHandler implements Runnable {
|
|||||||
isAdmin
|
isAdmin
|
||||||
);
|
);
|
||||||
|
|
||||||
|
enrichChatEntry(entry, currentUser.getInternal_uuid());
|
||||||
|
|
||||||
if (archivedChatIds.contains(group.getInternal_uuid())) {
|
if (archivedChatIds.contains(group.getInternal_uuid())) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
chatList.add(entry);
|
chatList.add(entry);
|
||||||
@@ -863,6 +801,7 @@ public class ClientHandler implements Runnable {
|
|||||||
isOwner,
|
isOwner,
|
||||||
isAdmin
|
isAdmin
|
||||||
);
|
);
|
||||||
|
enrichChatEntry(entry, currentUser.getInternal_uuid());
|
||||||
|
|
||||||
if (archivedChatIds.contains(channel.getInternal_uuid())) {
|
if (archivedChatIds.contains(channel.getInternal_uuid())) {
|
||||||
archivedChatList.add(entry);
|
archivedChatList.add(entry);
|
||||||
@@ -1406,6 +1345,160 @@ public class ClientHandler implements Runnable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "get_messages_UI": {
|
||||||
|
if (currentUser == null) {
|
||||||
|
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String receiverId = requestJson.getString("receiver_id");
|
||||||
|
final String receiverType = requestJson.getString("receiver_type").toLowerCase();
|
||||||
|
final int offset = requestJson.optInt("offset", 0);
|
||||||
|
final int limit = requestJson.optInt("limit", 50);
|
||||||
|
|
||||||
|
List<Message> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
switch (receiverType) {
|
||||||
|
case "private" -> {
|
||||||
|
// receiver_id = private_chat.chat_id
|
||||||
|
UUID chatId = UUID.fromString(receiverId);
|
||||||
|
List<UUID> members = PrivateChatDatabase.getMembers(chatId);
|
||||||
|
if (members == null || !members.contains(currentUser.getInternal_uuid())) {
|
||||||
|
response = new ResponseModel("error", "You're not a member of this private chat.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
messages = MessageDatabase.privateChatHistory(chatId, currentUser.getInternal_uuid());
|
||||||
|
}
|
||||||
|
case "group" -> {
|
||||||
|
Group group = GroupDatabase.findByInternalUUID(UUID.fromString(receiverId));
|
||||||
|
if (group == null) {
|
||||||
|
response = new ResponseModel("error", "Group not found.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
messages = MessageDatabase.groupChatHistory(group.getInternal_uuid(), currentUser.getInternal_uuid());
|
||||||
|
}
|
||||||
|
case "channel" -> {
|
||||||
|
Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(receiverId));
|
||||||
|
if (channel == null) {
|
||||||
|
response = new ResponseModel("error", "Channel not found.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid(), currentUser.getInternal_uuid());
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
response = new ResponseModel("error", "Invalid receiver type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (response != null) break;
|
||||||
|
|
||||||
|
if (offset > 0 || limit > 0) {
|
||||||
|
int from = Math.max(0, Math.min(offset, messages.size()));
|
||||||
|
int to = Math.max(from, Math.min(from + limit, messages.size()));
|
||||||
|
messages = messages.subList(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray messageArray = new JSONArray();
|
||||||
|
|
||||||
|
for (Message m : messages) {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
|
obj.put("message_id", m.getMessage_id().toString());
|
||||||
|
obj.put("sender_id", m.getSender_id().toString());
|
||||||
|
|
||||||
|
User senderUser = userDatabase.findByInternalUUID(m.getSender_id());
|
||||||
|
obj.put("sender_name", senderUser != null ? senderUser.getProfile_name() : "Unknown");
|
||||||
|
|
||||||
|
obj.put("receiver_id", m.getReceiver_id().toString());
|
||||||
|
obj.put("receiver_type", m.getReceiver_type());
|
||||||
|
|
||||||
|
String receiverName = switch (m.getReceiver_type()) {
|
||||||
|
case "group" -> {
|
||||||
|
Group g = GroupDatabase.findByInternalUUID(m.getReceiver_id());
|
||||||
|
yield g != null ? g.getGroup_name() : "Unknown group";
|
||||||
|
}
|
||||||
|
case "channel" -> {
|
||||||
|
Channel c = ChannelDatabase.findByInternalUUID(m.getReceiver_id());
|
||||||
|
yield c != null ? c.getChannel_name() : "Unknown channel";
|
||||||
|
}
|
||||||
|
case "private" -> {
|
||||||
|
UUID otherId = PrivateChatDatabase.getOtherParticipant(m.getReceiver_id(), currentUser.getInternal_uuid());
|
||||||
|
User other = userDatabase.findByInternalUUID(otherId);
|
||||||
|
yield other != null ? other.getProfile_name() : "Unknown user";
|
||||||
|
}
|
||||||
|
default -> "Unknown";
|
||||||
|
};
|
||||||
|
obj.put("receiver_name", receiverName);
|
||||||
|
|
||||||
|
obj.put("content", m.getContent());
|
||||||
|
obj.put("message_type", m.getMessage_type()); // TEXT/IMAGE/AUDIO/VIDEO/FILE
|
||||||
|
obj.put("send_at", m.getSend_at().toString());
|
||||||
|
|
||||||
|
obj.put("is_edited", m.isIs_edited());
|
||||||
|
obj.put("is_deleted_globally", m.isIs_deleted_globally());
|
||||||
|
obj.put("edited_at", m.getEdited_at() != null ? m.getEdited_at().toString() : JSONObject.NULL);
|
||||||
|
|
||||||
|
if (m.getReply_to_id() != null) {
|
||||||
|
obj.put("reply_to_id", m.getReply_to_id().toString());
|
||||||
|
|
||||||
|
Message replied = MessageDatabase.findById(m.getReply_to_id());
|
||||||
|
if (replied != null) {
|
||||||
|
User rSender = userDatabase.findByInternalUUID(replied.getSender_id());
|
||||||
|
obj.put("reply_to_sender", rSender != null ? rSender.getProfile_name() : "Unknown");
|
||||||
|
obj.put("reply_to_content", replied.getContent());
|
||||||
|
obj.put("reply_to_type", replied.getMessage_type());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj.put("reply_to_id", JSONObject.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.getOriginal_message_id() != null && m.getForwarded_from() != null) {
|
||||||
|
User originalSender = userDatabase.findByInternalUUID(m.getForwarded_from());
|
||||||
|
obj.put("is_forwarded", true);
|
||||||
|
obj.put("forwarded_from", originalSender != null
|
||||||
|
? originalSender.getProfile_name()
|
||||||
|
: m.getForwarded_from().toString());
|
||||||
|
obj.put("forwarded_by", senderUser != null ? senderUser.getProfile_name() : "Unknown");
|
||||||
|
obj.put("forwarded_from_id", m.getForwarded_from().toString());
|
||||||
|
} else {
|
||||||
|
obj.put("is_forwarded", false);
|
||||||
|
obj.put("forwarded_from", JSONObject.NULL);
|
||||||
|
obj.put("forwarded_by", JSONObject.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONArray reactionsArr = new JSONArray();
|
||||||
|
try {
|
||||||
|
List<String> reactions = MessageReactionDatabase.getReactions(m.getMessage_id());
|
||||||
|
if (reactions != null && !reactions.isEmpty()) {
|
||||||
|
Map<String, Integer> counter = new HashMap<>();
|
||||||
|
for (String r : reactions) {
|
||||||
|
counter.put(r, counter.getOrDefault(r, 0) + 1);
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, Integer> e : counter.entrySet()) {
|
||||||
|
JSONObject ro = new JSONObject();
|
||||||
|
ro.put("emoji", e.getKey());
|
||||||
|
ro.put("count", e.getValue());
|
||||||
|
reactionsArr.put(ro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
obj.put("reactions", reactionsArr);
|
||||||
|
|
||||||
|
messageArray.put(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
data.put("messages", messageArray);
|
||||||
|
|
||||||
|
response = new ResponseModel("success", "Messages fetched.", data);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
response = new ResponseModel("error", "Error fetching messages: " + e.getMessage());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "toggle_block": {
|
case "toggle_block": {
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
@@ -1523,21 +1616,16 @@ public class ClientHandler implements Runnable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// دریافت شناسه چت و نوع حذف (یکطرفه یا دوطرفه)
|
|
||||||
UUID targetId = UUID.fromString(requestJson.getString("chat_id"));
|
UUID targetId = UUID.fromString(requestJson.getString("chat_id"));
|
||||||
boolean both = requestJson.getBoolean("both");
|
boolean both = requestJson.getBoolean("both");
|
||||||
|
|
||||||
// Real-Time Event Dispatch (اطلاعرسانی ریل تایم)
|
|
||||||
if (both) {
|
if (both) {
|
||||||
// حذف دوطرفه
|
|
||||||
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
|
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
|
||||||
RealTimeEventDispatcher.notifyChatDeleted("private", currentUser.getInternal_uuid(), List.of(targetId));
|
RealTimeEventDispatcher.notifyChatDeleted("private", currentUser.getInternal_uuid(), List.of(targetId));
|
||||||
} else {
|
} else {
|
||||||
// حذف یکطرفه
|
|
||||||
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
|
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// فراخوانی متد حذف چت
|
|
||||||
response = handleDeleteChat(requestJson);
|
response = handleDeleteChat(requestJson);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2642,6 +2730,115 @@ public class ClientHandler implements Runnable {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "mark_as_read": {
|
||||||
|
if (currentUser == null) {
|
||||||
|
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UUID chatId = UUID.fromString(requestJson.getString("receiver_id"));
|
||||||
|
String chatType = requestJson.getString("receiver_type").toLowerCase();
|
||||||
|
int limit = requestJson.optInt("limit", 500);
|
||||||
|
|
||||||
|
List<UUID> targetMessageIds = new ArrayList<>();
|
||||||
|
if (requestJson.has("message_ids")) {
|
||||||
|
JSONArray arr = requestJson.getJSONArray("message_ids");
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
targetMessageIds.add(UUID.fromString(arr.getString(i)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetMessageIds = MessageDatabase.getUnreadMessageIds(
|
||||||
|
currentUser.getInternal_uuid(), chatId, chatType, limit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int updatedStatus = 0;
|
||||||
|
int insertedReceipts = 0;
|
||||||
|
|
||||||
|
try (Connection c = ConnectionDb.connect()) {
|
||||||
|
c.setAutoCommit(false);
|
||||||
|
for (UUID mid : targetMessageIds) {
|
||||||
|
updatedStatus += MessageDatabase.setMessageReadIfNeeded(mid, currentUser.getInternal_uuid());
|
||||||
|
insertedReceipts+= MessageDatabase.insertReceiptIfAbsent(mid, currentUser.getInternal_uuid());
|
||||||
|
}
|
||||||
|
c.commit();
|
||||||
|
} catch (Exception tx) {
|
||||||
|
tx.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
data.put("marked_count", targetMessageIds.size());
|
||||||
|
data.put("status_updates", updatedStatus);
|
||||||
|
data.put("receipts_inserted", insertedReceipts);
|
||||||
|
|
||||||
|
response = new ResponseModel("success", "Marked as read.", data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
response = new ResponseModel("error", "Error in mark_as_read: " + e.getMessage());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "get_other_user_status": {
|
||||||
|
try {
|
||||||
|
String userIdStr = requestJson.getString("user_id");
|
||||||
|
UUID user_id = UUID.fromString(userIdStr);
|
||||||
|
|
||||||
|
String lastSeenStr = userDatabase.getLastSeen(user_id); // returns ISO string?
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
|
||||||
|
if (lastSeenStr != null && !"Unknown".equals(lastSeenStr)) {
|
||||||
|
// parse string to time
|
||||||
|
LocalDateTime lastSeen = LocalDateTime.parse(lastSeenStr);
|
||||||
|
long diffMillis = java.time.Duration.between(lastSeen, LocalDateTime.now()).toMillis();
|
||||||
|
|
||||||
|
boolean online = diffMillis <= 120_000;
|
||||||
|
|
||||||
|
data.put("online", online);
|
||||||
|
data.put("last_seen", lastSeen.toString());
|
||||||
|
|
||||||
|
response = new ResponseModel("success", "Status fetched.", data);
|
||||||
|
} else {
|
||||||
|
response = new ResponseModel("error", "User not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
response = new ResponseModel("error", "Failed to fetch status.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
case "get_header_info": {
|
||||||
|
try {
|
||||||
|
String type = requestJson.getString("receiver_type"); // "private" | "group" | "channel"
|
||||||
|
UUID receiverId = UUID.fromString(requestJson.getString("receiver_id"));
|
||||||
|
|
||||||
|
UUID viewerId = null;
|
||||||
|
if ("private".equalsIgnoreCase(type)) {
|
||||||
|
String v = requestJson.optString("viewer_id",
|
||||||
|
requestJson.optString("my_id", null));
|
||||||
|
if (v == null) {
|
||||||
|
response = new ResponseModel("error", "viewer_id (or my_id) is required for private chats.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
viewerId = UUID.fromString(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
org.json.JSONObject data = org.to.telegramfinalproject.Database.ChatInfoDatabase
|
||||||
|
.getHeaderInfo(type, receiverId, viewerId);
|
||||||
|
|
||||||
|
response = new ResponseModel("success", "Header info fetched.", data);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
response = new ResponseModel("error", "Failed to fetch header info.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
response = new ResponseModel("error", "Unknown action: " + action);
|
response = new ResponseModel("error", "Unknown action: " + action);
|
||||||
}
|
}
|
||||||
@@ -2768,6 +2965,8 @@ public class ClientHandler implements Runnable {
|
|||||||
RealTimeEventDispatcher.sendToUser(receiver, chatPayload);
|
RealTimeEventDispatcher.sendToUser(receiver, chatPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealTimeEventDispatcher.sendToUser(senderId, chatPayload);
|
||||||
|
|
||||||
JSONObject data = new JSONObject();
|
JSONObject data = new JSONObject();
|
||||||
data.put("message_id", messageId.toString());
|
data.put("message_id", messageId.toString());
|
||||||
return new ResponseModel("success", "Message sent successfully.", data);
|
return new ResponseModel("success", "Message sent successfully.", data);
|
||||||
@@ -2855,4 +3054,35 @@ public class ClientHandler implements Runnable {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void enrichChatEntry(ChatEntry e, UUID me) {
|
||||||
|
Message last = MessageDatabase.getLastMessage(e.getId(), e.getType());
|
||||||
|
if (last != null) {
|
||||||
|
e.setLastMessageTime(String.valueOf(last.getSend_at()));
|
||||||
|
e.setLastMessagePreview(buildPreview(last));
|
||||||
|
e.setLastMessageType(last.getMessage_type());
|
||||||
|
e.setLastMessageSenderId(last.getSender_id());
|
||||||
|
}
|
||||||
|
|
||||||
|
int unread = MessageDatabase.getUnreadCount(me, e.getId(), e.getType());
|
||||||
|
e.setUnreadCount(unread);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPreview(Message m) {
|
||||||
|
String t = m.getMessage_type();
|
||||||
|
if ("TEXT".equalsIgnoreCase(t)) {
|
||||||
|
String c = m.getContent();
|
||||||
|
if (c == null) return "";
|
||||||
|
return c.length() > 120 ? c.substring(0, 120) + "…" : c;
|
||||||
|
}
|
||||||
|
switch (t) {
|
||||||
|
case "IMAGE": return "[Image]";
|
||||||
|
case "AUDIO": return "[Audio]";
|
||||||
|
case "VIDEO": return "[Video]";
|
||||||
|
case "FILE": return "[File]";
|
||||||
|
default: return "[Message]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.shape.Circle;
|
import javafx.scene.shape.Circle;
|
||||||
|
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -21,10 +22,6 @@ public class ChatItemController {
|
|||||||
@FXML private ImageView profileImageSystem;
|
@FXML private ImageView profileImageSystem;
|
||||||
@FXML private Circle systemCircle;
|
@FXML private Circle systemCircle;
|
||||||
|
|
||||||
// Default avatar resource path
|
|
||||||
private static final String DEFAULT_AVATAR =
|
|
||||||
"/org/to/telegramfinalproject/Avatars/default_avatar.png";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set chat item data, including a profile image (or default).
|
* Set chat item data, including a profile image (or default).
|
||||||
*
|
*
|
||||||
@@ -34,7 +31,7 @@ public class ChatItemController {
|
|||||||
* @param unread Unread message count
|
* @param unread Unread message count
|
||||||
* @param imageUrl Path/URL of profile image (can be null/empty)
|
* @param imageUrl Path/URL of profile image (can be null/empty)
|
||||||
*/
|
*/
|
||||||
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl) {
|
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl, String chatType) {
|
||||||
chatName.setText(name);
|
chatName.setText(name);
|
||||||
lastMessage.setText(lastMsg);
|
lastMessage.setText(lastMsg);
|
||||||
chatTime.setText(time);
|
chatTime.setText(time);
|
||||||
@@ -72,17 +69,52 @@ public class ChatItemController {
|
|||||||
systemAvatar.setManaged(true);
|
systemAvatar.setManaged(true);
|
||||||
|
|
||||||
} else if (imageUrl != null && !imageUrl.isEmpty()) {
|
} else if (imageUrl != null && !imageUrl.isEmpty()) {
|
||||||
profileImageUser.setImage(new Image(imageUrl, true));
|
// profileImageUser.setImage(new Image(imageUrl, true));
|
||||||
|
// profileImageUser.setVisible(true);
|
||||||
|
// profileImageUser.setManaged(true);
|
||||||
|
|
||||||
|
Image img = AvatarLocalResolver.load(imageUrl);
|
||||||
|
if (img != null) {
|
||||||
|
profileImageUser.setImage(img);
|
||||||
|
AvatarFX.circleClip(profileImageUser, 40);
|
||||||
|
} else {
|
||||||
|
String path;
|
||||||
|
if ("group".equalsIgnoreCase(chatType)) {
|
||||||
|
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||||
|
} else if ("channel".equalsIgnoreCase(chatType)) {
|
||||||
|
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||||
|
} else {
|
||||||
|
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||||
|
}
|
||||||
|
profileImageUser.setImage(new Image(
|
||||||
|
java.util.Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||||
|
));
|
||||||
|
}
|
||||||
profileImageUser.setVisible(true);
|
profileImageUser.setVisible(true);
|
||||||
profileImageUser.setManaged(true);
|
profileImageUser.setManaged(true);
|
||||||
|
|
||||||
} else {
|
} 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(
|
profileImageUser.setImage(new Image(
|
||||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||||
"/org/to/telegramfinalproject/Icons/default_profile.png"))
|
|
||||||
));
|
));
|
||||||
profileImageUser.setVisible(true);
|
profileImageUser.setVisible(true);
|
||||||
profileImageUser.setManaged(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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,25 +118,34 @@ public class IntroController {
|
|||||||
slider.setDaemon(true);
|
slider.setDaemon(true);
|
||||||
slider.start();
|
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
|
@FXML
|
||||||
private void handleStartMessaging(ActionEvent event) {
|
private void handleStartMessaging(ActionEvent event) {
|
||||||
try {
|
AppRouter.showLogin(); // همون Scene میمونه، فقط Root عوض میشه
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/login_view.fxml"));
|
}
|
||||||
Scene loginScene = new Scene(loader.load());
|
|
||||||
|
@FXML private void goRegister() { AppRouter.showRegister(); }
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ import javafx.scene.control.*;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
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.Database.userDatabase;
|
||||||
import org.to.telegramfinalproject.Models.User;
|
import org.to.telegramfinalproject.Models.User;
|
||||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
import org.to.telegramfinalproject.Security.PasswordHashing;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.to.telegramfinalproject.UI.AppRouter.showRegister;
|
||||||
|
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
@FXML private TextField usernameField;
|
@FXML private TextField usernameField;
|
||||||
@@ -23,7 +28,7 @@ public class LoginController {
|
|||||||
@FXML private PasswordField passwordField;
|
@FXML private PasswordField passwordField;
|
||||||
@FXML private TextField visiblePasswordField;
|
@FXML private TextField visiblePasswordField;
|
||||||
|
|
||||||
@FXML private Button togglePasswordBtn;
|
@FXML private Button toggleVisibilityBtn;
|
||||||
@FXML private Label errorLabel;
|
@FXML private Label errorLabel;
|
||||||
|
|
||||||
private ClientConnection connection;
|
private ClientConnection connection;
|
||||||
@@ -48,63 +53,131 @@ public class LoginController {
|
|||||||
visiblePasswordField.setManaged(passwordVisible);
|
visiblePasswordField.setManaged(passwordVisible);
|
||||||
passwordField.setVisible(!passwordVisible);
|
passwordField.setVisible(!passwordVisible);
|
||||||
passwordField.setManaged(!passwordVisible);
|
passwordField.setManaged(!passwordVisible);
|
||||||
togglePasswordBtn.setText(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
|
@FXML
|
||||||
private void handleLogin() {
|
private void handleLogin() {
|
||||||
String username = usernameField.getText();
|
String u = usernameField.getText().trim();
|
||||||
String password = passwordField.getText();
|
String p = passwordField.getText();
|
||||||
|
|
||||||
// 1. Check for empty fields
|
// 1. Check for empty fields
|
||||||
if (username.isEmpty() || password.isEmpty()) {
|
if (u.isEmpty() || p.isEmpty()) {
|
||||||
showError("Please fill in all required fields.");
|
showError("Please fill in all required fields.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check if username exists
|
// 2. Check if username exists
|
||||||
userDatabase userDb = new userDatabase();
|
userDatabase userDb = new userDatabase();
|
||||||
if (!userDb.existsByUsername(username)) {
|
if (!userDb.existsByUsername(u)) {
|
||||||
showError("This username doesn’t exist.");
|
showError("This username doesn’t exist.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check if password is correct
|
// 3. Check if password is correct
|
||||||
User user = userDb.findByUsername(username);
|
User user = userDb.findByUsername(u);
|
||||||
if (!PasswordHashing.verify(password, user.getPassword())) {
|
if (!PasswordHashing.verify(p, user.getPassword())) {
|
||||||
showError("Incorrect password.");
|
showError("Incorrect password.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Attempt to send to server
|
setUiBusy(true);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
JSONObject request = new JSONObject();
|
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||||
request.put("action", "login");
|
var handler = cli.getHandler();
|
||||||
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)
|
handler.login(u,p);
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Login successful!");
|
|
||||||
alert.show();
|
javafx.application.Platform.runLater(() -> {
|
||||||
|
setUiBusy(false);
|
||||||
|
|
||||||
|
if (!handler.wasSuccess() || Session.currentUser == null) {
|
||||||
|
showError(handler.getLastMessage().isEmpty() ? "Login failed." : handler.getLastMessage());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// موفق: Session از داخل send پر شده
|
||||||
|
goMain(); // بدون Alert → مستقیم به main.fxml
|
||||||
|
});
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
showError("Unable to connect to server. Please try again later.");
|
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
|
@FXML
|
||||||
private void switchToRegister() throws IOException {
|
private void switchToRegister() throws IOException {
|
||||||
switchScene("register_view.fxml");
|
showRegister();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchScene(String fxmlFile) {
|
private void switchScene(String fxmlFile) {
|
||||||
@@ -127,6 +200,7 @@ public class LoginController {
|
|||||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||||
pause.setOnFinished(event -> {
|
pause.setOnFinished(event -> {
|
||||||
errorLabel.setText(message);
|
errorLabel.setText(message);
|
||||||
|
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||||
errorLabel.setVisible(true);
|
errorLabel.setVisible(true);
|
||||||
});
|
});
|
||||||
pause.play();
|
pause.play();
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
package org.to.telegramfinalproject.UI;
|
|
||||||
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.PasswordField;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
|
||||||
import org.to.telegramfinalproject.Database.userDatabase;
|
|
||||||
import org.to.telegramfinalproject.Models.User;
|
|
||||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class LoginForm {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Button loginButton;
|
|
||||||
@FXML
|
|
||||||
private Button backButton;
|
|
||||||
@FXML private TextField usernameField;
|
|
||||||
@FXML private PasswordField passwordField;
|
|
||||||
|
|
||||||
private ClientConnection connection;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void initialize() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
connection = new ClientConnection("localhost", 8000);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("Could not connect to server: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
loginButton.setOnAction(e -> {
|
|
||||||
String username = usernameField.getText();
|
|
||||||
String password = passwordField.getText();
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
userDatabase userDb = new userDatabase();
|
|
||||||
User user = userDb.findByUsername(username);
|
|
||||||
|
|
||||||
if(!userDb.existsByUsername(username)){
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid username");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else if(!PasswordHashing.verify(password,user.getPassword())){
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else if(!PasswordHashing.verify(password,user.getPassword()) && !userDb.existsByUsername(username)){
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password and username");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
try {
|
|
||||||
String responseStr = connection.receive();
|
|
||||||
JSONObject response = new JSONObject(responseStr);
|
|
||||||
System.out.println("Status: " + response.getString("status"));
|
|
||||||
System.out.println("Message: " + response.getString("message"));
|
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, " Message: " + response.getString("message"));
|
|
||||||
alert.show();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
backButton.setOnAction(e -> {
|
|
||||||
switchScene("login_view.fxml");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchScene(String fxmlFile) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
|
||||||
Parent root = loader.load();
|
|
||||||
|
|
||||||
|
|
||||||
Stage stage = (Stage) backButton.getScene().getWindow();
|
|
||||||
stage.setScene(new Scene(root));
|
|
||||||
stage.show();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -9,25 +9,43 @@ import javafx.scene.Node;
|
|||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.shape.Circle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import org.to.telegramfinalproject.Client.Session;
|
||||||
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class MainController {
|
public class MainController {
|
||||||
|
|
||||||
// === LEFT PANE ===
|
// === LEFT PANE ===
|
||||||
@FXML private VBox chatListContainer; // inside the scrollPane
|
@FXML private VBox chatListContainer; // inside the scrollPane
|
||||||
@FXML private ScrollPane scrollPane; // chat list scroll
|
@FXML private ScrollPane scrollPane; // chat list scroll
|
||||||
|
|
||||||
|
// === Global Search ===
|
||||||
|
@FXML private TextField searchBar;
|
||||||
|
@FXML private VBox globalSearchPane;
|
||||||
|
@FXML private VBox noResultsBox;
|
||||||
|
@FXML private ImageView noResultIcon;
|
||||||
|
@FXML private ScrollPane globalSearchScroll;
|
||||||
|
@FXML private VBox globalSearchResultsContainer;
|
||||||
|
private enum SearchMode {
|
||||||
|
GLOBAL,
|
||||||
|
CHAT
|
||||||
|
}
|
||||||
|
private SearchMode currentSearchMode = SearchMode.GLOBAL;
|
||||||
|
private UUID currentChatId; // if in CHAT mode, which chat to search in
|
||||||
|
|
||||||
|
// === Search In Chat ===
|
||||||
@FXML private VBox chatSearchPane; // search results panel
|
@FXML private VBox chatSearchPane; // search results panel
|
||||||
@FXML private ListView<String> chatSearchResults;
|
@FXML private ListView<String> chatSearchResults;
|
||||||
@FXML private MenuButton scopeDropdown;
|
|
||||||
|
|
||||||
// === TOP BAR ===
|
|
||||||
@FXML private TextField searchBar; // global search bar
|
|
||||||
@FXML private Button menuButton;
|
|
||||||
|
|
||||||
// === RIGHT PANE ===
|
// === RIGHT PANE ===
|
||||||
@FXML private VBox leftPane;
|
@FXML private VBox leftPane;
|
||||||
@@ -41,6 +59,14 @@ public class MainController {
|
|||||||
// === Sidebar ===
|
// === Sidebar ===
|
||||||
@FXML private Pane overlay;
|
@FXML private Pane overlay;
|
||||||
@FXML private ImageView menuIcon;
|
@FXML private ImageView menuIcon;
|
||||||
|
private SidebarMenuController sidebarController; //save sidebar controller
|
||||||
|
|
||||||
|
// === Time formatter for messages ===
|
||||||
|
private static final java.time.format.DateTimeFormatter FMT_HHMM =
|
||||||
|
java.time.format.DateTimeFormatter.ofPattern("HH:mm");
|
||||||
|
private static final java.time.format.DateTimeFormatter FMT_DATE_TIME =
|
||||||
|
java.time.format.DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
|
||||||
|
private static final String YESTERDAY_LABEL = "Yesterday";
|
||||||
|
|
||||||
// === STATE ===
|
// === STATE ===
|
||||||
private static MainController instance;
|
private static MainController instance;
|
||||||
@@ -57,10 +83,82 @@ public class MainController {
|
|||||||
public static MainController getInstance() {
|
public static MainController getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
private final Map<UUID, ChatItemController> itemControllers = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
//For realtime handling
|
||||||
|
public void onChatUpdated(UUID chatId, String chatType, LocalDateTime lastTs,
|
||||||
|
boolean isIncoming, String lastPreview) {
|
||||||
|
|
||||||
|
boolean exists =
|
||||||
|
(Session.chatList != null && Session.chatList.stream().anyMatch(c -> chatId.equals(c.getId()))) ||
|
||||||
|
(Session.activeChats != null && Session.activeChats.stream().anyMatch(c -> chatId.equals(c.getId()))) ||
|
||||||
|
(Session.archivedChats != null && Session.archivedChats.stream().anyMatch(c -> chatId.equals(c.getId())));
|
||||||
|
if (!exists) return;
|
||||||
|
|
||||||
|
boolean isOpen = Session.currentChatId != null &&
|
||||||
|
Session.currentChatId.equals(chatId.toString());
|
||||||
|
|
||||||
|
forEachChat(chatId, chat -> {
|
||||||
|
chat.setLastMessageTime(lastTs);
|
||||||
|
if (lastPreview != null) chat.setLastMessagePreview(lastPreview);
|
||||||
|
if (isIncoming && !isOpen) chat.setUnreadCount(chat.getUnreadCount() + 1); // ✅ یکدست با unreadCount
|
||||||
|
});
|
||||||
|
|
||||||
|
java.util.Comparator<ChatEntry> byTimeDesc = (c1, c2) -> {
|
||||||
|
var t1 = c1.getLastMessageTime();
|
||||||
|
var t2 = c2.getLastMessageTime();
|
||||||
|
if (t1 == null && t2 == null) return 0;
|
||||||
|
if (t1 == null) return 1;
|
||||||
|
if (t2 == null) return -1;
|
||||||
|
return t2.compareTo(t1);
|
||||||
|
};
|
||||||
|
if (Session.chatList != null) Session.chatList.sort(byTimeDesc);
|
||||||
|
if (Session.activeChats != null) Session.activeChats.sort(byTimeDesc);
|
||||||
|
if (Session.archivedChats != null) Session.archivedChats.sort(byTimeDesc);
|
||||||
|
|
||||||
|
refreshChatListUI();
|
||||||
|
refreshBadgesIfAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MainController
|
||||||
|
private void forEachChat(UUID chatId, java.util.function.Consumer<ChatEntry> fn) {
|
||||||
|
java.util.List<java.util.List<ChatEntry>> lists = java.util.List.of(
|
||||||
|
Session.chatList != null ? Session.chatList : java.util.List.of(),
|
||||||
|
Session.activeChats != null ? Session.activeChats : java.util.List.of(),
|
||||||
|
Session.archivedChats != null ? Session.archivedChats : java.util.List.of()
|
||||||
|
);
|
||||||
|
for (var lst : lists) {
|
||||||
|
for (var c : lst) {
|
||||||
|
if (chatId.equals(c.getId())) {
|
||||||
|
fn.accept(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void refreshChatListUI() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
chatListContainer.getChildren().clear();
|
||||||
|
itemControllers.clear();
|
||||||
|
populateChatListFromSession(); // همون متدی که نودها رو میسازه و میچسبونه
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void refreshBadgesIfAny() {
|
||||||
|
// آپدیت نشانها اگر لازم است
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
addSampleChats();
|
// Populate chat list
|
||||||
|
populateChatListFromSession();
|
||||||
|
|
||||||
// Register the scene for automatic CSS updates
|
// Register the scene for automatic CSS updates
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -70,17 +168,15 @@ public class MainController {
|
|||||||
// Listen for theme changes to update icons & labels manually
|
// Listen for theme changes to update icons & labels manually
|
||||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
// Dark mode ON
|
|
||||||
updateIconsForDarkMode();
|
updateIconsForDarkMode();
|
||||||
updateLabelsForDarkMode();
|
updateLabelsForDarkMode();
|
||||||
} else {
|
} else {
|
||||||
// Light mode ON
|
|
||||||
updateIconsForLightMode();
|
updateIconsForLightMode();
|
||||||
updateLabelsForLightMode();
|
updateLabelsForLightMode();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Smooth scroll feel
|
// Smooth scroll feel for chat list
|
||||||
scrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
scrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||||
scrollPane.setPannable(true);
|
scrollPane.setPannable(true);
|
||||||
scrollPane.setFitToWidth(true);
|
scrollPane.setFitToWidth(true);
|
||||||
@@ -99,23 +195,47 @@ public class MainController {
|
|||||||
mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider());
|
mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search in chat field listener
|
// Search bar enter action
|
||||||
searchBar.textProperty().addListener((obs, oldV, newV) -> {
|
searchBar.setOnAction(e -> {
|
||||||
if (!chatSearchPane.isVisible()) return; // only react if in search mode
|
String keyword = searchBar.getText().trim();
|
||||||
|
if (keyword.isEmpty()) return;
|
||||||
|
|
||||||
if (newV.trim().isEmpty()) {
|
if (currentSearchMode == SearchMode.GLOBAL) {
|
||||||
chatSearchResults.setVisible(false);
|
performGlobalSearch(keyword);
|
||||||
chatSearchResults.setManaged(false);
|
} else if (currentSearchMode == SearchMode.CHAT && currentChatId != null) {
|
||||||
} else {
|
performChatSearch(keyword, currentChatId);
|
||||||
chatSearchResults.setVisible(true);
|
|
||||||
chatSearchResults.setManaged(true);
|
|
||||||
chatSearchResults.getItems().setAll(
|
|
||||||
"Result 1: " + newV,
|
|
||||||
"Result 2: " + newV,
|
|
||||||
"Result 3: " + newV
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Typing in search bar shows global search panel
|
||||||
|
searchBar.textProperty().addListener((obs, o, n) -> {
|
||||||
|
if (n != null && !n.isBlank()) {
|
||||||
|
if (currentSearchMode == SearchMode.GLOBAL) {
|
||||||
|
showGlobalSearchPanel();
|
||||||
|
} else if (currentSearchMode == SearchMode.CHAT) {
|
||||||
|
showSearchPanel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chatSearchResults.setOnMouseClicked(e -> {
|
||||||
|
int idx = chatSearchResults.getSelectionModel().getSelectedIndex();
|
||||||
|
if (idx >= 0 && idx < searchBacking.size()) {
|
||||||
|
openSearchResult(searchBacking.get(idx));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth scroll feel for global search results
|
||||||
|
globalSearchScroll.getStylesheets().add(
|
||||||
|
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||||
|
);
|
||||||
|
globalSearchScroll.setPannable(true);
|
||||||
|
globalSearchScroll.setFitToWidth(true);
|
||||||
|
globalSearchScroll.setFitToHeight(false);
|
||||||
|
globalSearchScroll.getContent().setOnScroll(event -> {
|
||||||
|
double deltaY = event.getDeltaY() * 0.003; // match chat list smoothness
|
||||||
|
globalSearchScroll.setVvalue(globalSearchScroll.getVvalue() - deltaY);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from ChatPageController when user clicks search button
|
// Called from ChatPageController when user clicks search button
|
||||||
@@ -123,6 +243,8 @@ public class MainController {
|
|||||||
scrollPane.setVisible(false);
|
scrollPane.setVisible(false);
|
||||||
scrollPane.setManaged(false);
|
scrollPane.setManaged(false);
|
||||||
|
|
||||||
|
currentSearchMode = SearchMode.CHAT;
|
||||||
|
|
||||||
chatSearchPane.setVisible(true);
|
chatSearchPane.setVisible(true);
|
||||||
chatSearchPane.setManaged(true);
|
chatSearchPane.setManaged(true);
|
||||||
|
|
||||||
@@ -134,59 +256,185 @@ public class MainController {
|
|||||||
chatSearchPane.setVisible(false);
|
chatSearchPane.setVisible(false);
|
||||||
chatSearchPane.setManaged(false);
|
chatSearchPane.setManaged(false);
|
||||||
|
|
||||||
|
currentSearchMode = SearchMode.GLOBAL;
|
||||||
|
currentChatId = null;
|
||||||
|
|
||||||
scrollPane.setVisible(true);
|
scrollPane.setVisible(true);
|
||||||
scrollPane.setManaged(true);
|
scrollPane.setManaged(true);
|
||||||
|
|
||||||
|
searchBar.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSampleChats() {
|
public void showGlobalSearchPanel() {
|
||||||
addChat("Archived Chats", "Your archived chats", "10:45", 0);
|
scrollPane.setVisible(false);
|
||||||
addChat("Saved Messages", "Keep messages for later", "Yesterday", 0);
|
scrollPane.setManaged(false);
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
chatSearchPane.setVisible(false);
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
chatSearchPane.setManaged(false);
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
globalSearchPane.setVisible(true);
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
globalSearchPane.setManaged(true);
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
|
||||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addChat(String name, String lastMsg, String time, int unread) {
|
@FXML
|
||||||
|
public void closeGlobalSearch() {
|
||||||
|
globalSearchPane.setVisible(false);
|
||||||
|
globalSearchPane.setManaged(false);
|
||||||
|
|
||||||
|
scrollPane.setVisible(true);
|
||||||
|
scrollPane.setManaged(true);
|
||||||
|
|
||||||
|
searchBar.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatChatTime(java.time.LocalDateTime ts) {
|
||||||
|
if (ts == null) return "";
|
||||||
|
var today = java.time.LocalDate.now();
|
||||||
|
var d = ts.toLocalDate();
|
||||||
|
|
||||||
|
if (d.isEqual(today)) {
|
||||||
|
//today
|
||||||
|
return FMT_HHMM.format(ts);
|
||||||
|
//yesterday
|
||||||
|
} else if (d.isEqual(today.minusDays(1))) {
|
||||||
|
return YESTERDAY_LABEL;
|
||||||
|
} else {
|
||||||
|
return FMT_DATE_TIME.format(ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateChatListFromSession() {
|
||||||
|
chatListContainer.getChildren().clear();
|
||||||
|
|
||||||
|
var list = (org.to.telegramfinalproject.Client.Session.activeChats != null
|
||||||
|
&& !org.to.telegramfinalproject.Client.Session.activeChats.isEmpty())
|
||||||
|
? org.to.telegramfinalproject.Client.Session.activeChats
|
||||||
|
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||||
|
|
||||||
|
if (list == null || list.isEmpty()) return;
|
||||||
|
|
||||||
|
ChatEntry saved = null;
|
||||||
|
java.util.List<ChatEntry> others = new java.util.ArrayList<>();
|
||||||
|
for (ChatEntry c : list) {
|
||||||
|
if (saved == null && isSavedMessages(c)) {
|
||||||
|
saved = c;
|
||||||
|
} else {
|
||||||
|
others.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر “Archived Chats” یا هدر دیگری داری، قبلش اضافه کن (اختیاری)
|
||||||
|
// addArchivedHeaderIfYouHaveOne();
|
||||||
|
|
||||||
|
// 1) همیشه Saved اول بیاد (اگر وجود داشت)
|
||||||
|
if (saved != null) {
|
||||||
|
addChatNode(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ChatEntry c : others) {
|
||||||
|
addChatNode(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private void addChatNode(ChatEntry chat ) {
|
||||||
|
// try {
|
||||||
|
// FXMLLoader fx = new FXMLLoader(getClass().getResource(
|
||||||
|
// "/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||||
|
// Node item = fx.load();
|
||||||
|
// ChatItemController cc = fx.getController();
|
||||||
|
//
|
||||||
|
// String lastPreview = "";
|
||||||
|
// String time = (chat.getLastMessageTime() != null)
|
||||||
|
// ? timeFmt.format(chat.getLastMessageTime()) : "";
|
||||||
|
//
|
||||||
|
// cc.setChatData(chat.getName(), lastPreview, time, 0);
|
||||||
|
//
|
||||||
|
// item.setOnMouseClicked(e -> openChat(String.valueOf(chat)));
|
||||||
|
// chatListContainer.getChildren().add(item);
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private void addChatNode(ChatEntry chat) {
|
||||||
|
// try {
|
||||||
|
// FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||||
|
// Node item = fx.load();
|
||||||
|
// ChatItemController cc = fx.getController();
|
||||||
|
//
|
||||||
|
// String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||||
|
// String timeText = formatChatTime(chat.getLastMessageTime());
|
||||||
|
// FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||||
|
// Node chatItem = loader.load();
|
||||||
|
// ChatItemController controller = loader.getController();
|
||||||
|
//
|
||||||
|
// cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), "/org/to/telegramfinalproject/Avatars/default_profile.png");
|
||||||
|
// item.setOnMouseClicked(e -> openChat(chat));
|
||||||
|
// chatListContainer.getChildren().add(item);
|
||||||
|
//
|
||||||
|
// itemControllers.put(chat.getId(), cc);
|
||||||
|
//
|
||||||
|
// } catch (Exception ex) {
|
||||||
|
// ex.printStackTrace();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
private void addChatNode(ChatEntry chat) {
|
||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||||
Node chatItem = loader.load();
|
Node item = fx.load();
|
||||||
ChatItemController controller = loader.getController();
|
ChatItemController cc = fx.getController();
|
||||||
controller.setChatData(name, lastMsg, time, unread, "/org/to/telegramfinalproject/Avatars/default_profile.png");
|
|
||||||
|
|
||||||
chatItem.setOnMouseClicked(e -> openChat(name));
|
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||||
chatListContainer.getChildren().add(chatItem);
|
|
||||||
} catch (IOException e) {
|
String timeText = chat.getLastMessageTime() == null
|
||||||
e.printStackTrace();
|
? ""
|
||||||
|
: formatChatTime(chat.getLastMessageTime());
|
||||||
|
|
||||||
|
// If chat has a profile picture, pass it; otherwise null
|
||||||
|
String imageUrl = (chat.getImageUrl() != null && !chat.getImageUrl().isEmpty())
|
||||||
|
? chat.getImageUrl()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), imageUrl, chat.getType()); item.setOnMouseClicked(e -> openChat(chat));
|
||||||
|
chatListContainer.getChildren().add(item);
|
||||||
|
|
||||||
|
itemControllers.put(chat.getId(), cc);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openChat(String chatName) {
|
private String mapTypeToLabel(String t) {
|
||||||
|
switch (t.toUpperCase()) {
|
||||||
|
case "IMAGE": return "[Image]";
|
||||||
|
case "AUDIO": return "[Audio]";
|
||||||
|
case "VIDEO": return "[Video]";
|
||||||
|
case "FILE": return "[File]";
|
||||||
|
default: return "[Message]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openChat(ChatEntry chat) {
|
||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
||||||
Node chatPage = loader.load();
|
Node chatPage = loader.load();
|
||||||
|
|
||||||
ChatPageController controller = loader.getController();
|
ChatPageController controller = loader.getController();
|
||||||
controller.setChat("Alice", "/org/to/telegramfinalproject/Avatars/profile_test.png");
|
controller.showChat(chat);
|
||||||
|
|
||||||
|
this.chatPageController = controller;
|
||||||
|
Session.currentChatId = chat.getId().toString();
|
||||||
|
|
||||||
chatDisplayArea.getChildren().clear();
|
chatDisplayArea.getChildren().setAll(chatPage);
|
||||||
chatDisplayArea.getChildren().add(chatPage);
|
chat.setUnreadCount(0);
|
||||||
|
ChatItemController item = itemControllers.get(chat.getId());
|
||||||
|
if (item != null) item.setUnread(0);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException ex) {
|
||||||
e.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,25 +447,56 @@ public class MainController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @FXML
|
||||||
|
// private void openSidebar() {
|
||||||
|
// try {
|
||||||
|
// if (sidebarRoot == null) {
|
||||||
|
// sidebarRoot = FXMLLoader.load(getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml"));
|
||||||
|
// StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT);
|
||||||
|
// sidebarRoot.setTranslateX(-getSidebarWidth());
|
||||||
|
// mainRoot.getChildren().add(sidebarRoot);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot);
|
||||||
|
// slideIn.setToX(0);
|
||||||
|
// slideIn.play();
|
||||||
|
//
|
||||||
|
// overlay.setVisible(true);
|
||||||
|
// overlay.setMouseTransparent(false);
|
||||||
|
//
|
||||||
|
// isSidebarOpen = true;
|
||||||
|
//
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void openSidebar() {
|
private void openSidebar() {
|
||||||
try {
|
try {
|
||||||
if (sidebarRoot == null) {
|
if (sidebarRoot == null) {
|
||||||
sidebarRoot = FXMLLoader.load(getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml"));
|
FXMLLoader loader = new FXMLLoader(
|
||||||
|
getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml"));
|
||||||
|
sidebarRoot = loader.load();
|
||||||
|
sidebarController = loader.getController();
|
||||||
|
|
||||||
StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT);
|
StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT);
|
||||||
sidebarRoot.setTranslateX(-getSidebarWidth());
|
sidebarRoot.setTranslateX(-getSidebarWidth());
|
||||||
mainRoot.getChildren().add(sidebarRoot);
|
mainRoot.getChildren().add(sidebarRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sidebarController != null && org.to.telegramfinalproject.Client.Session.currentUser != null) {
|
||||||
|
sidebarController.setUserFromSession(
|
||||||
|
org.to.telegramfinalproject.Client.Session.currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot);
|
TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot);
|
||||||
slideIn.setToX(0);
|
slideIn.setToX(0);
|
||||||
slideIn.play();
|
slideIn.play();
|
||||||
|
|
||||||
overlay.setVisible(true);
|
overlay.setVisible(true);
|
||||||
overlay.setMouseTransparent(false);
|
overlay.setMouseTransparent(false);
|
||||||
|
|
||||||
isSidebarOpen = true;
|
isSidebarOpen = true;
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -263,11 +542,13 @@ public class MainController {
|
|||||||
private void updateIconsForDarkMode() {
|
private void updateIconsForDarkMode() {
|
||||||
// Example: switch images to white versions
|
// Example: switch images to white versions
|
||||||
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_light.png")));
|
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_light.png")));
|
||||||
|
noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_light.png")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateIconsForLightMode() {
|
private void updateIconsForLightMode() {
|
||||||
// Example: switch images to black versions
|
// Example: switch images to black versions
|
||||||
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_dark.png")));
|
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_dark.png")));
|
||||||
|
noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_dark.png")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLabelsForDarkMode() {
|
private void updateLabelsForDarkMode() {
|
||||||
@@ -286,4 +567,411 @@ public class MainController {
|
|||||||
public void closeOverlay(Node overlayNode) {
|
public void closeOverlay(Node overlayNode) {
|
||||||
mainRoot.getChildren().remove(overlayNode);
|
mainRoot.getChildren().remove(overlayNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ===== Search UI state =====
|
||||||
|
private final java.util.List<SearchResult> searchBacking = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
@FXML private AnchorPane chatHost; // کانتینر مناسب در مین برای قرار دادن ChatPage
|
||||||
|
|
||||||
|
private ChatPageController chatPageController;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ChatPageController getChatPageController() {
|
||||||
|
return chatPageController;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private enum SRType { USER, GROUP, CHANNEL, MESSAGE }
|
||||||
|
|
||||||
|
private static class SearchResult {
|
||||||
|
final SRType type;
|
||||||
|
final String title; // name / sender_name / ...
|
||||||
|
final String subtitle; // @"id" / context string / time
|
||||||
|
final String receiverType; // برای MESSAGE/GROUP/CHANNEL: private|group|channel, برای USER: null
|
||||||
|
final java.util.UUID uuid; // uuid آن موجودیت (user/group/channel/chat holder) یا receiver_id پیام
|
||||||
|
final String displayId; // id قابل نمایش (مثل username یا group_id/channel_id)
|
||||||
|
final String messageId; // فقط برای MESSAGE
|
||||||
|
final String time; // نمایش
|
||||||
|
|
||||||
|
SearchResult(SRType t, String title, String subtitle, String receiverType,
|
||||||
|
java.util.UUID uuid, String displayId, String messageId, String time) {
|
||||||
|
this.type = t; this.title = title; this.subtitle = subtitle;
|
||||||
|
this.receiverType = receiverType; this.uuid = uuid;
|
||||||
|
this.displayId = displayId; this.messageId = messageId; this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toDisplay() {
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE:
|
||||||
|
String left = (title == null || title.isBlank()) ? "Message" : title;
|
||||||
|
String right = (time == null ? "" : (" • " + time));
|
||||||
|
return "🗨 " + left + right + (subtitle==null?"":(" — " + subtitle));
|
||||||
|
case USER: return "👤 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||||
|
case GROUP: return "👥 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||||
|
case CHANNEL: return "📣 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performChatSearch(String keyword, UUID chatId) {
|
||||||
|
if (keyword == null || keyword.isBlank()) {
|
||||||
|
chatSearchResults.getItems().clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSearchPanel();
|
||||||
|
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject();
|
||||||
|
req.put("action", "search_in_chat");
|
||||||
|
req.put("keyword", keyword);
|
||||||
|
req.put("chat_id", chatId.toString());
|
||||||
|
req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"));
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
org.json.JSONObject resp;
|
||||||
|
try {
|
||||||
|
resp = ActionHandler.sendWithResponse(req);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp == null || !"success".equals(resp.optString("status"))) return;
|
||||||
|
org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results");
|
||||||
|
if (arr == null) arr = new org.json.JSONArray();
|
||||||
|
|
||||||
|
java.util.List<String> tmp = new java.util.ArrayList<>();
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
org.json.JSONObject it = arr.getJSONObject(i);
|
||||||
|
String time = it.optString("time", "");
|
||||||
|
String content = it.optString("content", "[No content]");
|
||||||
|
tmp.add("🗨 " + content + (time.isEmpty() ? "" : " • " + time));
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
chatSearchResults.getItems().setAll(tmp);
|
||||||
|
chatSearchResults.setVisible(true);
|
||||||
|
chatSearchResults.setManaged(true);
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void performGlobalSearch(String keyword) {
|
||||||
|
if (keyword == null || keyword.isBlank()) {
|
||||||
|
chatSearchResults.getItems().clear();
|
||||||
|
searchBacking.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (org.to.telegramfinalproject.Client.Session.currentUser == null ||
|
||||||
|
!org.to.telegramfinalproject.Client.Session.currentUser.has("user_id")) {
|
||||||
|
System.out.println("You must be logged in to search.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showGlobalSearchPanel();
|
||||||
|
|
||||||
|
// Request to server
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject();
|
||||||
|
req.put("action", "search");
|
||||||
|
req.put("keyword", keyword);
|
||||||
|
req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"));
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
org.json.JSONObject resp;
|
||||||
|
try {
|
||||||
|
resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resp == null || !"success".equals(resp.optString("status"))) return;
|
||||||
|
|
||||||
|
org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results");
|
||||||
|
if (arr == null) arr = new org.json.JSONArray();
|
||||||
|
|
||||||
|
final java.util.List<SearchResult> tmp = new java.util.ArrayList<>();
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
org.json.JSONObject it = arr.getJSONObject(i);
|
||||||
|
String type = it.optString("type","");
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "user": {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||||
|
String name = it.optString("name","Unknown");
|
||||||
|
String id = it.optString("id", "");
|
||||||
|
tmp.add(new SearchResult(
|
||||||
|
SRType.USER, name,
|
||||||
|
id.isBlank()? null : "@"+id,
|
||||||
|
null, uuid, id, null, null
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "group": {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||||
|
String name = it.optString("name","Unknown group");
|
||||||
|
String id = it.optString("id", "");
|
||||||
|
tmp.add(new SearchResult(
|
||||||
|
SRType.GROUP, name,
|
||||||
|
id.isBlank()? null : id,
|
||||||
|
"group", uuid, id, null, null
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "channel": {
|
||||||
|
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||||
|
String name = it.optString("name","Unknown channel");
|
||||||
|
String id = it.optString("id", "");
|
||||||
|
tmp.add(new SearchResult(
|
||||||
|
SRType.CHANNEL, name,
|
||||||
|
id.isBlank()? null : id,
|
||||||
|
"channel", uuid, id, null, null
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "message": {
|
||||||
|
String senderName = it.optString("sender_name", it.optString("sender","Unknown"));
|
||||||
|
String time = it.optString("time", "");
|
||||||
|
String content = it.optString("content","[No content]");
|
||||||
|
String rType = it.optString("receiver_type",""); // private|group|channel
|
||||||
|
java.util.UUID rUuid = java.util.UUID.fromString(it.getString("receiver_id"));
|
||||||
|
String ctx = "";
|
||||||
|
if ("group".equals(rType)) ctx = it.optString("group_name","");
|
||||||
|
if ("channel".equals(rType)) ctx = it.optString("channel_name","");
|
||||||
|
String subtitle = (ctx.isBlank()? "" : ctx) + (content.isBlank()? "" : (subtitleSep(ctx)+content));
|
||||||
|
tmp.add(new SearchResult(
|
||||||
|
SRType.MESSAGE, senderName, subtitle, rType, rUuid,
|
||||||
|
it.optString("receiver_display_id", ""),
|
||||||
|
it.optString("message_id", null),
|
||||||
|
time
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> renderSearchResults(tmp));
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String subtitleSep(String s){ return s==null || s.isBlank()? "" : " — "; }
|
||||||
|
|
||||||
|
// private void renderSearchResults(java.util.List<SearchResult> results) {
|
||||||
|
// searchBacking.clear();
|
||||||
|
// searchBacking.addAll(results);
|
||||||
|
//
|
||||||
|
// javafx.collections.ObservableList<SearchResult> view =
|
||||||
|
// javafx.collections.FXCollections.observableArrayList(results);
|
||||||
|
// globalSearchResults.setItems(view);
|
||||||
|
// globalSearchResults.setVisible(true);
|
||||||
|
// globalSearchResults.setManaged(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
private void renderSearchResults(List<SearchResult> results) {
|
||||||
|
globalSearchResultsContainer.getChildren().clear();
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
noResultsBox.setVisible(true);
|
||||||
|
noResultsBox.setManaged(true);
|
||||||
|
globalSearchScroll.setVisible(false);
|
||||||
|
globalSearchScroll.setManaged(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
noResultsBox.setVisible(false);
|
||||||
|
noResultsBox.setManaged(false);
|
||||||
|
globalSearchScroll.setVisible(true);
|
||||||
|
globalSearchScroll.setManaged(true);
|
||||||
|
|
||||||
|
for (SearchResult r : results) {
|
||||||
|
HBox container = new HBox(10);
|
||||||
|
ImageView avatar = new ImageView();
|
||||||
|
avatar.setFitWidth(40);
|
||||||
|
avatar.setFitHeight(40);
|
||||||
|
avatar.setSmooth(true);
|
||||||
|
avatar.setPreserveRatio(true);
|
||||||
|
|
||||||
|
// 🔑 Clip it into a circle
|
||||||
|
Circle clip = new Circle(20, 20, 20); // x,y = center, radius = 20
|
||||||
|
avatar.setClip(clip);
|
||||||
|
|
||||||
|
Label title = new Label(r.title);
|
||||||
|
title.getStyleClass().add("global-search-title");
|
||||||
|
|
||||||
|
Label subtitle = new Label(
|
||||||
|
r.type == SRType.MESSAGE
|
||||||
|
? (r.subtitle != null ? r.subtitle : "")
|
||||||
|
: "Press to see messages"
|
||||||
|
);
|
||||||
|
subtitle.getStyleClass().add("global-search-subtitle");
|
||||||
|
|
||||||
|
VBox texts = new VBox(2, title, subtitle);
|
||||||
|
container.getChildren().addAll(avatar, texts);
|
||||||
|
|
||||||
|
// Default profile per type
|
||||||
|
switch (r.type) {
|
||||||
|
case USER -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))));
|
||||||
|
case GROUP -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_group_profile.png"))));
|
||||||
|
case CHANNEL -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_channel_profile.png"))));
|
||||||
|
case MESSAGE -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||||
|
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
container.setOnMouseClicked(e -> openSearchResult(r));
|
||||||
|
globalSearchResultsContainer.getChildren().add(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openSearchResult(SearchResult r) {
|
||||||
|
switch (r.type) {
|
||||||
|
case USER: {
|
||||||
|
|
||||||
|
java.util.UUID chatId = findExistingPrivateChatId(r.uuid);
|
||||||
|
if (chatId == null) {
|
||||||
|
chatId = fetchOrCreatePrivateChat(r.uuid);
|
||||||
|
if (chatId == null) {
|
||||||
|
System.out.println("❌ Failed to create/find private chat.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||||
|
ce.setId(chatId.toString()); // ⬅️ internal chat_id
|
||||||
|
ce.setDisplayId(r.displayId); // username
|
||||||
|
ce.setName(r.title); // profile_name
|
||||||
|
ce.setType("private");
|
||||||
|
|
||||||
|
openChat(ce);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GROUP:
|
||||||
|
case CHANNEL: {
|
||||||
|
org.to.telegramfinalproject.Models.ChatEntry existing =
|
||||||
|
findExistingChat(r.uuid, r.receiverType);
|
||||||
|
if (existing != null) {
|
||||||
|
openChat(existing);
|
||||||
|
} else {
|
||||||
|
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||||
|
ce.setId(r.uuid.toString()); // internal_uuid group/channel
|
||||||
|
ce.setDisplayId(r.displayId); // group_id/channel_id
|
||||||
|
ce.setName(r.title);
|
||||||
|
ce.setType(r.receiverType);
|
||||||
|
openChat(ce);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MESSAGE: {
|
||||||
|
org.to.telegramfinalproject.Models.ChatEntry existing =
|
||||||
|
findExistingChat(r.uuid, r.receiverType);
|
||||||
|
if (existing != null) {
|
||||||
|
openChat(existing);
|
||||||
|
} else {
|
||||||
|
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||||
|
ce.setId(r.uuid.toString()); // receiver internal_uuid
|
||||||
|
ce.setType(r.receiverType);
|
||||||
|
ce.setName(guessNameForReceiver(r));
|
||||||
|
ce.setDisplayId(r.displayId);
|
||||||
|
openChat(ce);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private org.to.telegramfinalproject.Models.ChatEntry findExistingChat(java.util.UUID internalId, String type) {
|
||||||
|
var list = (org.to.telegramfinalproject.Client.Session.chatList==null)
|
||||||
|
? java.util.Collections.<org.to.telegramfinalproject.Models.ChatEntry>emptyList()
|
||||||
|
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||||
|
for (var c : list) {
|
||||||
|
if (internalId.toString().equals(c.getId().toString())
|
||||||
|
&& type.equalsIgnoreCase(c.getType())) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.util.UUID findExistingPrivateChatId(java.util.UUID otherUserUuid) {
|
||||||
|
var list = (org.to.telegramfinalproject.Client.Session.chatList==null)
|
||||||
|
? java.util.Collections.<org.to.telegramfinalproject.Models.ChatEntry>emptyList()
|
||||||
|
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||||
|
|
||||||
|
for (var c : list) {
|
||||||
|
if ("private".equalsIgnoreCase(c.getType())) {
|
||||||
|
if (otherUserUuid.equals(c.getOtherUserId())) {
|
||||||
|
try { return java.util.UUID.fromString(c.getId().toString()); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private java.util.UUID fetchOrCreatePrivateChat(java.util.UUID otherUserUuid) {
|
||||||
|
try {
|
||||||
|
org.json.JSONObject req = new org.json.JSONObject();
|
||||||
|
req.put("action", "get_or_create_private_chat");
|
||||||
|
req.put("user1", org.to.telegramfinalproject.Client.Session.currentUser.getString("internal_uuid"));
|
||||||
|
req.put("user2", otherUserUuid.toString());
|
||||||
|
org.json.JSONObject resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||||
|
if (resp != null && "success".equals(resp.optString("status"))) {
|
||||||
|
String chatId = resp.getJSONObject("data").getString("chat_id");
|
||||||
|
return java.util.UUID.fromString(chatId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String guessNameForReceiver(SearchResult r) {
|
||||||
|
if (r.title != null && !r.title.isBlank()) return r.title;
|
||||||
|
if (r.receiverType != null) {
|
||||||
|
switch (r.receiverType) {
|
||||||
|
case "group": return "Group";
|
||||||
|
case "channel": return "Channel";
|
||||||
|
case "private": return "Private Chat";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Chat";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSavedMessages(ChatEntry c) {
|
||||||
|
if (c == null) return false;
|
||||||
|
|
||||||
|
// اگر تایپ اختصاصی داری
|
||||||
|
if ("saved".equalsIgnoreCase(c.getType())) return true;
|
||||||
|
|
||||||
|
// اگر با نام مشخص ذخیره میکنی
|
||||||
|
String n = c.getName();
|
||||||
|
if (n != null && n.equalsIgnoreCase("Saved Messages")) return true;
|
||||||
|
|
||||||
|
// حالت پرایوت با خودِ کاربر
|
||||||
|
String me = (org.to.telegramfinalproject.Client.Session.currentUser != null)
|
||||||
|
? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "")
|
||||||
|
: "";
|
||||||
|
try {
|
||||||
|
UUID other = c.getOtherUserId(); // اگر این فیلد را داری
|
||||||
|
if ("private".equalsIgnoreCase(c.getType()) &&
|
||||||
|
other != null && other.toString().equalsIgnoreCase(me)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,8 @@ import org.to.telegramfinalproject.Database.userDatabase;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.to.telegramfinalproject.UI.AppRouter.showLogin;
|
||||||
|
|
||||||
public class RegisterController {
|
public class RegisterController {
|
||||||
|
|
||||||
@FXML private TextField userIdField;
|
@FXML private TextField userIdField;
|
||||||
@@ -38,15 +40,13 @@ public class RegisterController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Sync password and confirm fields with their visible counterparts
|
|
||||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
|
||||||
visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connection = new ClientConnection("localhost", 8000);
|
org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {
|
||||||
System.out.println("Could not connect to server: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||||
|
visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -69,6 +69,68 @@ public class RegisterController {
|
|||||||
toggleConfirmBtn.setText(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
|
@FXML
|
||||||
private void handleRegister() {
|
private void handleRegister() {
|
||||||
String userID = userIdField.getText().trim();
|
String userID = userIdField.getText().trim();
|
||||||
@@ -80,58 +142,79 @@ public class RegisterController {
|
|||||||
userDatabase userDb = new userDatabase();
|
userDatabase userDb = new userDatabase();
|
||||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
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()) {
|
if (userID.isEmpty() || username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) {
|
||||||
showError("Please fill in all required fields.");
|
showError("Please fill in all required fields."); return;
|
||||||
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; }
|
||||||
|
|
||||||
// 2. Username exists
|
// UI را موقتاً disable کن (اختیاری)
|
||||||
if (userDb.existsByUsername(username)) {
|
setBusy(true);
|
||||||
showError("This username is already taken.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. User ID exists
|
new Thread(() -> {
|
||||||
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 {
|
try {
|
||||||
JSONObject request = new JSONObject();
|
// 1) مطمئن شو کانکشن/لیسنر روشن است
|
||||||
request.put("action", "register");
|
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||||
request.put("user_id", userID);
|
var handler = cli.getHandler();
|
||||||
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)
|
// 2) Register
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration successful!");
|
var regReq = new org.json.JSONObject()
|
||||||
alert.show();
|
.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) {
|
} catch (Exception ex) {
|
||||||
showError("Failed to register. Please try again later.");
|
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
|
@FXML
|
||||||
private void switchToLogin() throws IOException {
|
private void switchToLogin() throws IOException {
|
||||||
switchScene("login_view.fxml");
|
showLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchScene(String fxmlFile) {
|
private void switchScene(String fxmlFile) {
|
||||||
@@ -154,6 +237,7 @@ public class RegisterController {
|
|||||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||||
pause.setOnFinished(event -> {
|
pause.setOnFinished(event -> {
|
||||||
errorLabel.setText(message);
|
errorLabel.setText(message);
|
||||||
|
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||||
errorLabel.setVisible(true);
|
errorLabel.setVisible(true);
|
||||||
});
|
});
|
||||||
pause.play();
|
pause.play();
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
package org.to.telegramfinalproject.UI;
|
|
||||||
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.PasswordField;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
|
||||||
import org.to.telegramfinalproject.Database.userDatabase;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class RegisterForm {
|
|
||||||
@FXML
|
|
||||||
private Button submitButton;
|
|
||||||
@FXML
|
|
||||||
private Button backButton;
|
|
||||||
@FXML private TextField userIdField;
|
|
||||||
@FXML private TextField usernameField;
|
|
||||||
@FXML private TextField profileNameField;
|
|
||||||
@FXML private PasswordField passwordField;
|
|
||||||
@FXML private PasswordField confirmPasswordField;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private ClientConnection connection;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void initialize() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
connection = new ClientConnection("localhost", 8000);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("Could not connect to server: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
submitButton.setOnAction(e -> {
|
|
||||||
String userID = userIdField.getText();
|
|
||||||
String username = usernameField.getText();
|
|
||||||
String profile_name = profileNameField.getText();
|
|
||||||
String password = passwordField.getText();
|
|
||||||
String confirmPass =confirmPasswordField.getText();
|
|
||||||
JSONObject request = new JSONObject();
|
|
||||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
|
||||||
|
|
||||||
userDatabase userDb = new userDatabase();
|
|
||||||
if(password.equals(confirmPass) && password.matches(passwordRegex) && !userDb.existsByUserId(userID)&& !userDb.existsByUsername(username)){
|
|
||||||
try {
|
|
||||||
request.put("action", "register");
|
|
||||||
request.put("user_id", userID);
|
|
||||||
request.put("username", username);
|
|
||||||
request.put("password", password);
|
|
||||||
request.put("profile_name", profile_name);
|
|
||||||
connection.send(request.toString());
|
|
||||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration is successful");
|
|
||||||
alert.show();
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(!password.equals(confirmPass) && password.matches(passwordRegex)) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Password doesn't match");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else if(userDb.existsByUserId(userID))
|
|
||||||
{
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "User ID is already exist");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else if(userDb.existsByUsername(username)){
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Username is already exist");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Password isn't Strong enough");
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
backButton.setOnAction(e -> {
|
|
||||||
switchScene("login_view.fxml");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchScene(String fxmlFile) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
|
||||||
Parent root = loader.load();
|
|
||||||
|
|
||||||
|
|
||||||
Stage stage = (Stage) backButton.getScene().getWindow();
|
|
||||||
stage.setScene(new Scene(root));
|
|
||||||
stage.show();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -14,6 +14,9 @@ import javafx.scene.layout.HBox;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -22,6 +25,9 @@ public class SidebarMenuController {
|
|||||||
|
|
||||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@FXML private VBox sidebarRoot;
|
@FXML private VBox sidebarRoot;
|
||||||
@FXML private ImageView profileImage;
|
@FXML private ImageView profileImage;
|
||||||
@FXML private Label usernameLabel;
|
@FXML private Label usernameLabel;
|
||||||
@@ -46,8 +52,10 @@ public class SidebarMenuController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Load default profile image
|
// Load default profile image
|
||||||
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_profile.png");
|
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||||
if (profile != null) profileImage.setImage(profile);
|
if (profile != null) profileImage.setImage(profile);
|
||||||
|
AvatarFX.circleClip(profileImage, 56);
|
||||||
|
|
||||||
|
|
||||||
setupButtonActions();
|
setupButtonActions();
|
||||||
setupToggleAction();
|
setupToggleAction();
|
||||||
@@ -210,4 +218,58 @@ public class SidebarMenuController {
|
|||||||
private void openSettings() { System.out.println("Opening Settings..."); }
|
private void openSettings() { System.out.println("Opening Settings..."); }
|
||||||
private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); }
|
private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); }
|
||||||
private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); }
|
private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// کمککننده: هم URL و هم ریسورس کلاسپث را امتحان میکند
|
||||||
|
// private Image tryLoadImage(String src) {
|
||||||
|
// if (src == null || src.isBlank()) return null;
|
||||||
|
// try {
|
||||||
|
// // اگر ریسورس داخل پروژه است (با / شروع شود یا در resources موجود باشد)
|
||||||
|
// var res = getClass().getResource(src);
|
||||||
|
// if (res != null) return new Image(res.toExternalForm(), true);
|
||||||
|
// // در غیر این صورت فرض کن URL است (http/https/file)
|
||||||
|
// return new Image(src, true);
|
||||||
|
// } catch (Exception ignored) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
private Image tryLoadImage(String src) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,36 @@
|
|||||||
package org.to.telegramfinalproject.UI;
|
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.application.Application;
|
||||||
@@ -6,28 +38,31 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.to.telegramfinalproject.UI.AppRouter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class TelegramApplication extends Application {
|
public class TelegramApplication extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/main.fxml"));
|
// First: intro.fxml
|
||||||
Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
|
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());
|
scene.getStylesheets().add(
|
||||||
|
getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm()
|
||||||
|
);
|
||||||
|
|
||||||
stage.setTitle("Telegram");
|
stage.setTitle("Telegram");
|
||||||
|
stage.getIcons().add(new Image(
|
||||||
|
TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png")
|
||||||
|
));
|
||||||
stage.setScene(scene);
|
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();
|
stage.show();
|
||||||
}
|
|
||||||
public static void main(String[] args) {
|
AppRouter.init(stage, scene);
|
||||||
launch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) { launch(); }
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
@@ -1,3 +1,9 @@
|
|||||||
|
/* ===== Dark Theme Root ===== */
|
||||||
|
.root {
|
||||||
|
-fx-background-color: #0f1c26; /* overall dark base */
|
||||||
|
-fx-text-fill: #e8f1f8;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sidebar buttons */
|
/* Sidebar buttons */
|
||||||
.sidebar-btn {
|
.sidebar-btn {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
@@ -170,7 +176,7 @@
|
|||||||
.search-placeholder { -fx-text-fill: #8ea1b2; -fx-font-size: 13px; }
|
.search-placeholder { -fx-text-fill: #8ea1b2; -fx-font-size: 13px; }
|
||||||
|
|
||||||
/* ===== Global search bar ===== */
|
/* ===== Global search bar ===== */
|
||||||
.global-search {
|
.global-search-pane {
|
||||||
-fx-background-color: #1b2b39;
|
-fx-background-color: #1b2b39;
|
||||||
-fx-background-radius: 15;
|
-fx-background-radius: 15;
|
||||||
-fx-text-fill: #e8f1f8;
|
-fx-text-fill: #e8f1f8;
|
||||||
@@ -295,6 +301,86 @@
|
|||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Global Search List (Dark) === */
|
||||||
|
.global-search-list {
|
||||||
|
-fx-background-color: #1b2735; /* uniform dark background */
|
||||||
|
-fx-control-inner-background: #1b2735;
|
||||||
|
-fx-control-inner-background-alt: #1b2735; /* disable stripes */
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-list .virtual-flow,
|
||||||
|
.global-search-list .clipped-container,
|
||||||
|
.global-search-list .sheet {
|
||||||
|
-fx-background-color: #1b2735; /* internal containers same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-list .list-cell {
|
||||||
|
-fx-padding: 8 10;
|
||||||
|
-fx-background-color: #1b2735; /* solid dark rows */
|
||||||
|
-fx-border-color: #2e3948; /* subtle divider */
|
||||||
|
-fx-border-width: 0 0 0.5 0;
|
||||||
|
-fx-alignment: CENTER_LEFT;
|
||||||
|
-fx-text-fill: #e8f1f8; /* light text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-list .list-cell:hover {
|
||||||
|
-fx-background-color: rgba(255, 255, 255, 0.08); /* lighter on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-list .list-cell:selected {
|
||||||
|
-fx-background-color: #2a3a4a; /* darker blue-gray on select */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Titles & subtitles */
|
||||||
|
.global-search-title {
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #e8f1f8;
|
||||||
|
}
|
||||||
|
.global-search-subtitle {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: #8ea1b2; /* muted gray-blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search header */
|
||||||
|
.search-header {
|
||||||
|
-fx-padding: 8 12;
|
||||||
|
-fx-background-color: #1b2735;
|
||||||
|
-fx-border-color: #2e3948;
|
||||||
|
-fx-border-width: 0 0 1 0;
|
||||||
|
}
|
||||||
|
.search-header-label {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: #e8f1f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No results */
|
||||||
|
.no-results-label {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: #8ea1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar placeholders */
|
||||||
|
.global-search-avatar {
|
||||||
|
-fx-background-radius: 50%;
|
||||||
|
-fx-background-color: #444;
|
||||||
|
-fx-min-width: 36;
|
||||||
|
-fx-min-height: 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close button */
|
||||||
|
.search-close-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: #e8f1f8;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
.search-close-btn:hover {
|
||||||
|
-fx-text-fill: #2ca4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ===== Overlay ===== */
|
/* ===== Overlay ===== */
|
||||||
.overlay-root {
|
.overlay-root {
|
||||||
-fx-alignment: CENTER;
|
-fx-alignment: CENTER;
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
.root {
|
||||||
|
-fx-base: #ffffff;
|
||||||
|
-fx-text-base-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sidebar buttons */
|
/* Sidebar buttons */
|
||||||
.sidebar-btn {
|
.sidebar-btn {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
@@ -195,7 +200,7 @@
|
|||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-search {
|
.global-search-pane {
|
||||||
-fx-background-color: #f5f5f5;
|
-fx-background-color: #f5f5f5;
|
||||||
-fx-background-radius: 15;
|
-fx-background-radius: 15;
|
||||||
-fx-text-fill: #0f141a;
|
-fx-text-fill: #0f141a;
|
||||||
@@ -294,6 +299,108 @@
|
|||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Global Search List === */
|
||||||
|
.global-search-list .list-cell {
|
||||||
|
-fx-padding: 8 10;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: #ddd; /* light divider */
|
||||||
|
-fx-border-width: 0 0 0.5 0;
|
||||||
|
-fx-alignment: CENTER_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-list .list-cell:hover {
|
||||||
|
-fx-background-color: rgba(0, 0, 0, 0.05); /* light hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-title {
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-search-subtitle {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: #666; /* gray for light mode */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar placeholder (if no image) */
|
||||||
|
.global-search-avatar {
|
||||||
|
-fx-background-radius: 50%;
|
||||||
|
-fx-background-color: #bbb;
|
||||||
|
-fx-min-width: 36;
|
||||||
|
-fx-min-height: 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global search panel */
|
||||||
|
.global-search-pane {
|
||||||
|
-fx-background-color: -fx-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-header {
|
||||||
|
-fx-padding: 8 12;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: #ddd;
|
||||||
|
-fx-border-width: 0 0 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-header-label {
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-text-fill: -fx-text-base-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results-label {
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-text-fill: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Global Search Result Panel ===== */
|
||||||
|
.search-results-container {
|
||||||
|
-fx-background-color: -fx-base;
|
||||||
|
-fx-padding: 8;
|
||||||
|
-fx-spacing: 6;
|
||||||
|
-fx-border-color: transparent transparent #ddd transparent;
|
||||||
|
-fx-border-width: 0 0 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results-title {
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
-fx-font-size: 13px;
|
||||||
|
-fx-text-fill: -fx-text-base-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-padding: 6 10;
|
||||||
|
-fx-spacing: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:hover {
|
||||||
|
-fx-background-color: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar always square, not squeezed */
|
||||||
|
.search-result-avatar {
|
||||||
|
-fx-fit-width: 40;
|
||||||
|
-fx-fit-height: 40;
|
||||||
|
-fx-preserve-ratio: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close button */
|
||||||
|
.search-close-btn {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: -fx-text-base-color;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ===== Overlay ===== */
|
/* ===== Overlay ===== */
|
||||||
.overlay-root {
|
.overlay-root {
|
||||||
-fx-alignment: CENTER;
|
-fx-alignment: CENTER;
|
||||||
|
|||||||
@@ -44,10 +44,14 @@
|
|||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<HBox spacing="5" alignment="CENTER" HBox.hgrow="ALWAYS">
|
||||||
<TextField fx:id="searchBar"
|
<TextField fx:id="searchBar"
|
||||||
prefHeight="36" prefWidth="430.0"
|
|
||||||
promptText="Search"
|
promptText="Search"
|
||||||
|
HBox.hgrow="ALWAYS"
|
||||||
|
maxWidth="Infinity"
|
||||||
styleClass="global-search"/>
|
styleClass="global-search"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
@@ -69,44 +73,82 @@
|
|||||||
</content>
|
</content>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
|
|
||||||
<!-- Search panel (hidden initially) -->
|
<!-- Search in chat -->
|
||||||
<VBox fx:id="chatSearchPane"
|
<VBox fx:id="chatSearchPane"
|
||||||
visible="false" managed="false"
|
visible="false" managed="false"
|
||||||
styleClass="chat-search-pane">
|
styleClass="chat-search-pane">
|
||||||
|
|
||||||
<!-- Section label -->
|
|
||||||
<Label text="Search messages in" styleClass="search-label"/>
|
<Label text="Search messages in" styleClass="search-label"/>
|
||||||
|
|
||||||
<!-- Tiny separator line -->
|
|
||||||
<Separator styleClass="search-separator" prefHeight="0.5"/>
|
<Separator styleClass="search-separator" prefHeight="0.5"/>
|
||||||
|
|
||||||
<!-- User profile + "This chat" + close -->
|
|
||||||
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-scope-bar">
|
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-scope-bar">
|
||||||
<!-- Avatar -->
|
<ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true"/>
|
||||||
<ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@/org/to/telegramfinalproject/Avatars/profile_test.png"/>
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
|
|
||||||
<!-- scope -->
|
|
||||||
<VBox alignment="CENTER_LEFT">
|
<VBox alignment="CENTER_LEFT">
|
||||||
<Label text="This chat" styleClass="search-scope"/>
|
<Label text="This chat" styleClass="search-scope"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
<Region HBox.hgrow="ALWAYS"/>
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
<!-- Close button -->
|
|
||||||
<Button text="✕" onAction="#closeSearchPanel" styleClass="close-btn"/>
|
<Button text="✕" onAction="#closeSearchPanel" styleClass="close-btn"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<!-- Placeholder -->
|
|
||||||
<VBox alignment="CENTER" spacing="10" prefHeight="300">
|
<VBox alignment="CENTER" spacing="10" prefHeight="300">
|
||||||
<ImageView fx:id="searchIconLarge" fitWidth="64" fitHeight="64" preserveRatio="true"/>
|
<ImageView fx:id="searchIconLarge" fitWidth="64" fitHeight="64" preserveRatio="true"/>
|
||||||
<Label text="Search for messages" styleClass="search-placeholder"/>
|
<Label text="Search for messages" styleClass="search-placeholder"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<!-- Results -->
|
|
||||||
<ListView fx:id="chatSearchResults" visible="false" managed="false"/>
|
<ListView fx:id="chatSearchResults" visible="false" managed="false"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
|
<!-- Global Search Panel -->
|
||||||
|
<VBox fx:id="globalSearchPane" visible="false" managed="false" styleClass="global-search-pane">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<HBox alignment="CENTER_LEFT">
|
||||||
|
<Label text=" Search results" styleClass="search-results-title"/>
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button text="✕" onAction="#closeGlobalSearch" styleClass="search-close-btn"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- Search results -->
|
||||||
|
<ScrollPane fx:id="globalSearchScroll"
|
||||||
|
fitToWidth="true"
|
||||||
|
hbarPolicy="NEVER"
|
||||||
|
vbarPolicy="AS_NEEDED"
|
||||||
|
styleClass="chat-scroll">
|
||||||
|
<content>
|
||||||
|
<VBox fx:id="globalSearchResultsContainer" spacing="5"
|
||||||
|
styleClass="global-search-list">
|
||||||
|
<!-- One search result item -->
|
||||||
|
<HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-result-item">
|
||||||
|
<!-- Avatar -->
|
||||||
|
<StackPane>
|
||||||
|
<ImageView fx:id="searchResultAvatar"
|
||||||
|
fitWidth="40" fitHeight="40"
|
||||||
|
preserveRatio="true"
|
||||||
|
smooth="true"
|
||||||
|
cache="true"
|
||||||
|
styleClass="avatar"/>
|
||||||
|
</StackPane>
|
||||||
|
|
||||||
|
<!-- Texts -->
|
||||||
|
<VBox spacing="2">
|
||||||
|
<Label fx:id="searchResultTitle" styleClass="global-search-title"/>
|
||||||
|
<Label fx:id="searchResultSubtitle" styleClass="global-search-subtitle"/>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</content>
|
||||||
|
</ScrollPane>
|
||||||
|
|
||||||
|
<!-- No Results Placeholder -->
|
||||||
|
<VBox fx:id="noResultsBox" alignment="CENTER" spacing="10" visible="false" managed="false">
|
||||||
|
<ImageView fx:id="noResultIcon" fitWidth="64" fitHeight="64" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@/org/to/telegramfinalproject/Icons/no_result_dark.png"/>
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<Label text="No results found" styleClass="no-results-label"/>
|
||||||
|
</VBox>
|
||||||
|
</VBox>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 812 B |
|
After Width: | Height: | Size: 827 B |