From fb43adff7a35d4c871f489148e756974dba74bb2 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Mon, 11 Aug 2025 17:09:52 +0330 Subject: [PATCH 01/11] Message class updated. --- .../java/org/to/telegramfinalproject/Models/Message.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Models/Message.java b/src/main/java/org/to/telegramfinalproject/Models/Message.java index a519e33..cfbed22 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/Message.java +++ b/src/main/java/org/to/telegramfinalproject/Models/Message.java @@ -202,12 +202,5 @@ public class Message { public boolean getIs_deleted_globally() { return is_deleted_globally; } -// -// public void setEdited_at(LocalDateTime edited_at) { -// this.edited_at = edited_at; -// } -// -// public LocalDateTime getEdited_at() { -// return edited_at; -// } + } From 42d98c53d8ae185c309e17ae996d50e3069c769d Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Mon, 11 Aug 2025 20:52:39 +0330 Subject: [PATCH 02/11] Fixed save message --- .../Client/ActionHandler.java | 549 ++++++++++++------ .../Client/SidebarHandler.java | 327 ++++++++--- .../Database/PrivateChatDatabase.java | 78 ++- .../Models/PrivateChat.java | 8 + .../Server/ClientHandler.java | 104 +++- .../Server/SidebarService.java | 87 ++- 6 files changed, 818 insertions(+), 335 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 16f0235..70db64c 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -339,6 +339,7 @@ public class ActionHandler { if (chat.has("other_user_id") && !chat.isNull("other_user_id")) { entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); } + entry.setSavedMessages(chat.optBoolean("is_saved_messages", false)); return entry; } @@ -479,80 +480,96 @@ public class ActionHandler { JSONArray Archived = Session.currentUser.getJSONArray("archived_chat_list"); JSONArray Active = Session.currentUser.getJSONArray("active_chat_list"); JSONArray contactList = Session.currentUser.getJSONArray("contact_list"); +// List chatList = 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) +// +// +// ); +// +// if (chat.has("other_user_id")) { +// 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); +// +// } +// +// List archivedChats = new ArrayList<>(); +// for (Object obj : Archived){ +// 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) +// ); +// if (chat.has("other_user_id")) { +// entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); +// } +// archivedChats.add(entry); +// +// } +// +// List activeChats = new ArrayList<>(); +// for (Object obj : Active){ +// 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) +// ); +// if (chat.has("other_user_id")) { +// entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); +// } +// +// activeChats.add(entry); +// +// } + List chatList = 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) - - - ); - - if (chat.has("other_user_id")) { - 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); - + chatList.add(parseChatEntry((JSONObject) obj)); } List archivedChats = new ArrayList<>(); - for (Object obj : Archived){ - 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) - ); - if (chat.has("other_user_id")) { - entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); - } - archivedChats.add(entry); - + for (Object obj : Archived) { + archivedChats.add(parseChatEntry((JSONObject) obj)); } List activeChats = new ArrayList<>(); - for (Object obj : Active){ - 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) - ); - if (chat.has("other_user_id")) { - entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); - } - - activeChats.add(entry); - + for (Object obj : Active) { + activeChats.add(parseChatEntry((JSONObject) obj)); } + Session.contactEntries.clear(); for (Object obj : contactList) { JSONObject c = (JSONObject) obj; @@ -830,6 +847,8 @@ public class ActionHandler { } + + public void userMenu(UUID internal_uuid) throws IOException { while (true) { @@ -1131,86 +1150,158 @@ public class ActionHandler { // } +// public void showChatListAndSelect() { +// if (Session.activeChats == null || Session.activeChats.isEmpty()) { +// System.out.println("No active chats."); +// return; +// } +// +// 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(index + ". [" + entry.getType() + "] " + +// entry.getName() + " - Last: " + time); +// index++; +// } +// +// System.out.print("Select a chat by number: "); +// int choice = Integer.parseInt(scanner.nextLine()); +// +// if (choice == 0) { +// showArchivedChats(); +// return; +// } +// +// if (choice == savedMessagesIndex) { +// new SidebarHandler(scanner, this).getSavedMessagesData(Session.getUserUUID()); +// return; +// } +// +// // 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; +// } +// +// // 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); +// } + + + public void showChatListAndSelect() { if (Session.activeChats == null || Session.activeChats.isEmpty()) { - System.out.println("No active chats."); + System.out.println("📭 You have no chats."); return; } 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; + // پیدا کردن Saved در لیست + Integer savedIdxInActive = null; 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++; + if (Session.activeChats.get(i).isSavedMessages()) { + savedIdxInActive = i; 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 - } + // مپ شمارهٔ نمایش → ایندکس واقعی در activeChats + Map displayToActive = new HashMap<>(); - String time = (entry.getLastMessageTime() == null) - ? "No messages yet" - : entry.getLastMessageTime().toString(); - System.out.println(index + ". [" + entry.getType() + "] " + - entry.getName() + " - Last: " + time); - index++; + int displayIndex = 1; + + // چاپ Saved (در صورت وجود) و ثبت در مپ + if (savedIdxInActive != null) { + ChatEntry saved = Session.activeChats.get(savedIdxInActive); + String last = (saved.getLastMessageTime() == null) ? "No messages yet" : saved.getLastMessageTime().toString(); + System.out.println(displayIndex + ". 💾 Saved Messages - Last: " + last); + + // نکتهٔ کلیدی: مپ کن تا مثل بقیه با openChat باز شود + displayToActive.put(displayIndex, savedIdxInActive); + displayIndex++; } + // چاپ بقیهٔ چت‌ها + ثبت مپ + for (int i = 0; i < Session.activeChats.size(); i++) { + if (savedIdxInActive != null && i == savedIdxInActive) continue; + + ChatEntry e = Session.activeChats.get(i); + String last = (e.getLastMessageTime() == null) ? "No messages yet" : e.getLastMessageTime().toString(); + System.out.println(displayIndex + ". [" + e.getType() + "] " + e.getName() + " - Last: " + last); + + displayToActive.put(displayIndex, i); + displayIndex++; + } + + // انتخاب System.out.print("Select a chat by number: "); - int choice = Integer.parseInt(scanner.nextLine()); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine().trim()); + } catch (Exception e) { + System.out.println("❌ Invalid selection."); + return; + } if (choice == 0) { showArchivedChats(); return; } - if (choice == savedMessagesIndex) { - new SidebarHandler(scanner, this).getSavedMessagesData(Session.getUserUUID()); + Integer activeIdx = displayToActive.get(choice); + if (activeIdx == null) { + System.out.println("❌ Invalid selection."); return; } - // 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; - } - - // 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); + ChatEntry selected = Session.activeChats.get(activeIdx); + openChat(selected); // برای Saved هم همین مسیر اجرا می‌شود } - private void showArchivedChats() { if (Session.archivedChats == null || Session.archivedChats.isEmpty()) { System.out.println("📭 No archived chats."); @@ -1371,6 +1462,116 @@ public class ActionHandler { +// +// private boolean showPrivateChatMenu(ChatEntry chat) { +// +// if (forceExitChat) { +// forceExitChat = false; +// System.out.println("🚪 Exiting chat due to real-time update."); +// return false; +// } +// +// +// JSONObject reqTarget = new JSONObject(); +// reqTarget.put("action", "get_private_chat_target"); +// reqTarget.put("chat_id", chat.getId()); +// +// JSONObject resTarget = sendWithResponse(reqTarget); +// if (resTarget == null || !resTarget.getString("status").equals("success")) { +// System.out.println("❌ Failed to fetch target user for private chat."); +// return false; +// } +// +// String otherUserId = resTarget.getJSONObject("data").getString("target_id"); +// chat.setOtherUserId(UUID.fromString(otherUserId)); +// +// +// JSONObject req = new JSONObject(); +// req.put("action", "view_profile"); +// req.put("target_id", chat.getOtherUserId()); // internal UUID +// +// JSONObject res = sendWithResponse(req); +// +// if (res.getString("status").equals("success")) { +// JSONObject data = res.getJSONObject("data"); +// +// System.out.println("\n💬 Private Chat with: " + data.getString("profile_name")); +// +// if (data.getBoolean("is_online")) { +// System.out.println("✅ Status: Online"); +// } else { +// System.out.println("📅 Last seen: " + data.getString("last_seen")); +// } +// +// System.out.println("────────────────────────────────────────────"); +// } +// +// +// System.out.println("1. Send message"); +// System.out.println("2. Block/Unblock"); +// System.out.println("3. Delete chat (one-sided)"); +// System.out.println("4. Delete chat (both sides)"); +// System.out.println("5. View profile"); +// System.out.println("6. Archive/Unarchived"); +// System.out.println("7. View messages"); +// System.out.println("8. Back"); +// +// +// String input = scanner.nextLine(); +// switch (input) { +// case "1" -> sendMessage(chat.getId(), "private"); +// case "2" -> toggleBlock(chat.getOtherUserId()); +// case "3" -> { +// deleteChat(chat.getId(), false); +// return true; +// } +// case "4" -> { +// deleteChat(chat.getId(), true); +// return true; +// } +// case "5" -> { +// JSONObject reqProfile = new JSONObject(); +// reqProfile.put("action", "view_profile"); +// reqProfile.put("target_id", chat.getOtherUserId()); +// +// JSONObject resProfile = sendWithResponse(reqProfile); +// +// if (resProfile.getString("status").equals("success")) { +// JSONObject profile = resProfile.getJSONObject("data"); +// System.out.println("\n👤 Profile Info:"); +// System.out.println("🔷 Name: " + profile.optString("profile_name", "Unknown")); +// System.out.println("📄 Bio: " + profile.optString("bio", "No bio set")); +// System.out.println("🖼️ Image URL: " + profile.optString("image_url", "N/A")); +// +// if (profile.getBoolean("is_online")) { +// System.out.println("✅ Status: Online"); +// } else { +// System.out.println("📅 Last seen: " + profile.optString("last_seen", "Unknown")); +// } +// System.out.println("────────────────────────────────────────────"); +// } else { +// System.out.println("❌ Could not load profile."); +// } +// return true; +// } +// +// case "6" ->{ +// toggleArchive(chat.getId() , "private"); +// +// return true; +// } +// case "7" ->{ +// viewMessagesInChat(chat); +// } +// +// case "8" -> { +// return false; +// } +// default -> System.out.println("Invalid choice."); +// } +// return true; +// } + private boolean showPrivateChatMenu(ChatEntry chat) { @@ -1380,41 +1581,57 @@ public class ActionHandler { return false; } + UUID chatId = chat.getId(); - JSONObject reqTarget = new JSONObject(); - reqTarget.put("action", "get_private_chat_target"); - reqTarget.put("chat_id", chat.getId()); + if (chat.getOtherUserId() == null) { + JSONObject reqTarget = new JSONObject() + .put("action", "get_private_chat_target") + .put("chat_id", chatId.toString()); JSONObject resTarget = sendWithResponse(reqTarget); - if (resTarget == null || !resTarget.getString("status").equals("success")) { + if (resTarget == null || !"success".equals(resTarget.optString("status"))) { System.out.println("❌ Failed to fetch target user for private chat."); return false; } - - String otherUserId = resTarget.getJSONObject("data").getString("target_id"); - chat.setOtherUserId(UUID.fromString(otherUserId)); - - - JSONObject req = new JSONObject(); - req.put("action", "view_profile"); - req.put("target_id", chat.getOtherUserId()); // internal UUID - - JSONObject res = sendWithResponse(req); - - if (res.getString("status").equals("success")) { - JSONObject data = res.getJSONObject("data"); - - System.out.println("\n💬 Private Chat with: " + data.getString("profile_name")); - - if (data.getBoolean("is_online")) { - System.out.println("✅ Status: Online"); - } else { - System.out.println("📅 Last seen: " + data.getString("last_seen")); - } - - System.out.println("────────────────────────────────────────────"); + chat.setOtherUserId(UUID.fromString(resTarget.getJSONObject("data").getString("target_id"))); } + UUID me = UUID.fromString(Session.currentUser.getString("internal_uuid")); + boolean isSaved = chat.isSavedMessages() || me.equals(chat.getOtherUserId()); + + System.out.println(); + if (isSaved) { + System.out.println("💬 Private Chat with: Saved Messages"); + System.out.println("────────────────────────────────────────────"); + } else { + JSONObject req = new JSONObject() + .put("action", "view_profile") + .put("target_id", chat.getOtherUserId()); + + JSONObject res = sendWithResponse(req); + if (res != null && "success".equals(res.optString("status"))) { + JSONObject data = res.getJSONObject("data"); + System.out.println("\n💬 Private Chat with: " + data.optString("profile_name", chat.getName())); + if (data.optBoolean("is_online", false)) System.out.println("✅ Status: Online"); + else System.out.println("📅 Last seen: " + data.optString("last_seen", "Unknown")); + System.out.println("────────────────────────────────────────────"); + } + } + + if (isSaved) { + System.out.println("1. Send message"); + System.out.println("2. View messages"); + System.out.println("3. Back"); + + String input = scanner.nextLine().trim(); + switch (input) { + case "1" -> sendMessage(chatId, "private"); + case "2" -> { viewMessagesInChat(chat); } + case "3" -> { return false; } + default -> System.out.println("Invalid choice."); + } + return true; + } System.out.println("1. Send message"); System.out.println("2. Block/Unblock"); @@ -1425,57 +1642,35 @@ public class ActionHandler { System.out.println("7. View messages"); System.out.println("8. Back"); - - String input = scanner.nextLine(); + String input = scanner.nextLine().trim(); switch (input) { - case "1" -> sendMessage(chat.getId(), "private"); + case "1" -> sendMessage(chatId, "private"); case "2" -> toggleBlock(chat.getOtherUserId()); - case "3" -> { - deleteChat(chat.getId(), false); - return true; - } - case "4" -> { - deleteChat(chat.getId(), true); - return true; - } + case "3" -> { deleteChat(chatId, false); return true; } + case "4" -> { deleteChat(chatId, true); return true; } case "5" -> { - JSONObject reqProfile = new JSONObject(); - reqProfile.put("action", "view_profile"); - reqProfile.put("target_id", chat.getOtherUserId()); + JSONObject reqProfile = new JSONObject() + .put("action", "view_profile") + .put("target_id", chat.getOtherUserId()); JSONObject resProfile = sendWithResponse(reqProfile); - - if (resProfile.getString("status").equals("success")) { + if (resProfile != null && "success".equals(resProfile.optString("status"))) { JSONObject profile = resProfile.getJSONObject("data"); System.out.println("\n👤 Profile Info:"); System.out.println("🔷 Name: " + profile.optString("profile_name", "Unknown")); System.out.println("📄 Bio: " + profile.optString("bio", "No bio set")); System.out.println("🖼️ Image URL: " + profile.optString("image_url", "N/A")); - - if (profile.getBoolean("is_online")) { - System.out.println("✅ Status: Online"); - } else { - System.out.println("📅 Last seen: " + profile.optString("last_seen", "Unknown")); - } + if (profile.optBoolean("is_online", false)) System.out.println("✅ Status: Online"); + else System.out.println("📅 Last seen: " + profile.optString("last_seen", "Unknown")); System.out.println("────────────────────────────────────────────"); } else { System.out.println("❌ Could not load profile."); } return true; } - - case "6" ->{ - toggleArchive(chat.getId() , "private"); - - return true; - } - case "7" ->{ - viewMessagesInChat(chat); - } - - case "8" -> { - return false; - } + case "6" -> { toggleArchive(chatId, "private"); return true; } + case "7" -> { viewMessagesInChat(chat); break; } + case "8" -> { return false; } default -> System.out.println("Invalid choice."); } return true; diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 652b8b7..302289b 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -4,6 +4,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; +import org.to.telegramfinalproject.Models.FileAttachment; import org.to.telegramfinalproject.Models.Message; import java.time.LocalDateTime; @@ -13,9 +14,16 @@ import static org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse; public class SidebarHandler { private final Scanner scanner; - private final ActionHandler actionHandler; + private ActionHandler actionHandler ; private final String userUUID; + public SidebarHandler(Scanner scanner, ActionHandler actionHandler, ActionHandler action) { + this.scanner = scanner; + this.actionHandler = action; + this.userUUID = Session.getUserUUID(); + + } + public SidebarHandler(Scanner scanner, ActionHandler actionHandler) { this.scanner = scanner; this.actionHandler = actionHandler; @@ -38,7 +46,7 @@ public class SidebarHandler { showContacts(); break; case SAVED_MESSAGES: - getSavedMessagesData(userUUID); + ensureSavedMessagesCreated(); break; case SETTINGS: openSettings(); @@ -347,110 +355,183 @@ public class SidebarHandler { } private void showSavedMessages(UUID chatId, List messages) { - Scanner scanner = new Scanner(System.in); +// Scanner scanner = new Scanner(System.in); +// +// System.out.println("==== Saved Messages ===="); +// if (messages == null || messages.isEmpty()) { +// System.out.println("No messages yet."); +// } else { +// for (Message msg : messages) { +// System.out.println("[" + msg.getSend_at() + "] " + msg.getContent()); +// } +// } +// +// System.out.println("\n(Type your message below, or type 0 to exit)"); +// +// while (true) { +// System.out.print("You: "); +// String content = scanner.nextLine().trim(); +// if (content.equals("0")) { +// System.out.println("Exiting Saved Messages."); +// break; +// } +// +// System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); +// String messageType = scanner.nextLine().trim().toUpperCase(); +// Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE", "AUDIO"); +// while (!allowedTypes.contains(messageType)) { +// System.out.print("Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); +// messageType = scanner.nextLine().trim().toUpperCase(); +// } +// +// // Attachments (اختیاری) +// 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().trim(); +// +// if (fileUrl.isEmpty()) { +// System.out.println("URL can not be empty. Try again."); +// continue; +// } +// if (fileUrl.contains(" ")) { +// System.out.println("URL cannot contain spaces. Try again."); +// continue; +// } +// if (!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().trim().toUpperCase(); +// Set allowedFileTypes = Set.of("IMAGE", "VIDEO", "FILE", "AUDIO"); +// while (!allowedFileTypes.contains(fileType)) { +// System.out.print("Invalid type. Try again (IMAGE / VIDEO / FILE / AUDIO): "); +// fileType = scanner.nextLine().trim().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; +// } +// } +// +// // درخواست مطابق هندلر send_message +// JSONObject request = new JSONObject(); +// request.put("action", "send_message"); +// request.put("receiver_type", "private"); +// request.put("receiver_id", chatId.toString()); // chat_id +// request.put("content", content); +// request.put("message_type", messageType); +// if (attachmentsArray.length() > 0) { +// request.put("attachments", attachmentsArray); +// } +// +// JSONObject response = ActionHandler.sendWithResponse(request); +// if (!"success".equalsIgnoreCase(response.optString("status"))) { +// System.out.println("Failed to send message: " + response.optString("message", "Unknown error")); +// } else { +// System.out.println("Message sent."); +// +// Message justSent = new Message( +// UUID.fromString(response.getJSONObject("data").getString("message_id")), +// /* senderId */ userUUID, +// /* receiverId */ chatId, +// /* type */ "private", +// /* content */ content, +// /* msgType */ messageType, +// /* send_at */ java.time.LocalDateTime.now() +// ); +// messages.add(justSent); +// System.out.println("[" + justSent.getSend_at() + "] " + justSent.getContent()); +// } +// } + } - System.out.println("==== Saved Messages ===="); - // Show previous messages - if (messages.isEmpty()) { - System.out.println("No messages yet."); - } else { - for (Message msg : messages) { - System.out.println("[" + msg.getSend_at() + "] " + msg.getContent()); +// public void openSavedMessages() { +// JSONObject req = new JSONObject().put("action", "get_or_create_saved_messages"); +// JSONObject res = sendWithResponse(req); +// if (res == null || !"success".equals(res.optString("status"))) { +// System.out.println("❌ Could not open Saved Messages: " + res.optString("message","")); +// return; +// } +// +// ActionHandler.requestChatList(); +// String chatId = res.getJSONObject("data").getString("chat_id"); +// +// JSONObject mreq = new JSONObject() +// .put("action", "get_messages") +// .put("receiver_type", "private") +// .put("receiver_id", chatId) +// .put("offset", 0) +// .put("limit", 50); +// +// JSONObject mres = sendWithResponse(mreq); +// JSONArray msgs = (mres != null && mres.has("data")) +// ? mres.getJSONObject("data").optJSONArray("messages") +// : new JSONArray(); +// +// List messages = parseMessages(msgs); // تبدیل JSON → Message +// showSavedMessages(UUID.fromString(chatId), messages); +// } +// + + + + public void ensureSavedMessagesCreated() { + // 1) Client-side quick check + boolean alreadyExists = false; + if (Session.activeChats != null) { + for (ChatEntry e : Session.activeChats) { + if (e != null && e.isSavedMessages()) { alreadyExists = true; break; } } } + if (alreadyExists) { + System.out.println("ℹ️ Saved Messages already exists."); + return; + } - System.out.println("\n(Type your message below, or type 0 to exit)"); + // 2) Ask server to get or create the self-chat + JSONObject res = sendWithResponse(new JSONObject().put("action", "get_or_create_saved_messages")); + if (res == null || !"success".equals(res.optString("status"))) { + System.out.println("❌ Failed to create/open Saved Messages: " + (res != null ? res.optString("message","") : "")); + return; + } - while (true) { - System.out.print("You: "); - String content = scanner.nextLine().trim(); + JSONObject data = res.optJSONObject("data"); + if (data == null) { + System.out.println("❌ Invalid server response for Saved Messages."); + return; + } - if (content.equals("0")) { - System.out.println("Exiting Saved Messages."); - break; - } + boolean created = data.optBoolean("created", true); // if server sends it + String chatId = data.optString("chat_id", null); - 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_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("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); - - if (!response.optString("status", "fail").equals("success")) { - System.out.println("Failed to send message: " + response.optString("message", "Unknown error")); - } else { - System.out.println("Message sent."); - } + if (created) { + System.out.println("✅ Saved Messages created." + (chatId != null ? " chat_id=" + chatId : "")); + try { + UUID cid = UUID.fromString(chatId); + ChatEntry saved = new ChatEntry(cid, "Saved Messages", "Saved Messages", "", "private", null, true, false); + saved.setSavedMessages(true); + if (Session.activeChats == null) Session.activeChats = new ArrayList<>(); + Session.activeChats.add(0, saved); + } catch (Exception ignore) {} + } else { + System.out.println("ℹ️ Saved Messages already exists." + (chatId != null ? " chat_id=" + chatId : "")); } } + + + private void openSettings() { System.out.println("⚙️ Opening settings..."); } @@ -462,4 +543,60 @@ public class SidebarHandler { private void showTelegramQA() { System.out.println("❓ Showing Q&A..."); } + + + private List parseMessages(JSONArray msgs) { + List list = new ArrayList<>(); + if (msgs == null) return list; + + for (int i = 0; i < msgs.length(); i++) { + try { + JSONObject obj = msgs.getJSONObject(i); + + UUID messageId = UUID.fromString(obj.getString("message_id")); + UUID senderId = UUID.fromString(obj.getString("sender_id")); + String receiverType= obj.getString("receiver_type"); // "private" | "group" | "channel" + UUID receiverId = UUID.fromString(obj.getString("receiver_id")); // برای private = chat_id + String content = obj.optString("content", ""); + String messageType = obj.optString("message_type", "TEXT"); + LocalDateTime sent = LocalDateTime.parse(obj.getString("send_at")); + + Message m = new Message( + messageId, senderId, receiverId, receiverType, content, messageType, sent + ); + + if (obj.has("status") && !obj.isNull("status")) { + try { m.setStatus(obj.getString("status")); } catch (Exception ignore) {} + } + if (obj.has("reply_to_id") && !obj.isNull("reply_to_id")) { + try { m.setReply_to_id(UUID.fromString(obj.getString("reply_to_id"))); } catch (Exception ignore) {} + } + if (obj.has("forwarded_by") && !obj.isNull("forwarded_by")) { + try { m.setForwarded_by(UUID.fromString(obj.getString("forwarded_by"))); } catch (Exception ignore) {} + } + if (obj.has("forwarded_from") && !obj.isNull("forwarded_from")) { + try { m.setForwarded_from(UUID.fromString(obj.getString("forwarded_from"))); } catch (Exception ignore) {} + } + +// // ضمیمه‌ها (اگر در مدل Message متد addAttachment داری) +// if (obj.has("attachments") && !obj.isNull("attachments")) { +// try { +// JSONArray atts = obj.getJSONArray("attachments"); +// for (int j = 0; j < atts.length(); j++) { +// JSONObject a = atts.getJSONObject(j); +// String fileUrl = a.getString("file_url"); +// String fileType = a.getString("file_type"); +// FileAttachment fa = new FileAttachment(fileUrl, fileType); +// try { m.addAttachment(fa); } catch (Exception ignore) {} +// } +// } catch (Exception ignore) {} +// } + + list.add(m); + } catch (Exception perItem) { + perItem.printStackTrace(); + } + } + return list; + } } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java index 6b2ab1f..a666c34 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java @@ -307,12 +307,86 @@ public class PrivateChatDatabase { return null; } +// public static UUID getOtherParticipant(UUID chatId, UUID me) { +// List members = getMembers(chatId); +// for (UUID u : members) { +// if (!u.equals(me)) return u; +// } +// return null; +// } + public static UUID getOtherParticipant(UUID chatId, UUID me) { - List members = getMembers(chatId); + List members = getMembers(chatId); // باید [user1_id, user2_id] بده + if (members == null || members.isEmpty()) return null; + + boolean isMember = false; + UUID other = null; + for (UUID u : members) { - if (!u.equals(me)) return u; + if (u == null) continue; + if (u.equals(me)) { + isMember = true; + } else { + other = u; + } } + + if (!isMember) return null; + return (other != null) ? other + : me; + } + + + + + + public static PrivateChat findSelfChat(UUID userId) { + String sql = """ + SELECT chat_id, user1_id, user2_id, user1_deleted, user2_deleted + FROM private_chat + WHERE user1_id = ? AND user2_id = ? + LIMIT 1 + """; + try (Connection c = ConnectionDb.connect(); + PreparedStatement ps = c.prepareStatement(sql)) { + ps.setObject(1, userId); + ps.setObject(2, userId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) return map(rs); + } + } catch (SQLException e) { e.printStackTrace(); } return null; } + public static UUID createSelfChat(UUID userId) { + String sql = """ + INSERT INTO private_chat (chat_id, user1_id, user2_id, user1_deleted, user2_deleted, created_at) + VALUES (gen_random_uuid(), ?, ?, FALSE, FALSE, NOW()) + RETURNING chat_id + """; + try (Connection c = ConnectionDb.connect(); + PreparedStatement ps = c.prepareStatement(sql)) { + ps.setObject(1, userId); + ps.setObject(2, userId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) return (UUID) rs.getObject("chat_id"); + } + } catch (SQLException e) { e.printStackTrace(); } + return null; + } + + + + + + private static PrivateChat map(ResultSet rs) throws SQLException { + return new PrivateChat( + (UUID) rs.getObject("chat_id"), + (UUID) rs.getObject("user1_id"), + (UUID) rs.getObject("user2_id"), + rs.getBoolean("user1_deleted"), + rs.getBoolean("user2_deleted") + ); + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java index 17ca2a9..79751f5 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java +++ b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java @@ -27,6 +27,14 @@ public class PrivateChat { this.created_at = createdAt; } + public PrivateChat(UUID chatId, UUID user1, UUID user2, boolean user1Deleted, boolean user2Deleted) { + this.chat_id = chatId; + this.user1_id = user1; + this.user2_id = user2; + this.user1_deleted = user1Deleted; + this.user2_deleted = user2Deleted; + } + public void setUser1_id(UUID user1_id){this.user1_id =user1_id;} public void setUser2_id(UUID user2_id){this.user2_id =user2_id;} diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 44cfaac..4a10df4 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -159,45 +159,91 @@ public class ClientHandler implements Runnable { // } +// +// List 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 privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid()); + + ChatEntry savedEntry = null; + for (PrivateChat chat : privateChats) { - UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ? - chat.getUser2_id() : chat.getUser1_id(); + boolean isSelf = + chat.getUser1_id().equals(chat.getUser2_id()) && + chat.getUser1_id().equals(currentUser.getInternal_uuid()); + + // در غیر self، otherId = طرف مقابل + UUID otherId = isSelf + ? currentUser.getInternal_uuid() + : (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(), + ChatEntry entry = new ChatEntry( + chat.getChat_id(), // internal_id = chat_id + isSelf ? "Saved Messages" : otherUser.getUser_id(), // id/display + isSelf ? "Saved Messages" : otherUser.getProfile_name(), // name + isSelf ? null : otherUser.getImage_url(), "private", lastMessageTime, - false, - false + false, // isOwner + false // isAdmin ); entry.setOtherUserId(otherId); - - if (currentUser.getInternal_uuid() == otherId) { - entry.setSavedMessages(true); + if (isSelf) { + entry.setSavedMessages(true); // فلگ مهم + savedEntry = entry; // برای بردن به اول لیست } - if (archivedChatIds.contains(chat.getChat_id())) { + if (!isSelf && archivedChatIds.contains(chat.getChat_id())) { archivedChatList.add(entry); - chatList.add(entry); } else { activeChatList.add(entry); - chatList.add(entry); } + chatList.add(entry); } + for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), user.getInternal_uuid()); @@ -2497,7 +2543,7 @@ public class ClientHandler implements Runnable { case "get_saved_messages": { UUID user_Id = UUID.fromString(requestJson.getString("user_id")); -// response = SidebarService.handleGetSavedMessages(user_Id); + response = SidebarService.handleGetSavedMessages(user_Id); break; } @@ -2522,6 +2568,12 @@ public class ClientHandler implements Runnable { break; } + case "get_or_create_saved_messages": { + response = handleGetOrCreateSavedMessages(requestJson); + } + break; + + default: response = new ResponseModel("error", "Unknown action: " + action); } @@ -2603,6 +2655,7 @@ public class ClientHandler implements Runnable { } + String content = json.optString("content", ""); String messageType = json.optString("message_type", "TEXT"); @@ -2716,5 +2769,22 @@ public class ClientHandler implements Runnable { + private ResponseModel handleGetOrCreateSavedMessages(JSONObject req) { + if (currentUser == null) return new ResponseModel("error", "Unauthorized."); + + UUID uid = currentUser.getInternal_uuid(); + PrivateChat chat = PrivateChatDatabase.findSelfChat(uid); + UUID chatId = (chat != null) ? chat.getChat_id() : PrivateChatDatabase.createSelfChat(uid); + if (chatId == null) return new ResponseModel("error", "Failed to create Saved Messages."); + + JSONObject data = new JSONObject(); + data.put("chat_id", chatId.toString()); + data.put("name", "Saved Messages"); + data.put("is_saved_messages", true); + data.put("chat_type", "private"); + return new ResponseModel("success", "Saved Messages ready.", data); + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index a10035e..94fb542 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -211,50 +211,49 @@ public class SidebarService { } } - // Get saved messages data -// public static ResponseModel handleGetSavedMessages(UUID userId) { -// try { -// -// UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId); -// if (chatId == null) { -// return new ResponseModel("error", "Failed to create or find saved messages chat."); -// } -// -// List messages = MessageDatabase.privateChatHistory(chatId); -// -// JSONArray messageArray = new JSONArray(); -// if (!messages.isEmpty()) { -// for (Message msg : messages) { -// JSONObject msgJson = new JSONObject(); -// msgJson.put("message_id", msg.getMessage_id().toString()); -// msgJson.put("sender_id", msg.getSender_id().toString()); -// msgJson.put("receiver_type", msg.getReceiver_type()); -// msgJson.put("receiver_id", msg.getReceiver_id().toString()); -// msgJson.put("content", msg.getContent()); -// msgJson.put("message_type", msg.getMessage_type()); -// msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime -// msgJson.put("status", msg.getStatus()); -// msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); -// msgJson.put("is_edited", msg.isIs_edited()); -// msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL); -// msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL); -// msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL); -// -// messageArray.put(msgJson); -// } -// } -// -// JSONObject data = new JSONObject(); -// data.put("chat_id", chatId.toString()); -// data.put("messages", messageArray); -// -// return new ResponseModel("success", "Saved messages retrieved successfully", data); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return new ResponseModel("error", "Unexpected server error."); -// } -// } + public static ResponseModel handleGetSavedMessages(UUID userId) { + try { + + UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId); + if (chatId == null) { + return new ResponseModel("error", "Failed to create or find saved messages chat."); + } + + List messages = MessageDatabase.privateChatHistory(chatId, userId); + + JSONArray messageArray = new JSONArray(); + if (!messages.isEmpty()) { + for (Message msg : messages) { + JSONObject msgJson = new JSONObject(); + msgJson.put("message_id", msg.getMessage_id().toString()); + msgJson.put("sender_id", msg.getSender_id().toString()); + msgJson.put("receiver_type", msg.getReceiver_type()); + msgJson.put("receiver_id", msg.getReceiver_id().toString()); + msgJson.put("content", msg.getContent()); + msgJson.put("message_type", msg.getMessage_type()); + msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime + msgJson.put("status", msg.getStatus()); + msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); + msgJson.put("is_edited", msg.isIs_edited()); + msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL); + msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL); + msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL); + + messageArray.put(msgJson); + } + } + + JSONObject data = new JSONObject(); + data.put("chat_id", chatId.toString()); + data.put("messages", messageArray); + + return new ResponseModel("success", "Saved messages retrieved successfully", data); + + } catch (Exception e) { + e.printStackTrace(); + return new ResponseModel("error", "Unexpected server error."); + } + } // Save messages to DB public static ResponseModel handleSendMessage(JSONObject requestJson) { From 82776814311fc29777984409c6bb953092316637 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Mon, 11 Aug 2025 21:29:34 +0330 Subject: [PATCH 03/11] Fixed save message --- .../to/telegramfinalproject/Server/ClientHandler.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 4a10df4..b57313e 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -2120,6 +2120,15 @@ public class ClientHandler implements Runnable { userId = currentUser.getInternal_uuid(); UUID targetId = PrivateChatDatabase.getOtherUserInChat(chatId, userId); + if (targetId == null) { + PrivateChat chat = PrivateChatDatabase.findById(chatId); + if (chat != null + && (userId.equals(chat.getUser1_id()) || userId.equals(chat.getUser2_id())) + && chat.getUser1_id().equals(chat.getUser2_id())) { + targetId = userId; + } + } + if (targetId == null) { response = new ResponseModel("error", "Could not find other user."); } else { From 34c14a0d808aa0f2c111acd4705d5a951c471ddb Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 12 Aug 2025 15:50:21 +0330 Subject: [PATCH 04/11] Work on telegram setting --- .../Client/SidebarHandler.java | 263 +++++++++++++++++- .../Database/ContactDatabase.java | 27 ++ .../Database/userDatabase.java | 37 +++ .../Server/ClientHandler.java | 87 +++++- 4 files changed, 398 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 302289b..5085e14 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -531,9 +531,264 @@ public class SidebarHandler { - private void openSettings() { - System.out.println("⚙️ Opening settings..."); + while (true) { + System.out.println("⚙️ Settings\n"); + + String imageUrl = Session.currentUser.optString("image_url", "—"); + String profileName = Session.currentUser.optString("profile_name", "—"); + String userId = Session.currentUser.optString("user_id", "—"); + + renderUserCard(imageUrl, profileName, userId); + System.out.println(); + renderMenu(); + + System.out.print("\nChoose an option (0-4): "); + String pick = scanner.nextLine().trim(); + + switch (pick) { + case "1": openUserProfile(); break; + case "2": showPrivacySettings(); break; + case "3": showTelegramQA(); break; + case "4": showTelegramFeatures(); break; + case "0": return; + default: + System.out.println("❌ Invalid choice. Press Enter to continue..."); + scanner.nextLine(); + } + } + } + + + + private void showPrivacySettings() { + while (true) { + System.out.println("🔒 Privacy\n"); + System.out.println("1) Blocked users"); + System.out.println("2) Change username / password"); + System.out.println("0) Back"); + System.out.print("\nChoose: "); + String pick = scanner.nextLine().trim(); + + switch (pick) { + case "1": viewBlockedUsers(); break; + case "2": changeCredentialsFlow(); break; + case "0": return; + default: + System.out.println("❌ Invalid choice. Press Enter..."); + scanner.nextLine(); + } + } + } + + private void viewBlockedUsers() { + System.out.println("🚫 Blocked Users\n"); + + org.json.JSONObject req = new org.json.JSONObject(); + req.put("action", "get_blocked_users"); + + org.json.JSONObject res = sendWithResponse(req); + if (res == null || !res.optString("status","error").equals("success")) { + System.out.println("❌ Failed to fetch blocked users. Press Enter..."); + scanner.nextLine(); + return; + } + + org.json.JSONArray arr = res.getJSONObject("data").optJSONArray("blocked_users"); + if (arr == null || arr.isEmpty()) { + System.out.println("📭 No blocked users."); + System.out.println("\nPress Enter..."); + scanner.nextLine(); + return; + } + + for (int i = 0; i < arr.length(); i++) { + org.json.JSONObject u = arr.getJSONObject(i); + String profileName = u.optString("profile_name", "—"); + String userId = u.optString("user_id", "—"); + System.out.printf("%d) %s (@%s)\n", i + 1, profileName, userId); + } + System.out.println("\n0) Back"); + System.out.print("\nChoose a user to UNBLOCK (number): "); + String pick = scanner.nextLine().trim(); + + if (pick.equals("0")) return; + int idx; + try { idx = Integer.parseInt(pick) - 1; } catch (Exception e) { idx = -1; } + if (idx < 0 || idx >= arr.length()) { + System.out.println("❌ Invalid index. Press Enter..."); + scanner.nextLine(); + return; + } + + org.json.JSONObject target = arr.getJSONObject(idx); + String targetDisplayId = target.optString("user_id", ""); + java.util.UUID targetInternalId = java.util.UUID.fromString(target.getString("internal_uuid")); + + System.out.printf("Unblock %s (@%s)? (yes/no): ", target.optString("profile_name","—"), targetDisplayId); + if (!scanner.nextLine().trim().equalsIgnoreCase("yes")) return; + + org.json.JSONObject unReq = new org.json.JSONObject(); + unReq.put("action", "toggle_block"); + unReq.put("user_id",Session.getUserUUID()); + unReq.put("target_id", targetInternalId.toString()); + + org.json.JSONObject unRes = sendWithResponse(unReq); + if (unRes != null && unRes.optString("status","error").equals("success")) { + System.out.println("✅ User unblocked."); + } else { + System.out.println("❌ Failed to unblock."); + } + System.out.println("Press Enter..."); + scanner.nextLine(); + } + + + private void changeCredentialsFlow() { + System.out.println("🛡️ Change Username / Password\n"); + + System.out.print("Enter current password: "); + String currentPassword = scanner.nextLine(); + + org.json.JSONObject verReq = new org.json.JSONObject(); + verReq.put("action", "verify_password"); + verReq.put("current_password", currentPassword); + + org.json.JSONObject verRes = sendWithResponse(verReq); + if (verRes == null || !verRes.optString("status","error").equals("success")) { + System.out.println("❌ Current password is incorrect."); + System.out.println("Press Enter..."); + scanner.nextLine(); + return; + } + + String currentUsername = Session.currentUser.optString("username", "—"); + System.out.println("\n✅ Verified."); + System.out.println("Current username: " + currentUsername); + System.out.println("Current password: ******** (hidden)"); + System.out.println("\nWhat do you want to change?"); + System.out.println("1) Username"); + System.out.println("2) Password"); + System.out.println("3) Both"); + System.out.println("0) Back"); + System.out.print("\nChoose: "); + String pick = scanner.nextLine().trim(); + + switch (pick) { + case "1": + changeUsername(currentPassword); + break; + case "2": + changePassword(currentPassword); + break; + case "3": + boolean uOk = changeUsername(currentPassword); + boolean pOk = changePassword(currentPassword); + if (uOk && pOk) System.out.println("✅ Username and password updated."); + System.out.println("Press Enter..."); + scanner.nextLine(); + break; + case "0": + return; + default: + System.out.println("❌ Invalid choice. Press Enter..."); + scanner.nextLine(); + } + } + + + private boolean changeUsername(String currentPassword) { + System.out.print("\nNew username: "); + String newUsername = scanner.nextLine().trim(); + + if (!isValidUsername(newUsername)) { + System.out.println("❌ Invalid username. Use 4–32 chars: letters, digits, underscore."); + return false; + } + + org.json.JSONObject req = new org.json.JSONObject(); + req.put("action", "update_username"); + req.put("current_password", currentPassword); + req.put("new_username", newUsername); + + org.json.JSONObject res = sendWithResponse(req); + if (res != null && res.optString("status","error").equals("success")) { + Session.currentUser.put("username", newUsername); + System.out.println("✅ Username updated."); + return true; + } else { + String msg = (res == null) ? "No response." : res.optString("message","Update failed."); + System.out.println("❌ " + msg); + return false; + } + } + + private boolean isValidUsername(String s) { + //4-32 char + return s != null && s.matches("^[A-Za-z0-9_]{4,32}$"); + } + + private boolean changePassword(String currentPassword) { + System.out.print("\nNew password: "); + String newPassword = scanner.nextLine(); + System.out.print("Repeat new password: "); + String repeat = scanner.nextLine(); + + if (!newPassword.equals(repeat)) { + System.out.println("❌ Passwords do not match."); + return false; + } + if (!isStrongPassword(newPassword)) { + System.out.println("❌ Weak password. Min 8 chars, include letters and digits."); + return false; + } + + org.json.JSONObject req = new org.json.JSONObject(); + req.put("action", "update_password"); + req.put("current_password", currentPassword); + req.put("new_password", newPassword); + + org.json.JSONObject res = sendWithResponse(req); + if (res != null && res.optString("status","error").equals("success")) { + System.out.println("✅ Password updated."); + return true; + } else { + String msg = (res == null) ? "No response." : res.optString("message","Update failed."); + System.out.println("❌ " + msg); + return false; + } + } + + + private void renderUserCard(String imageUrl, String profileName, String userId) { + int w = 60; + String top = "┌" + "─".repeat(w - 2) + "┐"; + String bot = "└" + "─".repeat(w - 2) + "┘"; + System.out.println(top); + System.out.println(padBoxLine("Profile", w)); + System.out.println("├" + "─".repeat(w - 2) + "┤"); + System.out.println(padBoxLine("Image URL: " + imageUrl, w)); + System.out.println(padBoxLine("Profile Name: "+ profileName, w)); + System.out.println(padBoxLine("User ID: " + userId, w)); + System.out.println(bot); + } + + private String padBoxLine(String text, int width) { + // عرض: width، دو طرف │ │ + final int inner = width - 2; + if (text.length() > inner) { + text = text.substring(0, inner - 1) + "…"; + } + int spaces = inner - text.length(); + return "│" + text + " ".repeat(Math.max(0, spaces)) + "│"; + } + + private void renderMenu() { + System.out.println("1) My Account"); + System.out.println("2) Privacy"); + System.out.println("3) Telegram Q&A"); + System.out.println("4) Telegram Features"); + System.out.println("0) Back"); } private void showTelegramFeatures() { @@ -544,6 +799,10 @@ public class SidebarHandler { System.out.println("❓ Showing Q&A..."); } + private boolean isStrongPassword(String s) { + boolean approved = s.matches("\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b"); + return approved ; + } private List parseMessages(JSONArray msgs) { List list = new ArrayList<>(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index 3801302..e10c899 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Database; +import org.json.JSONObject; import org.to.telegramfinalproject.Models.Contact; import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.User; @@ -99,6 +100,32 @@ public class ContactDatabase { } + public static List getBlockedUsers(UUID userId) { + String sql = """ + SELECT u.internal_uuid, u.user_id, u.profile_name + FROM contacts c + JOIN users u ON u.internal_uuid = c.contact_id + WHERE c.user_id = ? AND c.is_blocked = TRUE + ORDER BY u.profile_name NULLS LAST, u.user_id + """; + List out = new ArrayList<>(); + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, userId); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + JSONObject j = new JSONObject(); + j.put("internal_uuid", rs.getObject("internal_uuid").toString()); + j.put("user_id", rs.getString("user_id")); + j.put("profile_name", rs.getString("profile_name")); + out.add(j); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return out; + } public boolean unblockContact(UUID user_id, UUID contact_id) { String sql = "UPDATE contacts SET is_blocked = FALSE WHERE user_id = ? AND contact_id = ?"; diff --git a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index 57adbf8..ddf9d27 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -409,5 +409,42 @@ public class userDatabase { return "Unknown"; } + + public static String getPasswordHash(UUID userId) { + String sql = "SELECT password FROM users WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, userId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) return rs.getString("password"); + } + } catch (SQLException e) { e.printStackTrace(); } + return null; + } + + public static boolean updateUsername(UUID userId, String newUsername) throws SQLException { + String sql = "UPDATE users SET username = ? WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, newUsername); + ps.setObject(2, userId); + ps.executeUpdate(); + return true; + } catch (SQLException e) { + // 23505 = unique_violation در PostgreSQL + if ("23505".equals(e.getSQLState())) throw e; + throw e; + } + } + + public static boolean updatePasswordHash(UUID userId, String newHash) { + String sql = "UPDATE users SET password = ? WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, newHash); + ps.setObject(2, userId); + return ps.executeUpdate() > 0; + } catch (SQLException e) { e.printStackTrace(); return false; } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index b57313e..4bdca06 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -5,6 +5,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Database.*; import org.to.telegramfinalproject.Models.*; +import org.to.telegramfinalproject.Security.PasswordHashing; import org.to.telegramfinalproject.Utils.ChannelPermissionUtil; import org.to.telegramfinalproject.Utils.GroupPermissionUtil; @@ -205,7 +206,6 @@ public class ClientHandler implements Runnable { chat.getUser1_id().equals(chat.getUser2_id()) && chat.getUser1_id().equals(currentUser.getInternal_uuid()); - // در غیر self، otherId = طرف مقابل UUID otherId = isSelf ? currentUser.getInternal_uuid() : (chat.getUser1_id().equals(currentUser.getInternal_uuid()) @@ -2139,6 +2139,8 @@ public class ClientHandler implements Runnable { break; } + + case "get_contact_list": { if (currentUser == null) { response = new ResponseModel("error", "Unauthorized. Please login first."); @@ -2148,27 +2150,30 @@ public class ClientHandler implements Runnable { List contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid()); List contactEntries = new ArrayList<>(); + JSONArray contactList = new JSONArray(); for (Contact contact : contacts) { - UUID contactId = contact.getContact_id(); - User contactUser = userDatabase.findByInternalUUID(contactId); - if (contactUser == null) continue; + User target = userDatabase.findByInternalUUID(contact.getContact_id()); + if (target == null) continue; - ContactEntry entry = new ContactEntry( - contactId, - contactUser.getUser_id(), - contactUser.getProfile_name(), - contactUser.getImage_url(), - contact.getIs_blocked() - ); + JSONObject c = new JSONObject(); + c.put("user_id", contact.getUser_id().toString()); + c.put("contact_id", contact.getContact_id().toString()); + User Contact = userDatabase.findByInternalUUID(contact.getContact_id()); + c.put("contact_displayId", Contact.getUser_id()); + c.put("is_blocked", contact.getIs_blocked()); - contactEntries.add(entry); + c.put("profile_name", target.getProfile_name()); + c.put("image_url", target.getImage_url()); + c.put("last_seen", Contact.getLast_seen()); + + contactList.put(c); } // Optional: sort alphabetically contactEntries.sort(Comparator.comparing(ContactEntry::getProfileName, String.CASE_INSENSITIVE_ORDER)); JSONObject data = new JSONObject(); - data.put("contact_list", JsonUtil.contactEntryListToJson(contactEntries)); + data.put("contact_list", contactList); response = new ResponseModel("success", "Contact list refreshed", data); break; @@ -2579,9 +2584,63 @@ public class ClientHandler implements Runnable { case "get_or_create_saved_messages": { response = handleGetOrCreateSavedMessages(requestJson); - } break; + } + case "get_blocked_users": { + userId = currentUser.getInternal_uuid(); + var list = ContactDatabase.getBlockedUsers(userId); + org.json.JSONObject data = new org.json.JSONObject(); + data.put("blocked_users", list); + response = new ResponseModel("success", "ok", data); + break; + } + + case "verify_password": { + userId = currentUser.getInternal_uuid(); + String cur = requestJson.getString("current_password"); + User user = userDatabase.findByInternalUUID(userId); + boolean ok = PasswordHashing.verify(cur, user.getPassword()); + response = ok + ? new ResponseModel("success", "verified") + : new ResponseModel("error", "Invalid password."); + break; + } + + case "update_username": { + userId = currentUser.getInternal_uuid(); + String cur = requestJson.getString("current_password"); + String newUsername = requestJson.getString("new_username"); + boolean useBCrypt = true; + + try { + boolean ok = userDatabase.updateUsername(userId, newUsername); + if (ok) response = new ResponseModel("success", "username updated"); + else response = new ResponseModel("error", "Invalid current password."); + } catch (java.sql.SQLException e) { + if ("23505".equals(e.getSQLState())) { + response = new ResponseModel("error", "Username already taken."); + } else { + e.printStackTrace(); + response = new ResponseModel("error", "Failed to update username."); + } + } + break; + } + + case "update_password": { + userId = currentUser.getInternal_uuid(); + String cur = requestJson.getString("current_password"); + String newPass = requestJson.getString("new_password"); + + String newHash = PasswordHashing.hash(newPass); + + boolean ok = userDatabase.updatePasswordHash(userId, newHash); + response = ok + ? new ResponseModel("success", "password updated") + : new ResponseModel("error", "Invalid current password."); + break; + } default: response = new ResponseModel("error", "Unknown action: " + action); From 016ef9b370bf6be9e57611bb77f4c34cc68ce780 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 12 Aug 2025 15:51:19 +0330 Subject: [PATCH 05/11] Work on telegram setting --- .../org/to/telegramfinalproject/Client/ActionHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 70db64c..d27b6cb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -151,7 +151,7 @@ public class ActionHandler { req.put("keyword", keyword); req.put("user_id", Session.currentUser.getString("user_id")); req.put("entity_id", entityId.toString()); - req.put("entity_type", entityType); // group یا channel + req.put("entity_type", entityType); // group or channel JSONObject res = sendWithResponse(req); @@ -3710,9 +3710,13 @@ public class ActionHandler { ContactEntry entry = new ContactEntry( UUID.fromString(c.getString("contact_id")), c.getString("user_id"), + c.getString("contact_displayId"), c.getString("profile_name"), c.optString("image_url", ""), - c.optBoolean("is_blocked", false) + c.optBoolean("is_blocked", false), + c.isNull("last_seen") + ? null + : LocalDateTime.parse(c.getString("last_seen")) ); contactList.add(entry); From 82ade35450c41dca4d2708fdfdee70bd176cf503 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 12 Aug 2025 16:28:59 +0330 Subject: [PATCH 06/11] Clean code --- .../Client/SidebarHandler.java | 281 ------------------ 1 file changed, 281 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 5085e14..2960707 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -255,234 +255,7 @@ public class SidebarHandler { actionHandler.showContactList(); } - public void getSavedMessagesData(String userId) { - try { - // Step 1: Create the request - JSONObject request = new JSONObject(); - request.put("action", "get_saved_messages"); - request.put("user_id", userId); - // Step 2: Send request and wait for response - JSONObject response = ActionHandler.sendWithResponse(request); - - // Step 3: Check the response - if (!response.optString("status", "fail").equals("success")) { - System.out.println("Failed to open Saved Messages chat: " + response.optString("message", "Unknown error")); - return; - } - - // Step 4: Extract "data" object - JSONObject data = response.getJSONObject("data"); - UUID chatId = UUID.fromString(data.getString("chat_id")); - JSONArray messagesArray = data.getJSONArray("messages"); - - // Step 5: Parse messages - List messages = new ArrayList<>(); - if (!messagesArray.isEmpty()) { - for (int i = 0; i < messagesArray.length(); i++) { - JSONObject msgJson = messagesArray.getJSONObject(i); - - // Safely extract optional UUIDs - UUID replyToId = null; - String replyToIdStr = msgJson.optString("reply_to_id", null); - if (replyToIdStr != null && !replyToIdStr.equals("null")) { - replyToId = UUID.fromString(replyToIdStr); - } - - UUID originalMessageId = null; - String originalMessageIdStr = msgJson.optString("original_message_id", null); - if (originalMessageIdStr != null && !originalMessageIdStr.equals("null")) { - originalMessageId = UUID.fromString(originalMessageIdStr); - } - - UUID forwardedBy = null; - String forwardedByStr = msgJson.optString("forwarded_by", null); - if (forwardedByStr != null && !forwardedByStr.equals("null")) { - forwardedBy = UUID.fromString(forwardedByStr); - } - - UUID forwardedFrom = null; - String forwardedFromStr = msgJson.optString("forwarded_from", null); - if (forwardedFromStr != null && !forwardedFromStr.equals("null")) { - forwardedFrom = UUID.fromString(forwardedFromStr); - } - - Message msg = new Message( - UUID.fromString(msgJson.getString("message_id")), - UUID.fromString(msgJson.getString("sender_id")), - msgJson.getString("receiver_type"), - UUID.fromString(msgJson.getString("receiver_id")), - msgJson.getString("content"), - msgJson.getString("message_type"), - LocalDateTime.parse(msgJson.getString("send_at").replace(" ", "T")), - msgJson.getString("status"), - replyToId, - msgJson.getBoolean("is_edited"), - originalMessageId, - forwardedBy, - forwardedFrom, - msgJson.getBoolean("is_deleted_globally"), - LocalDateTime.parse(msgJson.getString("edited_at").replace(" ", "T")) - ); - - messages.add(msg); - } - } - - // 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) { - System.out.println("An error occurred while retrieving Saved Messages."); - e.printStackTrace(); - } - } - - private void showSavedMessages(UUID chatId, List messages) { -// Scanner scanner = new Scanner(System.in); -// -// System.out.println("==== Saved Messages ===="); -// if (messages == null || messages.isEmpty()) { -// System.out.println("No messages yet."); -// } else { -// for (Message msg : messages) { -// System.out.println("[" + msg.getSend_at() + "] " + msg.getContent()); -// } -// } -// -// System.out.println("\n(Type your message below, or type 0 to exit)"); -// -// while (true) { -// System.out.print("You: "); -// String content = scanner.nextLine().trim(); -// if (content.equals("0")) { -// System.out.println("Exiting Saved Messages."); -// break; -// } -// -// System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); -// String messageType = scanner.nextLine().trim().toUpperCase(); -// Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE", "AUDIO"); -// while (!allowedTypes.contains(messageType)) { -// System.out.print("Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); -// messageType = scanner.nextLine().trim().toUpperCase(); -// } -// -// // Attachments (اختیاری) -// 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().trim(); -// -// if (fileUrl.isEmpty()) { -// System.out.println("URL can not be empty. Try again."); -// continue; -// } -// if (fileUrl.contains(" ")) { -// System.out.println("URL cannot contain spaces. Try again."); -// continue; -// } -// if (!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().trim().toUpperCase(); -// Set allowedFileTypes = Set.of("IMAGE", "VIDEO", "FILE", "AUDIO"); -// while (!allowedFileTypes.contains(fileType)) { -// System.out.print("Invalid type. Try again (IMAGE / VIDEO / FILE / AUDIO): "); -// fileType = scanner.nextLine().trim().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; -// } -// } -// -// // درخواست مطابق هندلر send_message -// JSONObject request = new JSONObject(); -// request.put("action", "send_message"); -// request.put("receiver_type", "private"); -// request.put("receiver_id", chatId.toString()); // chat_id -// request.put("content", content); -// request.put("message_type", messageType); -// if (attachmentsArray.length() > 0) { -// request.put("attachments", attachmentsArray); -// } -// -// JSONObject response = ActionHandler.sendWithResponse(request); -// if (!"success".equalsIgnoreCase(response.optString("status"))) { -// System.out.println("Failed to send message: " + response.optString("message", "Unknown error")); -// } else { -// System.out.println("Message sent."); -// -// Message justSent = new Message( -// UUID.fromString(response.getJSONObject("data").getString("message_id")), -// /* senderId */ userUUID, -// /* receiverId */ chatId, -// /* type */ "private", -// /* content */ content, -// /* msgType */ messageType, -// /* send_at */ java.time.LocalDateTime.now() -// ); -// messages.add(justSent); -// System.out.println("[" + justSent.getSend_at() + "] " + justSent.getContent()); -// } -// } - } - - -// public void openSavedMessages() { -// JSONObject req = new JSONObject().put("action", "get_or_create_saved_messages"); -// JSONObject res = sendWithResponse(req); -// if (res == null || !"success".equals(res.optString("status"))) { -// System.out.println("❌ Could not open Saved Messages: " + res.optString("message","")); -// return; -// } -// -// ActionHandler.requestChatList(); -// String chatId = res.getJSONObject("data").getString("chat_id"); -// -// JSONObject mreq = new JSONObject() -// .put("action", "get_messages") -// .put("receiver_type", "private") -// .put("receiver_id", chatId) -// .put("offset", 0) -// .put("limit", 50); -// -// JSONObject mres = sendWithResponse(mreq); -// JSONArray msgs = (mres != null && mres.has("data")) -// ? mres.getJSONObject("data").optJSONArray("messages") -// : new JSONArray(); -// -// List messages = parseMessages(msgs); // تبدیل JSON → Message -// showSavedMessages(UUID.fromString(chatId), messages); -// } -// @@ -774,7 +547,6 @@ public class SidebarHandler { } private String padBoxLine(String text, int width) { - // عرض: width، دو طرف │ │ final int inner = width - 2; if (text.length() > inner) { text = text.substring(0, inner - 1) + "…"; @@ -804,58 +576,5 @@ public class SidebarHandler { return approved ; } - private List parseMessages(JSONArray msgs) { - List list = new ArrayList<>(); - if (msgs == null) return list; - for (int i = 0; i < msgs.length(); i++) { - try { - JSONObject obj = msgs.getJSONObject(i); - - UUID messageId = UUID.fromString(obj.getString("message_id")); - UUID senderId = UUID.fromString(obj.getString("sender_id")); - String receiverType= obj.getString("receiver_type"); // "private" | "group" | "channel" - UUID receiverId = UUID.fromString(obj.getString("receiver_id")); // برای private = chat_id - String content = obj.optString("content", ""); - String messageType = obj.optString("message_type", "TEXT"); - LocalDateTime sent = LocalDateTime.parse(obj.getString("send_at")); - - Message m = new Message( - messageId, senderId, receiverId, receiverType, content, messageType, sent - ); - - if (obj.has("status") && !obj.isNull("status")) { - try { m.setStatus(obj.getString("status")); } catch (Exception ignore) {} - } - if (obj.has("reply_to_id") && !obj.isNull("reply_to_id")) { - try { m.setReply_to_id(UUID.fromString(obj.getString("reply_to_id"))); } catch (Exception ignore) {} - } - if (obj.has("forwarded_by") && !obj.isNull("forwarded_by")) { - try { m.setForwarded_by(UUID.fromString(obj.getString("forwarded_by"))); } catch (Exception ignore) {} - } - if (obj.has("forwarded_from") && !obj.isNull("forwarded_from")) { - try { m.setForwarded_from(UUID.fromString(obj.getString("forwarded_from"))); } catch (Exception ignore) {} - } - -// // ضمیمه‌ها (اگر در مدل Message متد addAttachment داری) -// if (obj.has("attachments") && !obj.isNull("attachments")) { -// try { -// JSONArray atts = obj.getJSONArray("attachments"); -// for (int j = 0; j < atts.length(); j++) { -// JSONObject a = atts.getJSONObject(j); -// String fileUrl = a.getString("file_url"); -// String fileType = a.getString("file_type"); -// FileAttachment fa = new FileAttachment(fileUrl, fileType); -// try { m.addAttachment(fa); } catch (Exception ignore) {} -// } -// } catch (Exception ignore) {} -// } - - list.add(m); - } catch (Exception perItem) { - perItem.printStackTrace(); - } - } - return list; - } } \ No newline at end of file From fc2c0f858b738e4360d2fa8fbe317f2b3eb9f16d Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Tue, 12 Aug 2025 22:43:28 +0330 Subject: [PATCH 07/11] Sidebar updated. --- .../Client/SidebarHandler.java | 200 +++++++++--------- .../Server/ClientHandler.java | 20 +- .../Server/SidebarService.java | 155 +++++++------- 3 files changed, 185 insertions(+), 190 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 5085e14..dab17f2 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -255,106 +255,106 @@ public class SidebarHandler { actionHandler.showContactList(); } - public void getSavedMessagesData(String userId) { - try { - // Step 1: Create the request - JSONObject request = new JSONObject(); - request.put("action", "get_saved_messages"); - request.put("user_id", userId); +// public void getSavedMessagesData(String userId) { +// try { +// // Step 1: Create the request +// JSONObject request = new JSONObject(); +// request.put("action", "get_saved_messages"); +// request.put("user_id", userId); +// +// // Step 2: Send request and wait for response +// JSONObject response = ActionHandler.sendWithResponse(request); +// +// // Step 3: Check the response +// if (!response.optString("status", "fail").equals("success")) { +// System.out.println("Failed to open Saved Messages chat: " + response.optString("message", "Unknown error")); +// return; +// } +// +// // Step 4: Extract "data" object +// JSONObject data = response.getJSONObject("data"); +// UUID chatId = UUID.fromString(data.getString("chat_id")); +// JSONArray messagesArray = data.getJSONArray("messages"); +// +// // Step 5: Parse messages +// List messages = new ArrayList<>(); +// if (!messagesArray.isEmpty()) { +// for (int i = 0; i < messagesArray.length(); i++) { +// JSONObject msgJson = messagesArray.getJSONObject(i); +// +// // Safely extract optional UUIDs +// UUID replyToId = null; +// String replyToIdStr = msgJson.optString("reply_to_id", null); +// if (replyToIdStr != null && !replyToIdStr.equals("null")) { +// replyToId = UUID.fromString(replyToIdStr); +// } +// +// UUID originalMessageId = null; +// String originalMessageIdStr = msgJson.optString("original_message_id", null); +// if (originalMessageIdStr != null && !originalMessageIdStr.equals("null")) { +// originalMessageId = UUID.fromString(originalMessageIdStr); +// } +// +// UUID forwardedBy = null; +// String forwardedByStr = msgJson.optString("forwarded_by", null); +// if (forwardedByStr != null && !forwardedByStr.equals("null")) { +// forwardedBy = UUID.fromString(forwardedByStr); +// } +// +// UUID forwardedFrom = null; +// String forwardedFromStr = msgJson.optString("forwarded_from", null); +// if (forwardedFromStr != null && !forwardedFromStr.equals("null")) { +// forwardedFrom = UUID.fromString(forwardedFromStr); +// } +// +// Message msg = new Message( +// UUID.fromString(msgJson.getString("message_id")), +// UUID.fromString(msgJson.getString("sender_id")), +// msgJson.getString("receiver_type"), +// UUID.fromString(msgJson.getString("receiver_id")), +// msgJson.getString("content"), +// msgJson.getString("message_type"), +// LocalDateTime.parse(msgJson.getString("send_at").replace(" ", "T")), +// msgJson.getString("status"), +// replyToId, +// msgJson.getBoolean("is_edited"), +// originalMessageId, +// forwardedBy, +// forwardedFrom, +// msgJson.getBoolean("is_deleted_globally"), +// LocalDateTime.parse(msgJson.getString("edited_at").replace(" ", "T")) +// ); +// +// messages.add(msg); +// } +// } +// +// // 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) { +// System.out.println("An error occurred while retrieving Saved Messages."); +// e.printStackTrace(); +// } +// } - // Step 2: Send request and wait for response - JSONObject response = ActionHandler.sendWithResponse(request); - - // Step 3: Check the response - if (!response.optString("status", "fail").equals("success")) { - System.out.println("Failed to open Saved Messages chat: " + response.optString("message", "Unknown error")); - return; - } - - // Step 4: Extract "data" object - JSONObject data = response.getJSONObject("data"); - UUID chatId = UUID.fromString(data.getString("chat_id")); - JSONArray messagesArray = data.getJSONArray("messages"); - - // Step 5: Parse messages - List messages = new ArrayList<>(); - if (!messagesArray.isEmpty()) { - for (int i = 0; i < messagesArray.length(); i++) { - JSONObject msgJson = messagesArray.getJSONObject(i); - - // Safely extract optional UUIDs - UUID replyToId = null; - String replyToIdStr = msgJson.optString("reply_to_id", null); - if (replyToIdStr != null && !replyToIdStr.equals("null")) { - replyToId = UUID.fromString(replyToIdStr); - } - - UUID originalMessageId = null; - String originalMessageIdStr = msgJson.optString("original_message_id", null); - if (originalMessageIdStr != null && !originalMessageIdStr.equals("null")) { - originalMessageId = UUID.fromString(originalMessageIdStr); - } - - UUID forwardedBy = null; - String forwardedByStr = msgJson.optString("forwarded_by", null); - if (forwardedByStr != null && !forwardedByStr.equals("null")) { - forwardedBy = UUID.fromString(forwardedByStr); - } - - UUID forwardedFrom = null; - String forwardedFromStr = msgJson.optString("forwarded_from", null); - if (forwardedFromStr != null && !forwardedFromStr.equals("null")) { - forwardedFrom = UUID.fromString(forwardedFromStr); - } - - Message msg = new Message( - UUID.fromString(msgJson.getString("message_id")), - UUID.fromString(msgJson.getString("sender_id")), - msgJson.getString("receiver_type"), - UUID.fromString(msgJson.getString("receiver_id")), - msgJson.getString("content"), - msgJson.getString("message_type"), - LocalDateTime.parse(msgJson.getString("send_at").replace(" ", "T")), - msgJson.getString("status"), - replyToId, - msgJson.getBoolean("is_edited"), - originalMessageId, - forwardedBy, - forwardedFrom, - msgJson.getBoolean("is_deleted_globally"), - LocalDateTime.parse(msgJson.getString("edited_at").replace(" ", "T")) - ); - - messages.add(msg); - } - } - - // 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) { - System.out.println("An error occurred while retrieving Saved Messages."); - e.printStackTrace(); - } - } - - private void showSavedMessages(UUID chatId, List messages) { +// private void showSavedMessages(UUID chatId, List messages) { // Scanner scanner = new Scanner(System.in); // // System.out.println("==== Saved Messages ===="); @@ -453,7 +453,7 @@ public class SidebarHandler { // System.out.println("[" + justSent.getSend_at() + "] " + justSent.getContent()); // } // } - } +// } // public void openSavedMessages() { diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 4bdca06..06072e2 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -2555,16 +2555,16 @@ public class ClientHandler implements Runnable { break; } - case "get_saved_messages": { - UUID user_Id = UUID.fromString(requestJson.getString("user_id")); - response = SidebarService.handleGetSavedMessages(user_Id); - break; - } - - case "send_saved_messages": { - response = SidebarService.handleSendMessage(requestJson); - break; - } +// case "get_saved_messages": { +// UUID user_Id = UUID.fromString(requestJson.getString("user_id")); +// response = SidebarService.handleGetSavedMessages(user_Id); +// break; +// } +// +// case "send_saved_messages": { +// response = SidebarService.handleSendMessage(requestJson); +// break; +// } case "search_contacts": { String user_id = requestJson.getString("user_id"); diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index 94fb542..7bfb8e6 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -211,86 +211,81 @@ public class SidebarService { } } - public static ResponseModel handleGetSavedMessages(UUID userId) { - try { - - UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId); - if (chatId == null) { - return new ResponseModel("error", "Failed to create or find saved messages chat."); - } - - List messages = MessageDatabase.privateChatHistory(chatId, userId); - - JSONArray messageArray = new JSONArray(); - if (!messages.isEmpty()) { - for (Message msg : messages) { - JSONObject msgJson = new JSONObject(); - msgJson.put("message_id", msg.getMessage_id().toString()); - msgJson.put("sender_id", msg.getSender_id().toString()); - msgJson.put("receiver_type", msg.getReceiver_type()); - msgJson.put("receiver_id", msg.getReceiver_id().toString()); - msgJson.put("content", msg.getContent()); - msgJson.put("message_type", msg.getMessage_type()); - msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime - msgJson.put("status", msg.getStatus()); - msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); - msgJson.put("is_edited", msg.isIs_edited()); - msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL); - msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL); - msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL); - - messageArray.put(msgJson); - } - } - - JSONObject data = new JSONObject(); - data.put("chat_id", chatId.toString()); - data.put("messages", messageArray); - - return new ResponseModel("success", "Saved messages retrieved successfully", data); - - } catch (Exception e) { - e.printStackTrace(); - return new ResponseModel("error", "Unexpected server error."); - } - } +// public static ResponseModel handleGetSavedMessages(UUID userId) { +// try { +// +// UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId); +// if (chatId == null) { +// return new ResponseModel("error", "Failed to create or find saved messages chat."); +// } +// +// List messages = MessageDatabase.privateChatHistory(chatId, userId); +// +// JSONArray messageArray = new JSONArray(); +// if (!messages.isEmpty()) { +// for (Message msg : messages) { +// JSONObject msgJson = new JSONObject(); +// msgJson.put("message_id", msg.getMessage_id().toString()); +// msgJson.put("sender_id", msg.getSender_id().toString()); +// msgJson.put("receiver_type", msg.getReceiver_type()); +// msgJson.put("receiver_id", msg.getReceiver_id().toString()); +// msgJson.put("content", msg.getContent()); +// msgJson.put("message_type", msg.getMessage_type()); +// msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime +// msgJson.put("status", msg.getStatus()); +// msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); +// msgJson.put("is_edited", msg.isIs_edited()); +// msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL); +// msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL); +// msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL); +// +// messageArray.put(msgJson); +// } +// } +// +// JSONObject data = new JSONObject(); +// data.put("chat_id", chatId.toString()); +// data.put("messages", messageArray); +// +// return new ResponseModel("success", "Saved messages retrieved successfully", data); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return new ResponseModel("error", "Unexpected server error."); +// } +// } // Save messages to DB - public static ResponseModel handleSendMessage(JSONObject requestJson) { - try { - Message message = new Message( - UUID.fromString(requestJson.getString("message_id")), - UUID.fromString(requestJson.getString("sender_id")), - requestJson.getString("receiver_type"), - UUID.fromString(requestJson.getString("receiver_id")), - requestJson.optString("content", null), - requestJson.optString("message_type", "TEXT"), - LocalDateTime.now(), // send_at - requestJson.optString("status", "SEND"), - requestJson.isNull("reply_to_id") ? null : UUID.fromString(requestJson.getString("reply_to_id")), - requestJson.optBoolean("is_edited", false), - requestJson.isNull("original_message_id") ? null : UUID.fromString(requestJson.getString("original_message_id")), - requestJson.isNull("forwarded_by") ? null : UUID.fromString(requestJson.getString("forwarded_by")), - requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")), - requestJson.optBoolean("is_deleted_globally", false), - requestJson.isNull("edited_at") ? null : - LocalDateTime.ofInstant( - Instant.ofEpochMilli(requestJson.getLong("edited_at")), - ZoneId.systemDefault() - ) - ); - - MessageDatabase.insertSavedMessage(message); - return new ResponseModel("success", "Message saved successfully."); - - } catch (Exception e) { - e.printStackTrace(); - return new ResponseModel("error", "Unexpected server error."); - } - } - - // Changes username - public static boolean updateUserName(String userId, String newUserName) { - return false; - } +// public static ResponseModel handleSendMessage(JSONObject requestJson) { +// try { +// Message message = new Message( +// UUID.fromString(requestJson.getString("message_id")), +// UUID.fromString(requestJson.getString("sender_id")), +// requestJson.getString("receiver_type"), +// UUID.fromString(requestJson.getString("receiver_id")), +// requestJson.optString("content", null), +// requestJson.optString("message_type", "TEXT"), +// LocalDateTime.now(), // send_at +// requestJson.optString("status", "SEND"), +// requestJson.isNull("reply_to_id") ? null : UUID.fromString(requestJson.getString("reply_to_id")), +// requestJson.optBoolean("is_edited", false), +// requestJson.isNull("original_message_id") ? null : UUID.fromString(requestJson.getString("original_message_id")), +// requestJson.isNull("forwarded_by") ? null : UUID.fromString(requestJson.getString("forwarded_by")), +// requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")), +// requestJson.optBoolean("is_deleted_globally", false), +// requestJson.isNull("edited_at") ? null : +// LocalDateTime.ofInstant( +// Instant.ofEpochMilli(requestJson.getLong("edited_at")), +// ZoneId.systemDefault() +// ) +// ); +// +// MessageDatabase.insertSavedMessage(message); +// return new ResponseModel("success", "Message saved successfully."); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return new ResponseModel("error", "Unexpected server error."); +// } +// } } \ No newline at end of file From 054f3db155f15b92a7f6299a74ad51c7c1389994 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Tue, 12 Aug 2025 22:45:46 +0330 Subject: [PATCH 08/11] Sidebar UI implemented. --- .../UI/SidebarMenuController.java | 158 +++++++++++++++++- .../UI/TelegramApplication.java | 3 +- .../telegramfinalproject/CSS/dark_theme.css | 52 ++++++ .../telegramfinalproject/CSS/light_theme.css | 52 ++++++ .../Icons/contacts_dark.png | Bin 0 -> 471 bytes .../Icons/contacts_light.png | Bin 0 -> 475 bytes .../Icons/my_profile_dark.png | Bin 0 -> 706 bytes .../Icons/my_profile_light.png | Bin 0 -> 495 bytes .../Icons/new_channel_dark.png | Bin 0 -> 895 bytes .../Icons/new_channel_light.png | Bin 0 -> 935 bytes .../Icons/new_group_dark.png | Bin 0 -> 691 bytes .../Icons/new_group_light.png | Bin 0 -> 719 bytes .../Icons/night_mode_dark.png | Bin 0 -> 781 bytes .../Icons/night_mode_light.png | Bin 0 -> 834 bytes .../Icons/saved_messages_dark.png | Bin 0 -> 806 bytes .../Icons/saved_messages_light.png | Bin 0 -> 830 bytes .../Icons/settings_dark.png | Bin 0 -> 510 bytes .../Icons/settings_light.png | Bin 0 -> 493 bytes .../Icons/telegram_features_dark.png | Bin 0 -> 825 bytes .../Icons/telegram_features_light.png | Bin 0 -> 886 bytes .../Icons/telegram_qna_dark.png | Bin 0 -> 1142 bytes .../Icons/telegram_qna_light.png | Bin 0 -> 1174 bytes .../to/telegramfinalproject/sidebar_menu.fxml | 43 +++-- 23 files changed, 284 insertions(+), 24 deletions(-) create mode 100644 src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css create mode 100644 src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/contacts_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/contacts_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/my_profile_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/my_profile_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/new_channel_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/new_channel_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/new_group_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/new_group_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/night_mode_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/night_mode_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/saved_messages_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/saved_messages_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/settings_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/settings_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/telegram_features_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/telegram_features_light.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/telegram_qna_dark.png create mode 100644 src/main/resources/org/to/telegramfinalproject/Icons/telegram_qna_light.png diff --git a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java index 9a6915d..ffc2942 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/SidebarMenuController.java @@ -1,11 +1,26 @@ package org.to.telegramfinalproject.UI; +import javafx.animation.TranslateTransition; +import javafx.application.Platform; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.util.Duration; -public class SidebarMenuController { +import java.net.URL; + +public class SidebarMenuController { + + private static final String CSS_PATH = "/org/to/telegramfinalproject/CSS/"; + private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/"; + + @FXML private VBox sidebarRoot; // fx:id must match FXML @FXML private ImageView profileImage; @FXML private Label usernameLabel; @@ -18,14 +33,143 @@ public class SidebarMenuController { @FXML private Button telegramFeaturesButton; @FXML private Button telegramQnAButton; - @FXML private ToggleButton nightModeToggle; + // Custom Telegram-style toggle + @FXML private HBox nightModeToggle; + @FXML private Region toggleThumb; + @FXML private ImageView nightModeIcon; + + private boolean nightModeOn = false; // start light by default @FXML public void initialize() { - Image image = new Image(getClass().getResource("/org/to/telegramfinalproject/Images/profile.png").toExternalForm()); - profileImage.setImage(image); + // Load default profile picture (if present) + Image profile = loadImage("/org/to/telegramfinalproject/Images/profile.png"); + if (profile != null) profileImage.setImage(profile); - // Called automatically after FXML is loaded - usernameLabel.setText("Asal"); + // Set up handlers and defer theme application until the scene is available + setupButtonActions(); + setupToggleAction(); + + // When the scene is set, apply the initial theme (light) and initialize icons/positions + sidebarRoot.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) { + // Apply light theme at startup + updateTheme(false); + + // Position the thumb after layout pass (to use actual widths) + Platform.runLater(() -> { + double initialX = nightModeOn + ? nightModeToggle.getWidth() - toggleThumb.getWidth() - 4 + : 2; + toggleThumb.setTranslateX(initialX); + }); + } + }); } + + private void setupButtonActions() { + myProfileButton.setOnAction(e -> openMyProfile()); + newGroupButton.setOnAction(e -> createNewGroup()); + newChannelButton.setOnAction(e -> createNewChannel()); + contactsButton.setOnAction(e -> openContacts()); + savedMessagesButton.setOnAction(e -> openSavedMessages()); + settingsButton.setOnAction(e -> openSettings()); + telegramFeaturesButton.setOnAction(e -> openTelegramFeatures()); + telegramQnAButton.setOnAction(e -> openTelegramQnA()); + } + + private void setupToggleAction() { + nightModeToggle.setOnMouseClicked(e -> { + nightModeOn = !nightModeOn; + updateTheme(nightModeOn); + + // animate thumb + double targetX = nightModeOn + ? nightModeToggle.getWidth() - toggleThumb.getWidth() - 4 + : 2; + TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb); + tt.setToX(targetX); + tt.play(); + }); + } + + /** + * Apply theme and swap icons. darkMode == true => apply dark theme (dark background), + * and use light (white) icons. darkMode == false => apply light theme and use dark icons. + */ + private void updateTheme(boolean darkMode) { + Scene scene = sidebarRoot.getScene(); + if (scene == null) return; // safety + + // clear & apply the correct stylesheet + scene.getStylesheets().clear(); + String cssFile = darkMode ? "dark_theme.css" : "light_theme.css"; + URL cssUrl = getClass().getResource(CSS_PATH + cssFile); + if (cssUrl != null) { + scene.getStylesheets().add(cssUrl.toExternalForm()); + } else { + System.err.println("CSS not found: " + CSS_PATH + cssFile); + } + + // Keep toggle's "on" class in sync + if (darkMode) { + if (!nightModeToggle.getStyleClass().contains("on")) + nightModeToggle.getStyleClass().add("on"); + } else { + nightModeToggle.getStyleClass().remove("on"); + } + + // Icon file suffix mapping: + // - For darkMode == true (dark background) we want white icons => use "_light.png" + // - For darkMode == false (light background) we want dark icons => use "_dark.png" + String iconSuffix = darkMode ? "_light.png" : "_dark.png"; + + setIcons(iconSuffix); + + // night mode icon itself (moon) + Image moon = loadImage(ICON_PATH + "night_mode" + iconSuffix); + if (moon != null) nightModeIcon.setImage(moon); + } + + private void setIcons(String suffix) { + myProfileButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix)); + newGroupButton.setGraphic(makeIcon(ICON_PATH + "new_group" + suffix)); + newChannelButton.setGraphic(makeIcon(ICON_PATH + "new_channel" + suffix)); + contactsButton.setGraphic(makeIcon(ICON_PATH + "contacts" + suffix)); + savedMessagesButton.setGraphic(makeIcon(ICON_PATH + "saved_messages" + suffix)); + settingsButton.setGraphic(makeIcon(ICON_PATH + "settings" + suffix)); + telegramFeaturesButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix)); + telegramQnAButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix)); // prefer safe filename + } + + private Image loadImage(String path) { + URL res = getClass().getResource(path); + if (res == null) { + System.err.println("Resource not found: " + path); + return null; + } + return new Image(res.toExternalForm()); + } + + private ImageView makeIcon(String path) { + ImageView iv = new ImageView(); + Image img = loadImage(path); + if (img != null) { + iv.setImage(img); + iv.setFitWidth(22); + iv.setFitHeight(22); + iv.setPreserveRatio(true); + } + return iv; + } + + // Example button actions + private void openMyProfile() { System.out.println("Opening My Profile..."); } + private void createNewGroup() { System.out.println("Creating New Group..."); } + private void createNewChannel() { System.out.println("Creating New Channel..."); } + private void openContacts() { System.out.println("Opening Contacts..."); } + private void openSavedMessages() { System.out.println("Opening Saved Messages..."); } + private void openSettings() { System.out.println("Opening Settings..."); } + private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); } + private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java index 596c359..1adeb51 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java +++ b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java @@ -5,7 +5,6 @@ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; -import org.to.telegramfinalproject.HelloApplication; import java.io.IOException; @@ -15,7 +14,7 @@ public class TelegramApplication extends Application { FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml")); Scene scene = new Scene(fxmlLoader.load(), 1480, 820); - scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/sidebar_menu.css").toExternalForm()); + scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm()); stage.setTitle("Telegram"); stage.setScene(scene); diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css new file mode 100644 index 0000000..0b92773 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css @@ -0,0 +1,52 @@ +/* Sidebar buttons */ +.sidebar-btn { + -fx-background-color: transparent; + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-pref-width: 230; + -fx-alignment: CENTER_LEFT; + -fx-padding: 8 0 8 16; +} +.sidebar-btn:hover { + -fx-background-color: #2f3e50; + -fx-cursor: hand; +} + +/* Telegram-style toggle switch */ +.telegram-toggle { + -fx-background-color: #555; + -fx-background-radius: 15; + -fx-padding: 2; + -fx-pref-width: 40; + -fx-pref-height: 20; + -fx-alignment: center-left; + -fx-cursor: hand; +} +.telegram-toggle.on { + -fx-background-color: #4fa8f0; +} +.toggle-thumb { + -fx-background-color: white; + -fx-background-radius: 50%; + -fx-pref-width: 16; + -fx-pref-height: 16; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 3, 0, 0, 1); +} + +/* Separator */ +.thin-separator { + -fx-background-color: transparent; + -fx-padding: 0; +} +.thin-separator .line { + -fx-border-color: #2e3948; /* Telegram's subtle dark line */ + -fx-border-width: 0.5px; +} + +/* Theme background & text */ +#sidebarRoot { + -fx-background-color: #1b2735; /* dark theme */ +} +.button, .label { + -fx-text-fill: white; +} diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css new file mode 100644 index 0000000..7ef5c23 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css @@ -0,0 +1,52 @@ +/* Sidebar buttons */ +.sidebar-btn { + -fx-background-color: transparent; + -fx-text-fill: black; + -fx-font-size: 14px; + -fx-pref-width: 230; + -fx-alignment: CENTER_LEFT; + -fx-padding: 8 0 8 16; +} +.sidebar-btn:hover { + -fx-background-color: #e6e6e6; + -fx-cursor: hand; +} + +/* Telegram-style toggle switch */ +.telegram-toggle { + -fx-background-color: #aaa; + -fx-background-radius: 15; + -fx-padding: 2; + -fx-pref-width: 40; + -fx-pref-height: 20; + -fx-alignment: center-left; + -fx-cursor: hand; +} +.telegram-toggle.on { + -fx-background-color: #4fa8f0; +} +.toggle-thumb { + -fx-background-color: white; + -fx-background-radius: 50%; + -fx-pref-width: 16; + -fx-pref-height: 16; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.2), 3, 0, 0, 1); +} + +/* Separator */ +.thin-separator { + -fx-background-color: transparent; + -fx-padding: 0; +} +.thin-separator .line { + -fx-border-color: #ccc; + -fx-border-width: 0.5px; +} + +/* Theme background & text */ +#sidebarRoot { + -fx-background-color: white; +} +.button, .label { + -fx-text-fill: black; +} diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/contacts_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/contacts_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8168104dbf761733dd1967f479b84a2a46986b GIT binary patch literal 471 zcmV;|0Vw{7P)%g8%2zw;P45$6+xG35Lcp!1OA2tMPgQ( zmBT`RLfonGGsI!5Nbm#V6h(*{m4X{6f=Qe1YEbA4Z_!ov-TS)wy=sUM;UA(G$O0F@ zi{c#E0D8hUp9hM-SB-cAX2Uf9l+htD3bZN4fFtddfn*rRUX;N~9p0)qk3hG}0?_CyfCQ;MvUWki(>au{0 zM9rvq;Y8fD%L4jv2b%p9uqc5mpWo8t zTuWoYPXPx?Ucx_8(%AQtoLBN-fC7fB(QFv6O709$K$kR(R)h$D6WPYxIBVhMbKkn5KrQgD87aSCB&RG zCzpf1gm^Q?#}K1eMS>3yw6Et>(ZBpUI+offxXGr)^P!ydT3;+Aa&cx)m-pSTY;16)Wn>VX>*_uOWHj6`!D zxM$*~Z3Y+t?j@XRz)Oo$GFR7H00Z-J0X}4q*R+RKG1txwXE786fP}tu^$4>X*n4qd~S5!Yb1Rr5s& zy#X_N76+=o9DUUpK44Zo1`B@8yIjYbtXZ4VO(>~Ofyo-qx_XO1r=I|;61evBE;Tzh z(pd2m;8@LTO*j8&B&BiWr@5%+@fHG1I9Jn8b4AVlEd&^lM#Zm28*PMw#y7hu>e7Oi Rs>T2S002ovPDHLkV1l8$#Yg}E literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/my_profile_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/my_profile_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6d232031552a1dd2803b617c333dac4c67b799ca GIT binary patch literal 706 zcmV;z0zLhSP)y{SvC)+abQ*+Th4tvk<1qNk#}q7P#%rX?L7hF$96 z)eP@}U%)u9Q#Kl%gXlm~s0vaX)~)!=G5QcV4IFZ=*e!C>&iPZo1IO8?z*gY8{#%L@ z(dAr5gHG@6B|Tb!*Ur7MtW^1zTM#26f79;`vg4*_<5m`OPQ4xF^b^pWfo}!7;P5j) zNKlTWv%jn&Izcjefv*m4T~9JM5@TM4+sLgEca^{|U=FH4hRryM zTUf+$o)@@@6M^|x@=3hLZJfqVbo1=OH16OPj;)1w6RzMH_P4-~;yEs((cQtm+^x-C{q!&SV{aO8 z_b^rGh_#z81a!v=j+Md>}G|phQF2Pi+*ow_LpTj+cfgayLzFSJnZ;8PD zxUi**wwM(M`5Yc&xS<`xiJ8rq1KH}qQosaW;%e$qw?y6((@h<^mP&A>U%*9t3wiEG z-V+m2{x#aw2HeRcQO#Hj{K5QhTxccNJ%LA49Uou@d$BFgKAg)oF7$0nLz2Oi@EGEJ lTp)bR=j-t&D-LwS*bhZXap(CnrVjuB002ovPDHLkV1mn# literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/new_channel_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/new_channel_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..156c170ea0b0c89ec2ce597b605c79634577d1ea GIT binary patch literal 895 zcmV-_1AzRAP)g-3U)NLviF1MgE{(m=|101dq$-02qjXSq8k5=U0Il6i98rs3bzQ^wOv>T{5W)5#I$l-K<|*IP_Mta%Qd; z+^b{yLcy%l z5@4R>RT!8aF?OPgG3pM}7Lx!Q4yG$10p^iSp6QiZ%xeO44f8>s?{a95!!oFb>=IEcpvM5sJ4 zbAUtIzFf6$z*z_RC>KOl80G(`fv+0C3xTTX-n?W1VFR;9lIExpw0mR=Q1gT6Aq}F% z;x{^sil_a+J;VRIwG&9M2hjtT5K%B;`z%Fe*2SP|seqf1D*<)lmqiPWJQLTv+=cA4 z+mpV?gNDTc3mgj^BWx}-j1W~$ZS$-rzM8ORC z2AOA3OPPV41Z}Qn-aw+O;) z32YenE&pGSf-wOB+JPb9PO8BNrqi(ewE-Q#+c=mc5K0iQWz4m#Kqdm;-7U??ZR<5k$GEesI}u0DE0P6eM6DVs2nA zKUJWXHdWJMtNhBohNUmIs;YovARg-w+C6I+U>0ytJC`ec0nS#Nk1B$&g^~Gx3izx6 zJQt|+&gK|En7{xlRnn9yKwq@G)Da*x0Id-{pg}Zy2@1fd^|TMTADI6a{0?N+jp$wz zhzJ;8A5~FlyBSn1X+Dq1D}i>)@6HS4nJA_IE(F+aa{f+$(DO`~V&8>05wOj+jtYTj z4p}n=)0a9<1k6{CfS`Y-8K01*K*kTpnCQY{s-up^;EcC&0i zBBsSM?Oum%0U5A2=CW!|RKWR&^kN@sS-K}`VH?=%&l;vlGP2qLe5rE#yea@~gA9!8 z(`{rT31AbjF{tX)lk$9A>SIdFAC*7{CBiWYLd0ZSiCc1FV;OFTK2r@#mCv zs#^$b(7Gaj-#69^Ob_9D08IDlMZiqygbd2Mc%hy|T|sym=?|*-v`vr0kO~+Z!L{z z`~f1ie*6yY{b(`qs|3_UZEQd-h|y>RBG7Qnm_u^dU6(n#ckW)3d6G#sJA2-9W_D)Y z%|av+iA35!FK`QZ1N@Tq8km*7PSf4RDDVYn{-b>cM(7;fUDSc)oFqGK3FxDG-4wG* zY!#RS4oaJndprFzRIi)jtrEJD?&hlMcWbw5-4wr-&|tco1FCUC4B8xpNiy>9NS zo({Bxrdz0+;$~vAU6xa!FJrUa=m6c!<8CR?F&aWQ6K5u$a*`}349C}9t-7V?k=MQ` zn`%w2XQZ#FyY_hMwuQF2Ybe_0u0EUt7Jw~}867X-uHzp1TT;uUrTC_y^_Otheq*ll zJ_A@Ziq;Cat2>z-HDj)Mp8@D)GeGt3I-ue>nI3`P_8yE)809_PkpJ)t= zW?gbLB&R-5Hk9Y=Nkani*7977etq-;Pxr{>Jm#WR*p2o@&RTY}@}LIdmpZUQfXRlSyg8#2~wy4wJb0H0{jM_?rC5#4P7bzqt9ya)D1CF{1J zud>MxU=lc}*Q7pcG98ty+XC>)2whGXzhcIhqLOu60Dc>x{R!g-%=oXUWZf1d*dx); zsAS!4uX-xb5}L|OH!0DK5t5B{Nlpd5;+ySy*}CnHJ2}ci9HY7Gwo5hweCUv5*&2?k zyZY*80@$su{e^5Qz2tsc#?rbgj=F_Vn7f9eFn4tUI14-nHXLSjJgvI|;|}^8TFV)q z;(h2#>#jhbXRdQD4ag?)p_am3SM^!VGuNU^19sW;_ke5no49qCfWNpV3GJ@<)Rdut zVD5@%1K-W>4|^uc-Cfx-$0pxg0v?%wcfhD;!ra^y7zW;&dS+dMPRM34L2^F}G=QVJ zRCWP_z$svdPf@X4J5^&)HS3zAp*ih=vext*7vY8mbYd=#zdqRd_nmUF(;trX(FJY_fT*yOH$ZKH8A!Z18 zW8xkZQ*ClF$78N7oBAGb9vIN?{hHp(v-`1)zwYd=0h_q!;Mz$@jGw!PLe1`(NAXWH zxoaqZlfYeI0r;%5yr%c^?2dda1}mVVq9Xd|_ybr-7mJ-0-0lDX002ovPDHLkV1mxv BPpkj{ literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/night_mode_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/night_mode_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1ebf9f67b168b5447a3603d811eeed1ed8b75203 GIT binary patch literal 781 zcmV+o1M>WdP)>V zkxSAjQA)`*ip+z{ywP}3Jb5tl;Ke;f?lj0`$R+pd(bj+0*Xhihee8XlHQB#f4}0(L zTkF5pUbpXqn3$NDGz;diqN#F}Gh&I4b;1uvn!fhFW!?Xjcfgfpvd!-hK z=>eQKV!r`3z)L+p2TE|1Zoo|={;KBsMDuPlj#4bazR)8;M}4c&yRSeQj&jn7Kd5!! z^H}dM;wV){{Mi)orRLiK9Ha~IN&*`t*fPp=U}qA_ zbtHi!6lF0rrW)JHIKluUYC#6u_U4XMk3*4FQe}Ps`@z{Sw<`r@9MCM^@4#N5YZm@Rz*gWs zu!|5(FzaAhmcCwU<%aS944eQKD~c;fu`dT^0mp%N#_vtob-@ z5U@>yGZvU(@=SF^#B4J{mw-;>h6+t7|19YlW#varC+hxUJ!GOgaL2TWdx7RtqbyJ6z zXI;lX!VB)#q>Nlr2iJCB3eeXq_7ZJ}3xUJHixj`xz1 literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/night_mode_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/night_mode_light.png new file mode 100644 index 0000000000000000000000000000000000000000..8f9515a7036d11dbc3c50cfefdaff79009164980 GIT binary patch literal 834 zcmV-I1HJr-P)w@G;Sb%} zm}&(xu_cK89na#c^87hYOf`b(cqfS8na4UV4e!>Z8o>!gaEA>zwzTCxkogTWQjOrH zAbx$G2chlL^8SrfBUlo|zt#{xHMFfuwSnXDbrJYk7Tjn*KP0d+)dubf0&nWlepzT= zl4=4Iv8OY>^2s=oXCV!1DMk5(7;u}_KH*kI4g*{c?{d5!x?F7s_16bVQ~-k z*ZpJ7qVZ8V3OFSQTG6A=)?)t+&P+!VObCVy~fbmYx`ve}qQ+OI{^T)x3xHi<8HVq^`bb3ShLfbKE zD1o~tmxq)xyiQKT0?&mcHuXCndWe4#-V1#!O+)Tr5?&7-Y|C|`A;q4Hk3;OW!-(6h zqp9U;+2NBxPM;30a;xz}h`D~ast@h-IK0wWIX36&L?1Eo>GekJ3Ndn>^{&GzzERx0 zqw(dGjq4#?i8DHrJ=aR}a)?{-48Caiy^D*-MSLTX(@Kf4vu^jxe%zZkym1%b=Fs4Lk}D7$Vx%-vePZcc1f*-PeEb7yX8X=!Qs8~c)#T#{v%ivR!s M07*qoM6N<$f}mxZW&i*H literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/saved_messages_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/saved_messages_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3abefc98f5fbe430f07925e00d76914d6b2539 GIT binary patch literal 806 zcmV+>1KIqEP)V=Bx8tJ$5{B%4Qz|o|*)C;7!6M zZ}|Is2>5Ff)Op~qGyIY(aoA7n3xUrZnSdfNr2JCXq<~w>f3sEvafL0#~?x}!Y58M-g+s;=$oph3VpiS$fu`y;xmV7mV7PC>>9sdz9h8n>lU^&nT z{4^rA15=Vna1rVUDv<1jpyzKDw6uHTT9w{t`D&>X5E?jW9Ru*Wp%ZWcwX)`BZ z;;P}=Um*UXiIwHGD%|Kf!_`?J_S9%r9$y0P8?IIQOeXXaV5$?_Wn#}(i-0eNs|i@E zz9-NPY_f@6ZDME^|B7%*inEj$#Gm)Fy=EHqhU)98N6Iwp$IJ)D;o1&^b}L zr5{)}Li+)Y`(6nF9nMKg$`#;}^GvMq-WVenBskBCCa;h1H-qY7LbU5!wJ~xF(Nt*x zau|Q>{O<*@G@|`nb2Sr|ufV3X1pG7PG8qp#&xUpK<_cKjBTl{~S^qU0X zWD84PZ`F#097f)cFxj8@5U?dCnT+MxW$;aNtg`)*>+ytkGv+@7yMeI@+{!)Homsrj k22#MJG?HYP)VGU4B0UQQq#Bu&Z zh`z@RegW_~BCzcdw$r7x&V|>&v|gtnrOW3ox^I&I+Qtz#0DAaIu9j2lSRAxEg^siq_2sU+YDun)`uJI;!~fkvR&vAqEqU=qjy=jDRWc%*m* zmVpk3Hv}AbxPfALQ3E`Rv;kZIcR;elf<}NB;0oB1uf?eKkuxHgLn8F8U(&rxmSdn# z-|F?Wpn1hu@+BBk03+-I)v|X3A08>p)+J5@aOp@o9<^oD_EW^PY&p%dK&wX9S%{%m z^8%2f42rubZv9#FHiy$L+mjlyEl%sc#Y_3FY8exgjnt!Sgy%^U4 z1v)T0ICPN3UAd#+9CEdj^b44!#5K)3_qbk9yOKw`qBagahndh7w-^gih4vtV70tCA zteB#ZR54nM*p%-rP!;7c9Z~-KYI;R<0xQ~0m^(okoB?~llw@;*G^yczJGN~#Mq9)w z6`)0}Ignh#dbbunKulQkNMW{L{bkO_nc(l@2M1NCo3;>akN^Mx07*qoM6N<$g3Zs` AxBvhE literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/settings_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/settings_light.png new file mode 100644 index 0000000000000000000000000000000000000000..5668acb81874c344e24bf522ab86f39a9dca3252 GIT binary patch literal 493 zcmV{DIYPY0&!tH8*Nv z&8lg$92A(5h4sajr5>;6`apvg9JuVe_TK00z0W$4$bSH^0X)*d1F$~H1XuK;G^yYP z;0tH~Gg=MV3Zn*_RypQ@3~=P^_}by!ICkTtfdwm@#Rv;N3*5=cc39Q-Gq44u9BxXs zia1sI7;Y_=FTu7Gu&(c8AkpW<32_Xc@-^%PiF3dQ@S_sNflVMfL|LQa#^w91SaU9E zS(#sfTu7tOfj5=o49I(UImIw61$S|&A1~H4=iegm?io|FWgT8dzJ`mQ(*rT!Nrd+J z0sFG;s<|nB8}!4k`C;)IS_${H_P0wVf_+h#!4_`iPBo5m1sbd zvp~($bflslDiw~{ zl|5otQF2%8(z5x)uIv>%IZgqql4}ccI6$tgqz8^%uUU*iW&OJcCk#NzGp3VyGL^uN jZV!D6_hy?|f^qN*`=q$gGRCt{2nZ0WiK^Vq=m*`2wRw==oK`@z65 zH~T#E?(91|GYd5m(*?X>3Q$m)^b~4=R2fqykVn8ns)RP+E)WIujDS19i7Z8L>+yIM zD40lI1$lcBE)`4*&<=D0y}(bEUe&d$O%`5d;TrS+Ip;t7-3q*SlDkx~w!4ey^n@7w zNJe&d>r~eSJaIbs8Mpv!1)70bD$T01|GqfwyMZOrs#Ab3L*-+97dHWe>X3DAKTx0G zdTVn4=+m_W(qhyI1;LDesUsb_>uH${+>(ZA#lZxl$r*{W72ZXAoX$BT(?lS0PEK?< zxl##r&d79!dc0bloG9mU8t*adm-a3^lXIq6OYE#UDD8K6rp1|Jlgv(Osdr|w)p+EX zOwN1sxF`=>-+7}`CfhuEY?sG9j~;xG$x@FV&CU|0>{;Veku{O+Ct`+G*N8tFL-ymE)?b@n7&4tuZre`8JYZ00000NkvXXu0mjf Ddy0JS literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/telegram_features_light.png b/src/main/resources/org/to/telegramfinalproject/Icons/telegram_features_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2d15e68513cf56c163ed4393df66c4b67589824c GIT binary patch literal 886 zcmV-+1Bv{JP)wp2=64fAS?W zch2|!pL3t*o-wA3XaKGQcg%DL0yEt~Igo_me+ncG+%QQ1unV{dq)Mon0?q?FLy}&Q z88Qsi2P4j{S1(!?y`g)@5sv^3IqWKc<-h^p8ZZWYk!Mue_5;iEXf=q2>$sFgFFAps zECFl*9upnbcC&pqgX@9UVLGEPy)^{%du>g?fal;d-~_M{Xa;KKv7g)IH*4e24=nKb zZUgsojk;D~Yk@c7&^WLIsEY7jrHsZ?dG|fgS{kqacBU2hDvq23W*5O>N2*siEZ2G+ zZ*x1)B)w>HBy*m}bJ_%=M7%!uX_lU49`ONzVp1Hsy;W{1G%jgT=RzQ&dKw>XWU)#HN;*p-@U@8jcVI)wN~XYNk2q^D0f)t=Cj#FovF(ab#J&_F21k4d z=*!nTKqK+dSYQ_P+3U3_HR48Lk_yU!Bn)^C+U!`quE=LM9RgV`UsvX{OC*3-F=p0F zlT-l5Vgz#3Bo)9+31%Ws02A_mi0%zA6o6_uNXO-|pR0ojlwp#M9~~vSH^O{3+yDRo M07*qoM6N<$g1bzLX#fBK literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/Icons/telegram_qna_dark.png b/src/main/resources/org/to/telegramfinalproject/Icons/telegram_qna_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..671d745809ab8feb9c16a88332b9606dff5cc9d6 GIT binary patch literal 1142 zcmV-+1d02JP)p{0O3VL_DCDF1-EF-TDmaD~=| z2?i38A4NqM3W=Mh!iI!|#Ke^wHiRIA014O#Mt)eq1yCvr0_Z|gqLD&_=8+S>mzbwE8F;375 zyaOBp{sk&IpMQXZKsV6R2nU`3HUhu7LEQ$90H0}Q=IM<;uBT1^v$D^DqiU11zXtT^ z<Heg~%HwBMz+S0BX9x?P3{SrfxR(Mf`c8V(lPXm8CM?NlKt5Y^i7q}1CRcPEMHkuuit>|}=^Gt{A z{86oNRD~0Xk1w1PMqOqXwAbQvg1R~KlG1wO$eR&twq!X{Mrc|1{NkMWDw5rftk5cB zvHRVAIO4gcGi$N$Q=utqG9ntQVtPL@tD5e0$d~=8O z7&VE7evNf;8W~GSqbY#n7442RKABeOpgc7hbG%};#%Po(6>qDOX|XZzy3QZH5+919 z2^i2A?KqIR(OgxNbJN1B1NvSktzwFnT2)@y?D zcyXjhb_VlY3UxWz#>BBu%mrT26QX4$m=D|vRd;r{H&6z(&m*J{X)N1A474g7DiV?# zA!1JfPG}4#irh2K5HU-x73}YNZhXhxbzI>KpE+)72Mfk}o;5L~J}*W%h~2|17iJ^U zUeg4)B4}22y5+(tRG`RQGf4udNKlS-``VnEN~sTr^nv^RGal#0kl>c zL4X$EJ>W~=A;skZ@CEQL&^!_ji~+U)e>tP>0$&3=H8V49#DVAZwCXo&+XbA|H$kof z-8MLn%Zb;4i_U=4z#8EB2&{>~THuVsy8tX5?l3y)6#fJ(N{GJ%_{HHLNj^KkG#PGk z8`uamB~>z{rwP~u47k~znhN}7O*dL~W+ws8*Et6`C!PIfLs`Hn4)7Gvmdx7Px?6$o zq}y_GTA7>(;q(JAF1fX}Z5Rjq=)$ac&bY+3aGEqMmeyy-WET}jVwTvD*iktFjMYli z32X;e1Fw|GnE~0fq{wC$_KA&=E6)z#-zvNLx4_&8+%A{=wL=Wp>h_63ODym#@F}&$ zMt4C3_OJTBtDXc8w4?B5rx5aDlx2hX*dgGSYx^4*Ul4z(zP*ym%|zY?SBS`*FoqlC zKt`V#z)jbuqo9saZY5on*U?z4$(jg9UULRsvQVhfZ=dV;Nrawt`u0TLdE$W>X$%2; zRh0Z{)@-QnSu1KLwn46s=ClcbW%v*lL zhPW%C!VjDyA0@=yleZxTigGgX$bF?}LqgnM*}A+M84ILwVx>WaA#xi?BqXmPdPO%3Eow{XtYeJSLs6;`&HQ$qQ4V48M=x3lQ1Jteud`5WK&(tV-y zg^w=$Fp(We;K=c&ZDt0Tq-p3vdaR*lw5DeX#|KS~(LI08~&;a{HEv{#Fx*VNBjMV7$ZqfHyx@{V* z$KGcHZ>dkOs}ls;(K%B8fG}>h%1(ce6d3B2`zj2!kB9?VVg+S8=-8)tKSNbs#@|M8ZU6uP07*qoM6N<$g2!4dWdHyG literal 0 HcmV?d00001 diff --git a/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml b/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml index 2065f58..41153a8 100644 --- a/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml +++ b/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml @@ -6,9 +6,8 @@ - @@ -17,32 +16,46 @@ + - - - -