From b5340160bf07957d471ce78b4849cd9d8606768b Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Fri, 8 Aug 2025 13:33:03 +0330 Subject: [PATCH] Saved messages chat updated. --- .../Client/ActionHandler.java | 158 ++++++++++++++---- .../Client/SidebarHandler.java | 88 ++++++++-- .../Database/MessageDatabase.java | 44 ++++- .../telegramfinalproject/Models/JsonUtil.java | 2 +- .../Server/ClientHandler.java | 17 +- .../Server/SidebarService.java | 18 +- 6 files changed, 274 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 0c94d1c..c1e5954 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -268,34 +268,45 @@ public class ActionHandler { if (response.has("data") && !response.isNull("data")) { JSONObject data = response.getJSONObject("data"); - //is chat list available - if (!data.has("chat_list") || data.isNull("chat_list")) { - System.out.println("❌ chat_list not found in response data."); + if ((!data.has("active_chat_list") || data.isNull("active_chat_list")) && + (!data.has("archived_chat_list") || data.isNull("archived_chat_list"))) { + System.out.println("❌ No chat list found in response data."); return; } - JSONArray chatListJson = data.getJSONArray("chat_list"); - List chatList = new ArrayList<>(); + List activeChats = new ArrayList<>(); + List archivedChats = new ArrayList<>(); - for (Object obj : chatListJson) { - JSONObject chat = (JSONObject) obj; - - ChatEntry entry = new ChatEntry( - UUID.fromString(chat.getString("internal_id")), - 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) - ); - - chatList.add(entry); + // 📁 Parse active chat list + if (data.has("active_chat_list") && !data.isNull("active_chat_list")) { + JSONArray activeJson = data.getJSONArray("active_chat_list"); + for (Object obj : activeJson) { + JSONObject chat = (JSONObject) obj; + ChatEntry entry = parseChatEntry(chat); + activeChats.add(entry); + } } - Session.chatList = chatList; - System.out.println("✅ Chat list updated. Total: " + chatList.size()); + // 📁 Parse archived chat list + if (data.has("archived_chat_list") && !data.isNull("archived_chat_list")) { + JSONArray archivedJson = data.getJSONArray("archived_chat_list"); + for (Object obj : archivedJson) { + JSONObject chat = (JSONObject) obj; + ChatEntry entry = parseChatEntry(chat); + archivedChats.add(entry); + } + } + + Session.chatList = new ArrayList<>(); + Session.chatList.addAll(activeChats); + Session.chatList.addAll(archivedChats); + + Session.activeChats = activeChats; + Session.archivedChats = archivedChats; + + System.out.println("✅ Chat list updated."); + System.out.println("📂 Active Chats: " + activeChats.size()); + System.out.println("📁 Archived Chats: " + archivedChats.size()); } else { System.out.println("⚠️ Response has no data object."); } @@ -312,6 +323,25 @@ public class ActionHandler { } } + private ChatEntry parseChatEntry(JSONObject chat) { + ChatEntry entry = new ChatEntry( + UUID.fromString(chat.getString("internal_id")), + 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")) { + entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); + } + + return entry; + } + public void createGroup() { String groupId = null; @@ -470,6 +500,10 @@ public class ActionHandler { entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); } + if (chat.has("is_saved_messages")) { + entry.setSavedMessages(chat.getBoolean("is_saved_messages")); + } + chatList.add(entry); } @@ -940,16 +974,40 @@ public class ActionHandler { return; } + boolean isSavedMessage = false; + System.out.println("\nYour Chats:"); System.out.println("0. 📦 Archived Chats"); + // Track index dynamically + int index = 1; + + // Check if Saved Messages exists in the list + int savedMessagesIndex = -1; for (int i = 0; i < Session.activeChats.size(); i++) { ChatEntry entry = Session.activeChats.get(i); + + if (entry.isSavedMessages()) { + savedMessagesIndex = index; + System.out.println(index + ". 📦 Saved Messages Chat"); + index++; + break; + } + } + + // Print the rest of the chats + for (int i = 0; i < Session.activeChats.size(); i++) { + ChatEntry entry = Session.activeChats.get(i); + if (entry.isSavedMessages()) { + continue; // Already printed above + } + String time = (entry.getLastMessageTime() == null) ? "No messages yet" : entry.getLastMessageTime().toString(); - System.out.println((i + 1) + ". [" + entry.getType() + "] " + + System.out.println(index + ". [" + entry.getType() + "] " + entry.getName() + " - Last: " + time); + index++; } System.out.print("Select a chat by number: "); @@ -960,14 +1018,33 @@ public class ActionHandler { return; } - int index = choice - 1; + if (choice == savedMessagesIndex) { + new SidebarHandler(scanner, this).getSavedMessagesData(Session.getUserUUID()); + return; + } - if (index < 0 || index >= Session.activeChats.size()) { + // Adjust for Saved Messages if it was in the list + int baseIndex = (savedMessagesIndex != -1 && choice > savedMessagesIndex) ? 1 : 0; + int chatIndex = choice - 1 - baseIndex; + + if (chatIndex < 0 || chatIndex >= Session.activeChats.size()) { System.out.println("Invalid selection."); return; } - ChatEntry selected = Session.activeChats.get(index); + // Find the actual index of the chat, skipping the saved_messages entry + int actualIndex = 0; + for (int i = 0; i < Session.activeChats.size(); i++) { + if (Session.activeChats.get(i).getType().equalsIgnoreCase("saved_messages")) { + continue; // Skip saved_messages + } + if (actualIndex == chatIndex) { + break; + } + actualIndex++; + } + + ChatEntry selected = Session.activeChats.get(actualIndex); openChat(selected); } @@ -3168,11 +3245,11 @@ public class ActionHandler { System.out.print("Enter your message: "); String content = scanner.nextLine(); - System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): "); + System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); String messageType = scanner.nextLine().toUpperCase(); Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); while (!allowedTypes.contains(messageType)) { - System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE): "); + System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); messageType = scanner.nextLine().toUpperCase(); } @@ -3182,9 +3259,32 @@ public class ActionHandler { while (true) { System.out.print("File URL: "); String fileUrl = scanner.nextLine(); - System.out.print("File Type (IMAGE / VIDEO / FILE): "); + + // URL validation + if (fileUrl.isEmpty()) { + System.out.print("URL can not be empty. Try again."); + continue; + } + + if (fileUrl.contains(" ")) { + System.out.println("URL cannot contain spaces. Try again."); + continue; + } + + if (!fileUrl.isEmpty() && !fileUrl.matches("^(http|https)://.*$")) { + System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link."); + continue; + } + + System.out.print("File Type (IMAGE / VIDEO / FILE / AUDIO): "); String fileType = scanner.nextLine().toUpperCase(); + Set allowedFileTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedFileTypes.contains(fileType)) { + System.out.print("❌ Invalid type. Try again (IMAGE / VIDEO / FILE / AUDIO): "); + fileType = scanner.nextLine().toUpperCase(); + } + JSONObject fileJson = new JSONObject(); fileJson.put("file_url", fileUrl); fileJson.put("file_type", fileType); diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 035abe3..652b8b7 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -3,13 +3,11 @@ package org.to.telegramfinalproject.Client; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.Message; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.UUID; +import java.util.*; import static org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse; @@ -212,6 +210,7 @@ public class SidebarHandler { System.out.println("Enter new profile picture URL (or leave empty to remove):"); String newImageUrl = scanner.nextLine().trim(); + // URL validation if (newImageUrl.contains(" ")) { System.out.println("URL cannot contain spaces."); continue; @@ -245,7 +244,7 @@ public class SidebarHandler { } private void showContacts() { - System.out.println("📇 Showing contacts..."); + actionHandler.showContactList(); } public void getSavedMessagesData(String userId) { @@ -307,21 +306,38 @@ public class SidebarHandler { UUID.fromString(msgJson.getString("receiver_id")), msgJson.getString("content"), msgJson.getString("message_type"), - msgJson.optString("file_url", null), LocalDateTime.parse(msgJson.getString("send_at").replace(" ", "T")), msgJson.getString("status"), replyToId, msgJson.getBoolean("is_edited"), originalMessageId, forwardedBy, - forwardedFrom + forwardedFrom, + msgJson.getBoolean("is_deleted_globally"), + LocalDateTime.parse(msgJson.getString("edited_at").replace(" ", "T")) ); messages.add(msg); } } - // Step 6: Show chat + // Step 6: Add to active chats if not already present + boolean alreadyExists = Session.activeChats.stream() + .anyMatch(entry -> entry.getId().equals(chatId)); + if (!alreadyExists) { + ChatEntry savedEntry = new ChatEntry( + chatId, + "Saved-Messages", + "Saved Messages", + "📌", // or use a URL string if you have an icon for saved messages + "private", + messages.isEmpty() ? null : messages.get(messages.size() - 1).getSend_at() + ); + savedEntry.setSavedMessages(true); + Session.activeChats.add(savedEntry); + } + + // Step 7: Show chat showSavedMessages(chatId, messages); } catch (Exception e) { @@ -355,22 +371,74 @@ public class SidebarHandler { break; } + System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); + String messageType = scanner.nextLine().toUpperCase(); + Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedTypes.contains(messageType)) { + System.out.print("Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); + messageType = scanner.nextLine().toUpperCase(); + } + + // Attaching Files + JSONArray attachmentsArray = new JSONArray(); + System.out.print("Do you want to attach files? (yes/no): "); + if (scanner.nextLine().equalsIgnoreCase("yes")) { + while (true) { + System.out.print("File URL: "); + String fileUrl = scanner.nextLine(); + + // URL validation + if (fileUrl.isEmpty()) { + System.out.print("URL can not be empty. Try again."); + continue; + } + + if (fileUrl.contains(" ")) { + System.out.println("URL cannot contain spaces. Try again."); + continue; + } + + if (!fileUrl.isEmpty() && !fileUrl.matches("^(http|https)://.*$")) { + System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link."); + continue; + } + + System.out.print("File Type (IMAGE / VIDEO / FILE / AUDIO): "); + String fileType = scanner.nextLine().toUpperCase(); + + Set allowedFileTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedFileTypes.contains(fileType)) { + System.out.print("Invalid type. Try again (IMAGE / VIDEO / FILE / AUDIO): "); + fileType = scanner.nextLine().toUpperCase(); + } + + JSONObject fileJson = new JSONObject(); + fileJson.put("file_url", fileUrl); + fileJson.put("file_type", fileType); + attachmentsArray.put(fileJson); + + System.out.print("Add another file? (yes/no): "); + if (!scanner.nextLine().equalsIgnoreCase("yes")) break; + } + } + // Prepare the request JSON JSONObject request = new JSONObject(); - request.put("action", "send_message"); + request.put("action", "send_saved_messages"); request.put("message_id", UUID.randomUUID().toString()); request.put("sender_id", userUUID); request.put("receiver_type", "private"); request.put("receiver_id", userUUID); // saved messages = to yourself request.put("content", content); request.put("message_type", "TEXT"); - request.put("file_url", JSONObject.NULL); request.put("status", "READ"); request.put("reply_to_id", JSONObject.NULL); request.put("is_edited", false); request.put("original_message_id", JSONObject.NULL); request.put("forwarded_by", JSONObject.NULL); request.put("forwarded_from", JSONObject.NULL); + request.put("is_deleted_globally", JSONObject.NULL); + request.put("edited_at", JSONObject.NULL); // Send the message and wait for response JSONObject response = ActionHandler.sendWithResponse(request); diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 7efb4cd..91ade78 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -174,7 +174,7 @@ public class MessageDatabase { 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").toLocalDateTime() + rs.getTimestamp("edited_at") != null ? rs.getTimestamp("edited_at").toLocalDateTime() : null )); } @@ -485,13 +485,49 @@ public class MessageDatabase { } public static boolean insertSavedMessage(Message message) { - String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type, status, reply_to_id, is_edited, original_message_id," + - " forwarded_by, forwarded_from, is_deleted_globally) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)"; + String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type, send_at, status, reply_to_id, is_edited, edited_at," + + " original_message_id, forwarded_by, forwarded_from, is_deleted_globally) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, message.getMessage_id()); // message_id + ps.setObject(2, message.getSender_id()); // sender_id + ps.setString(3, message.getReceiver_type()); // receiver_type (private) + ps.setObject(4, message.getReceiver_id()); // receiver_id (same as sender_id for saved messages) + ps.setString(5, message.getContent()); // content + ps.setString(6, message.getMessage_type()); // message_type + ps.setTimestamp(7, Timestamp.valueOf(message.getSend_at())); // send_at + ps.setString(8, message.getStatus()); // status + if (message.getReply_to_id() != null) + ps.setObject(9, message.getReply_to_id()); // reply_to_id + else + ps.setNull(9, Types.OTHER); + + ps.setBoolean(10, message.isIs_edited()); // is_edited + + if (message.getEdited_at() != null) + ps.setTimestamp(11, Timestamp.valueOf(message.getEdited_at())); // edited_at + else + ps.setNull(11, Types.TIMESTAMP); + + if (message.getOriginal_message_id() != null) + ps.setObject(12, message.getOriginal_message_id()); // original_message_id + else + ps.setNull(12, Types.OTHER); + + if (message.getForwarded_by() != null) + ps.setObject(13, message.getForwarded_by()); // forwarded_by + else + ps.setNull(13, Types.OTHER); + + if (message.getForwarded_from() != null) + ps.setObject(14, message.getForwarded_from()); // forwarded_from + else + ps.setNull(14, Types.OTHER); + + ps.setBoolean(15, message.getIs_deleted_globally()); // is_deleted_globally return ps.executeUpdate() > 0; diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index 1b97b58..d27e297 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -161,7 +161,7 @@ public class JsonUtil { obj.put("last_message_time", entry.getLastMessageTime() == null ? JSONObject.NULL : entry.getLastMessageTime().toString()); obj.put("is_owner", entry.isOwner()); obj.put("is_admin", entry.isAdmin()); - + obj.put("is_saved_messages", entry.isSavedMessages()); jsonArray.put(obj); } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 7480ff7..e1b8628 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -179,6 +179,10 @@ public class ClientHandler implements Runnable { ); entry.setOtherUserId(otherId); + if (currentUser.getInternal_uuid() == otherId) { + entry.setSavedMessages(true); + } + if (archivedChatIds.contains(chat.getChat_id())) { archivedChatList.add(entry); chatList.add(entry); @@ -691,6 +695,8 @@ public class ClientHandler implements Runnable { LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private"); + boolean isSavedMessages = chat.getUser1_id().equals(currentUser.getInternal_uuid()) + && chat.getUser2_id().equals(currentUser.getInternal_uuid()); ChatEntry entry = new ChatEntry( chat.getChat_id(), @@ -704,6 +710,11 @@ public class ClientHandler implements Runnable { ); entry.setOtherUserId(otherId); + // Mark it as saved messages if it's the special self-chat + if (isSavedMessages) { + entry.setSavedMessages(true); + } + if (archivedChatIds.contains(chat.getChat_id())) { archivedChatList.add(entry); chatList.add(entry); @@ -2106,9 +2117,9 @@ public class ClientHandler implements Runnable { break; } - case "send_message": { - response = SidebarService.handleSendMessage(requestJson); - break; + case "send_saved_messages": { + response = SidebarService.handleSendMessage(requestJson); + break; } default: diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index a63c05b..ce07eb2 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -9,7 +9,9 @@ import org.to.telegramfinalproject.Models.Message; import org.to.telegramfinalproject.Models.ResponseModel; import org.to.telegramfinalproject.Models.User; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -165,7 +167,7 @@ public class SidebarService { return new ResponseModel("error", "Failed to create or find saved messages chat."); } - List messages = MessageDatabase.privateChatHistory(userId, userId); + List messages = MessageDatabase.privateChatHistory(chatId); JSONArray messageArray = new JSONArray(); if (!messages.isEmpty()) { @@ -177,7 +179,6 @@ public class SidebarService { msgJson.put("receiver_id", msg.getReceiver_id().toString()); msgJson.put("content", msg.getContent()); msgJson.put("message_type", msg.getMessage_type()); - msgJson.put("file_url", msg.getFile_url() != null ? msg.getFile_url() : JSONObject.NULL); msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime msgJson.put("status", msg.getStatus()); msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); @@ -212,17 +213,22 @@ public class SidebarService { UUID.fromString(requestJson.getString("receiver_id")), requestJson.optString("content", null), requestJson.optString("message_type", "TEXT"), - requestJson.isNull("file_url") ? null : requestJson.getString("file_url"), LocalDateTime.now(), // send_at requestJson.optString("status", "SEND"), requestJson.isNull("reply_to_id") ? null : UUID.fromString(requestJson.getString("reply_to_id")), requestJson.optBoolean("is_edited", false), requestJson.isNull("original_message_id") ? null : UUID.fromString(requestJson.getString("original_message_id")), requestJson.isNull("forwarded_by") ? null : UUID.fromString(requestJson.getString("forwarded_by")), - requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")) - ); + requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")), + requestJson.optBoolean("is_deleted_globally", false), + requestJson.isNull("edited_at") ? null : + LocalDateTime.ofInstant( + Instant.ofEpochMilli(requestJson.getLong("edited_at")), + ZoneId.systemDefault() + ) + ); - MessageDatabase.save(message); + MessageDatabase.insertSavedMessage(message); return new ResponseModel("success", "Message saved successfully."); } catch (Exception e) {