Merge pull request #13 from PartowRoshani/Sidebar-Menu

Sidebar-Menu
This commit is contained in:
Asal Lotfi
2025-08-11 13:17:12 +03:30
committed by GitHub
21 changed files with 1521 additions and 60 deletions
@@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Client;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Database.PrivateChatDatabase; import org.to.telegramfinalproject.Database.PrivateChatDatabase;
import org.to.telegramfinalproject.Database.ContactDatabase;
import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ChatEntry;
import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.ContactEntry;
import org.to.telegramfinalproject.Models.SearchRequestModel; import org.to.telegramfinalproject.Models.SearchRequestModel;
@@ -501,6 +502,10 @@ public class ActionHandler {
entry.setOtherUserId(UUID.fromString(chat.getString("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(entry);
} }
@@ -555,9 +560,13 @@ public class ActionHandler {
ContactEntry entry = new ContactEntry( ContactEntry entry = new ContactEntry(
UUID.fromString(c.getString("contact_id")), UUID.fromString(c.getString("contact_id")),
c.getString("user_id"), c.getString("user_id"),
c.getString("contact_displayId"),
c.getString("profile_name"), c.getString("profile_name"),
c.optString("image_url", ""), 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"))
); );
Session.contactEntries.add(entry); Session.contactEntries.add(entry);
} }
@@ -839,7 +848,8 @@ public class ActionHandler {
System.out.println("3. Create Channel"); System.out.println("3. Create Channel");
System.out.println("4. Create group"); System.out.println("4. Create group");
System.out.println("5. View contacts"); System.out.println("5. View contacts");
System.out.println("6. Logout"); System.out.println("6. Show sidebar menu");
System.out.println("7. Logout");
System.out.print("Choose an option: "); System.out.print("Choose an option: ");
String choice = scanner.nextLine(); String choice = scanner.nextLine();
@@ -853,7 +863,8 @@ public class ActionHandler {
case "3" -> createChannel(); case "3" -> createChannel();
case "4" -> createGroup(); case "4" -> createGroup();
case "5" -> showContactList(); case "5" -> showContactList();
case "6" -> { case "6" -> showSidebarMenu();
case "7" -> {
logout(); logout();
return; return;
} }
@@ -862,10 +873,118 @@ public class ActionHandler {
} }
} }
// public void showContactList() {
// List<ContactEntry> contacts = Session.contactEntries;
// if (contacts.isEmpty()) {
// System.out.println("📭 You have no contacts.");
// return;
// }
//
// System.out.println("👥 Your Contacts:");
// for (int i = 0; i < contacts.size(); i++) {
// System.out.println((i + 1) + ". " + contacts.get(i));
// }
//
// System.out.print("Select a contact (0 to go back): ");
// int choice = scanner.nextInt();
// scanner.nextLine();
//
// if (choice == 0) return;
// if (choice < 1 || choice > contacts.size()) {
// System.out.println("❌ Invalid choice.");
// return;
// }
//
// ContactEntry selected = contacts.get(choice - 1);
// System.out.println("\n📇 What do you want to do with " + selected.getProfileName() + "?");
// System.out.println("1. View Profile");
// System.out.println("2. Send Message");
// System.out.print("Enter your choice: ");
// int action = scanner.nextInt();
// scanner.nextLine();
//
// switch (action) {
// case 1 -> viewProfile(selected.getContactId());
// case 2 -> startPrivateChat(selected);
// default -> System.out.println("❌ Invalid option.");
// }
// }
public void showContactList() { public void showContactList() {
List<ContactEntry> contacts = Session.contactEntries; System.out.println("1. View All Contacts");
System.out.println("2. Search Contacts");
System.out.println("Choose an option: (0 to go back)");
int option = scanner.nextInt();
scanner.nextLine();
// Handle invalid input
while (option < 0 || option > 2) {
System.out.println("Invalid choice. Try again: ");
option = scanner.nextInt();
scanner.nextLine();
}
List<ContactEntry> contacts;
if (option == 0) {
return;
}
else if (option == 1) {
contacts = Session.contactEntries;
} else if (option == 2) {
contacts = new ArrayList<>();
System.out.print("Enter name or user ID to search: ");
String searchTerm = scanner.nextLine();
// Handle invalid input
while (searchTerm.isEmpty()) {
System.out.print("Search key can not be empty. Try again: ");
searchTerm = scanner.nextLine();
}
// Send a request to server
JSONObject request = new JSONObject();
request.put("action", "search_contacts");
request.put("user_id", Session.getUserUUID());
request.put("search_term", searchTerm);
JSONObject response = ActionHandler.sendWithResponse(request);
if (!response.optString("status", "fail").equals("success")) {
System.out.println("Failed to search contacts: " + response.optString("message", "Unknown error"));
return;
}
JSONObject data = response.getJSONObject("data");
JSONArray contactsJson = data.getJSONArray("contacts");
for (int i = 0; i < contactsJson.length(); i++) {
JSONObject contact = contactsJson.getJSONObject(i);
UUID contactId = UUID.fromString(contact.getString("contact_id"));
String userId = contact.getString("user_id");
String contact_displayId = contact.getString("contact_display_id");
String profileName = contact.getString("profile_name");
String imageUrl = contact.optString("image_url", "");
boolean isBlocked = contact.getBoolean("is_blocked");
String lastSeenString = contact.getString("last_seen");
LocalDateTime lastSeen = null;
if (lastSeenString != null) {
lastSeen = LocalDateTime.parse(lastSeenString);
}
contacts.add(new ContactEntry(contactId, userId, contact_displayId, profileName, imageUrl, isBlocked, lastSeen));
}
} else {
System.out.println("❌ Invalid choice.");
return;
}
if (contacts.isEmpty()) { if (contacts.isEmpty()) {
System.out.println("📭 You have no contacts."); System.out.println("📭 No contacts found.");
return; return;
} }
@@ -888,6 +1007,7 @@ public class ActionHandler {
System.out.println("\n📇 What do you want to do with " + selected.getProfileName() + "?"); System.out.println("\n📇 What do you want to do with " + selected.getProfileName() + "?");
System.out.println("1. View Profile"); System.out.println("1. View Profile");
System.out.println("2. Send Message"); System.out.println("2. Send Message");
System.out.println("3. Remove Contact");
System.out.print("Enter your choice: "); System.out.print("Enter your choice: ");
int action = scanner.nextInt(); int action = scanner.nextInt();
scanner.nextLine(); scanner.nextLine();
@@ -895,10 +1015,29 @@ public class ActionHandler {
switch (action) { switch (action) {
case 1 -> viewProfile(selected.getContactId()); case 1 -> viewProfile(selected.getContactId());
case 2 -> startPrivateChat(selected); case 2 -> startPrivateChat(selected);
case 3 -> {
// Send a request to server
JSONObject request = new JSONObject();
request.put("action", "remove_contact");
request.put("user_id", Session.getUserUUID()); // Current user
request.put("contact_id", selected.getContactId().toString()); // Contact to remove
JSONObject response = ActionHandler.sendWithResponse(request);
if ("success".equals(response.optString("status"))) {
System.out.println("✅ Contact removed successfully.");
Session.contactEntries.remove(selected); // Remove from local session list
} else {
System.out.println("❌ Failed to remove contact: " +
response.optString("message", "Unknown error"));
}
}
default -> System.out.println("❌ Invalid option."); default -> System.out.println("❌ Invalid option.");
} }
} }
private void viewProfile(UUID targetId) { private void viewProfile(UUID targetId) {
JSONObject req = new JSONObject(); JSONObject req = new JSONObject();
req.put("action", "view_profile"); req.put("action", "view_profile");
@@ -1001,13 +1140,35 @@ public class ActionHandler {
System.out.println("\nYour Chats:"); System.out.println("\nYour Chats:");
System.out.println("0. 📦 Archived Chats"); System.out.println("0. 📦 Archived Chats");
// 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++) { for (int i = 0; i < Session.activeChats.size(); i++) {
ChatEntry entry = Session.activeChats.get(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) String time = (entry.getLastMessageTime() == null)
? "No messages yet" ? "No messages yet"
: entry.getLastMessageTime().toString(); : entry.getLastMessageTime().toString();
System.out.println((i + 1) + ". [" + entry.getType() + "] " + System.out.println(index + ". [" + entry.getType() + "] " +
entry.getName() + " - Last: " + time); entry.getName() + " - Last: " + time);
index++;
} }
System.out.print("Select a chat by number: "); System.out.print("Select a chat by number: ");
@@ -1018,14 +1179,33 @@ public class ActionHandler {
return; return;
} }
int index = choice - 1; if (choice == savedMessagesIndex) {
new SidebarHandler(scanner, this).getSavedMessagesData(Session.getUserUUID());
return;
}
if (index < 0 || index >= Session.activeChats.size()) { // Adjust for Saved Messages if it was in the list
int baseIndex = (savedMessagesIndex != -1 && choice > savedMessagesIndex) ? 1 : 0;
int chatIndex = choice - 1 - baseIndex;
if (chatIndex < 0 || chatIndex >= Session.activeChats.size()) {
System.out.println("Invalid selection."); System.out.println("Invalid selection.");
return; return;
} }
ChatEntry selected = Session.activeChats.get(index); // Find the actual index of the chat, skipping the saved_messages entry
int actualIndex = 0;
for (int i = 0; i < Session.activeChats.size(); i++) {
if (Session.activeChats.get(i).getType().equalsIgnoreCase("saved_messages")) {
continue; // Skip saved_messages
}
if (actualIndex == chatIndex) {
break;
}
actualIndex++;
}
ChatEntry selected = Session.activeChats.get(actualIndex);
openChat(selected); openChat(selected);
} }
@@ -3238,11 +3418,11 @@ public class ActionHandler {
System.out.print("Enter your message: "); System.out.print("Enter your message: ");
String content = scanner.nextLine(); String content = scanner.nextLine();
System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): "); System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): ");
String messageType = scanner.nextLine().toUpperCase(); String messageType = scanner.nextLine().toUpperCase();
Set<String> allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); Set<String> allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE");
while (!allowedTypes.contains(messageType)) { while (!allowedTypes.contains(messageType)) {
System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE): "); System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): ");
messageType = scanner.nextLine().toUpperCase(); messageType = scanner.nextLine().toUpperCase();
} }
@@ -3252,9 +3432,32 @@ public class ActionHandler {
while (true) { while (true) {
System.out.print("File URL: "); System.out.print("File URL: ");
String fileUrl = scanner.nextLine(); String fileUrl = scanner.nextLine();
System.out.print("File Type (IMAGE / VIDEO / FILE): ");
// URL validation
if (fileUrl.isEmpty()) {
System.out.print("URL can not be empty. Try again.");
continue;
}
if (fileUrl.contains(" ")) {
System.out.println("URL cannot contain spaces. Try again.");
continue;
}
if (!fileUrl.isEmpty() && !fileUrl.matches("^(http|https)://.*$")) {
System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link.");
continue;
}
System.out.print("File Type (IMAGE / VIDEO / FILE / AUDIO): ");
String fileType = scanner.nextLine().toUpperCase(); String fileType = scanner.nextLine().toUpperCase();
Set<String> 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(); JSONObject fileJson = new JSONObject();
fileJson.put("file_url", fileUrl); fileJson.put("file_url", fileUrl);
fileJson.put("file_type", fileType); fileJson.put("file_type", fileType);
@@ -3676,6 +3879,52 @@ public class ActionHandler {
} }
} }
private void showSidebarMenu() {
System.out.println("\n--- Sidebar Menu ---");
System.out.println("1. View Profile");
System.out.println("2. New Group");
System.out.println("3. New Channel");
System.out.println("4. View Contacts");
System.out.println("5. Saved messages");
System.out.println("6. Settings");
System.out.println("7. Telegram features");
System.out.println("8. Telegram Q&A");
System.out.println("0. Back");
System.out.println("Choose your action: (0 to 8)");
// Handle invalid input
int action;
while (true) {
if (scanner.hasNextInt()) {
action = scanner.nextInt();
scanner.nextLine(); // Clear newline
if (action >= 0 && action <= 8) {
break;
}
System.out.println("Invalid input. Please enter a number between 0 and 8.");
} else {
scanner.nextLine();
System.out.println("Invalid input. Try again.");
}
}
SidebarHandler sidebarHandler = new SidebarHandler(scanner, this);
switch (action) {
case 0 -> {
return;
}
case 1 -> sidebarHandler.handleSidebarAction(SidebarAction.MY_PROFILE);
case 2 -> sidebarHandler.handleSidebarAction(SidebarAction.NEW_GROUP);
case 3 -> sidebarHandler.handleSidebarAction(SidebarAction.NEW_CHANNEL);
case 4 -> sidebarHandler.handleSidebarAction(SidebarAction.CONTACTS);
case 5 -> sidebarHandler.handleSidebarAction(SidebarAction.SAVED_MESSAGES);
case 6 -> sidebarHandler.handleSidebarAction(SidebarAction.SETTINGS);
case 7 -> sidebarHandler.handleSidebarAction(SidebarAction.FEATURES);
case 8 -> sidebarHandler.handleSidebarAction(SidebarAction.Q_AND_A);
default -> System.out.println("Invalid action.");
}
}
} }
@@ -0,0 +1,12 @@
package org.to.telegramfinalproject.Client;
public enum SidebarAction {
MY_PROFILE,
NEW_GROUP,
NEW_CHANNEL,
CONTACTS,
SAVED_MESSAGES,
SETTINGS,
FEATURES,
Q_AND_A
}
@@ -0,0 +1,465 @@
package org.to.telegramfinalproject.Client;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.to.telegramfinalproject.Models.ChatEntry;
import org.to.telegramfinalproject.Models.Message;
import java.time.LocalDateTime;
import java.util.*;
import static org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse;
public class SidebarHandler {
private final Scanner scanner;
private final ActionHandler actionHandler;
private final String userUUID;
public SidebarHandler(Scanner scanner, ActionHandler actionHandler) {
this.scanner = scanner;
this.actionHandler = actionHandler;
this.userUUID = Session.getUserUUID();
}
public void handleSidebarAction(SidebarAction action) {
switch (action) {
case MY_PROFILE:
openUserProfile();
break;
case NEW_GROUP:
createNewGroup();
break;
case NEW_CHANNEL:
createNewChannel();
break;
case CONTACTS:
showContacts();
break;
case SAVED_MESSAGES:
getSavedMessagesData(userUUID);
break;
case SETTINGS:
openSettings();
break;
case FEATURES:
showTelegramFeatures();
break;
case Q_AND_A:
showTelegramQA();
break;
}
}
private void openUserProfile() {
// Step 1: Request profile info from the server
JSONObject request = new JSONObject();
request.put("action", "get_user_profile");
JSONObject response = sendWithResponse(request);
if (response == null || !response.optString("status", "fail").equals("success")) {
System.out.println("Failed to load profile information.");
return;
}
JSONObject profile;
try {
profile = response.getJSONObject("data");
} catch (JSONException e) {
System.out.println("Received malformed profile data from server.");
return;
}
String profileName = profile.optString("profile_name", "NOT-AVAILABLE");
String userId = profile.optString("user_id", "NOT-AVAILABLE");
String status = profile.optString("status", "NOT-AVAILABLE");
String bio = profile.optString("bio", "NOT-AVAILABLE");
String profilePictureUrl = profile.optString("profile_picture_url", "NOT-AVAILABLE");
// Step 2: Show profile info
System.out.println("===== My Profile =====");
System.out.println("Profile Picture URL : " + profilePictureUrl);
System.out.println("Profile Name : " + profileName);
System.out.println("User ID : @" + userId);
System.out.println("Status : " + status);
System.out.println("Bio : " + bio);
System.out.println("======================");
// Step 3: Ask if user wants to edit anything
System.out.println("Do you want to edit any of these? (yes/no)");
while (true) {
String choice = scanner.nextLine().trim().toLowerCase();
if (choice.equals("no")) return;
if (!choice.equals("yes")) {
System.out.println("Invalid input. Please enter 'yes' or 'no'.");
continue;
}
// Step 4: Show editable fields
System.out.println("Which field do you want to edit?");
System.out.println("1. Profile Name");
System.out.println("2. User ID");
System.out.println("3. Bio");
System.out.println("4. Profile Picture URL");
System.out.println("0. Cancel");
String option = scanner.nextLine().trim();
switch (option) {
case "1" -> editProfileName();
case "2" -> editUserId();
case "3" -> editBio();
case "4" -> editProfilePictureUrl();
case "0" -> { return; }
default -> System.out.println("Invalid choice. Try again.");
}
// Re-fetch and re-display profile after editing
openUserProfile();
return;
}
}
private void editProfileName() {
System.out.println("Enter your new profile name:");
String newProfileName = scanner.nextLine().trim();
// Step 1: Validate input
while (newProfileName.trim().isEmpty()) {
System.out.println("Profile name cannot be empty. Enter another name.");
newProfileName = scanner.nextLine().trim();
}
// Step 2: Create request
JSONObject request = new JSONObject();
request.put("action", "edit_profile_name");
request.put("new_profile_name", newProfileName);
// Step 3: Send request and receive response
JSONObject response = sendWithResponse(request);
// Step 4: Handle response
if (response == null || !response.optString("status", "fail").equals("success")) {
System.out.println("Failed to update profile name.");
} else {
System.out.println("Profile name updated successfully!");
}
}
private void editUserId() {
while (true) {
System.out.println("Enter your new user ID: ");
String newUserId = scanner.nextLine().trim();
if (newUserId.isEmpty()) {
System.out.println("User ID cannot be empty.");
continue;
}
if (newUserId.contains(" ")) {
System.out.println("User ID cannot contain spaces.");
continue;
}
if (!newUserId.matches("^[a-zA-Z0-9_]+$")) {
System.out.println("User ID can only contain letters, digits, and underscores.");
continue;
}
// Send to server
JSONObject request = new JSONObject();
request.put("action", "edit_user_id");
request.put("new_user_id", newUserId);
JSONObject response = sendWithResponse(request);
if (response.getString("status").equals("success")) {
System.out.println("User ID updated successfully.");
break;
} else {
// Server-side error message (e.g., ID already exists)
System.out.println(response.getString("message"));
}
}
}
private void editBio() {
System.out.println("Enter your new bio:");
String newBio = scanner.nextLine().trim();
// Limit the bio length
if (newBio.length() > 70) {
System.out.println("Bio cannot be more than 70 characters.");
return;
}
JSONObject request = new JSONObject();
request.put("action", "edit_bio");
request.put("new_bio", newBio);
JSONObject response = sendWithResponse(request);
if (response.getString("status").equals("success")) {
System.out.println("Bio updated successfully.");
} else {
System.out.println(response.getString("message"));
}
}
private void editProfilePictureUrl() {
while (true) {
System.out.println("Enter new profile picture URL (or leave empty to remove):");
String newImageUrl = scanner.nextLine().trim();
// URL validation
if (newImageUrl.contains(" ")) {
System.out.println("URL cannot contain spaces.");
continue;
}
if (!newImageUrl.isEmpty() && !newImageUrl.matches("^(http|https)://.*$")) {
System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link.");
continue;
}
JSONObject request = new JSONObject();
request.put("action", "edit_profile_picture");
request.put("new_image_url", newImageUrl);
JSONObject response = sendWithResponse(request);
if (response.getString("status").equals("success")) {
System.out.println("Bio updated successfully.");
} else {
System.out.println(response.getString("message"));
}
break;
}
}
private void createNewGroup() {
actionHandler.createGroup();
}
private void createNewChannel() {
actionHandler.createChannel();
}
private void showContacts() {
actionHandler.showContactList();
}
public void 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<Message> 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<Message> messages) {
Scanner scanner = new Scanner(System.in);
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());
}
}
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().toUpperCase();
Set<String> 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<String> 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.");
}
}
}
private void openSettings() {
System.out.println("⚙️ Opening settings...");
}
private void showTelegramFeatures() {
System.out.println("🌟 Showing Telegram features...");
}
private void showTelegramQA() {
System.out.println("❓ Showing Q&A...");
}
}
@@ -5,6 +5,7 @@ import org.to.telegramfinalproject.Models.ContactEntry;
import org.to.telegramfinalproject.Models.User; import org.to.telegramfinalproject.Models.User;
import java.sql.*; import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -46,7 +47,7 @@ public class ContactDatabase {
public boolean removeContact(UUID user_id, UUID contact_id) { public static boolean removeContact(UUID user_id, UUID contact_id) {
String sql = "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?"; String sql = "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?";
try (Connection connection = getConnection()) { try (Connection connection = getConnection()) {
PreparedStatement stmt = connection.prepareStatement(sql); PreparedStatement stmt = connection.prepareStatement(sql);
@@ -172,35 +173,6 @@ public class ContactDatabase {
return false; return false;
} }
public List<Contact> searchContacts(UUID user_id, String searchTerm) {
List<Contact> results = new ArrayList<>(); //ILIKE case-insensitive
String sql = """
SELECT c.contact_id, c.added_at, c.is_blocked
FROM contacts c
JOIN users u ON c.contact_id = u.internal_uuid
WHERE c.user_id = ? AND (u.user_id ILIKE ? OR u.profile_name ILIKE ?)
""";
try (Connection connection = getConnection()) {
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setObject(1, user_id);
stmt.setString(2, "%" + searchTerm + "%");
stmt.setString(3, "%" + searchTerm + "%");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
UUID contactId = (UUID) rs.getObject("contact_id");
boolean isBlocked = rs.getBoolean("is_blocked");
Timestamp addedAt = rs.getTimestamp("added_at");
Contact contact = new Contact(user_id, contactId);
contact.setIs_blocked(isBlocked);
contact.setAdd_at(addedAt.toLocalDateTime());
results.add(contact);
}
} catch (SQLException e) {
e.printStackTrace();
}
return results;
}
public static boolean deleteChatOneSide(UUID currentUserId, UUID otherUserId) { public static boolean deleteChatOneSide(UUID currentUserId, UUID otherUserId) {
String sql = """ String sql = """
@@ -306,6 +278,50 @@ public class ContactDatabase {
return entries; return entries;
} }
public static List<ContactEntry> searchContacts(UUID userId, String searchTerm) {
List<ContactEntry> results = new ArrayList<>();
String sql = """
SELECT u.internal_uuid, u.user_id, c.is_blocked
FROM contacts c
JOIN users u ON c.contact_id = u.internal_uuid
WHERE c.user_id = ?
AND (u.user_id ILIKE ? OR u.profile_name ILIKE ?)
""";
try (Connection connection = getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setObject(1, userId);
stmt.setString(2, "%" + searchTerm + "%");
stmt.setString(3, "%" + searchTerm + "%");
ResultSet rs = stmt.executeQuery();
userDatabase userDB = new userDatabase();
while (rs.next()) {
UUID contactId = (UUID) rs.getObject("internal_uuid");
String displayId = rs.getString("user_id");
String contact_displayId = userDB.getUserId(contactId);
String profileName = userDB.getProfileName(contactId);
String imageUrl = userDB.getProfilePicture(contactId);
boolean isBlocked = rs.getBoolean("is_blocked");
String lastSeenString = userDB.getLastSeen(contactId);
// Convert to LocalDateTime
LocalDateTime lastSeen = null;
if (!"Unknown".equals(lastSeenString)) {
lastSeen = LocalDateTime.parse(lastSeenString);
}
results.add(new ContactEntry(contactId, displayId, contact_displayId, profileName, imageUrl, isBlocked, lastSeen));
}
} catch (SQLException e) {
e.printStackTrace();
}
return results;
}
} }
@@ -431,8 +431,10 @@ public class MessageDatabase {
rs.getBoolean("is_deleted_globally"), rs.getBoolean("is_deleted_globally"),
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null, rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null, rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null,
); rs.getBoolean("is_deleted_globally"),
rs.getTimestamp("edited_at") != null ? rs.getTimestamp("edited_at").toLocalDateTime() : null
));
messages.add(message); messages.add(message);
} }
@@ -534,10 +536,11 @@ public class MessageDatabase {
rs.getString("status"), rs.getString("status"),
(UUID) rs.getObject("reply_to_id"), (UUID) rs.getObject("reply_to_id"),
rs.getBoolean("is_edited"), rs.getBoolean("is_edited"),
rs.getBoolean("is_deleted_globally"),
(UUID) rs.getObject("original_message_id"), (UUID) rs.getObject("original_message_id"),
(UUID) rs.getObject("forwarded_by"), (UUID) rs.getObject("forwarded_by"),
(UUID) rs.getObject("forwarded_from") (UUID) rs.getObject("forwarded_from"),
rs.getBoolean("is_deleted_globally"),
(rs.getTimestamp("edited_at") != null) ? rs.getTimestamp("edited_at").toLocalDateTime() : null
); );
} }
@@ -1002,8 +1005,57 @@ public class MessageDatabase {
return messages; return messages;
} }
public static boolean insertSavedMessage(Message message) {
String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type, send_at, status, reply_to_id, is_edited, edited_at," +
" original_message_id, forwarded_by, forwarded_from, is_deleted_globally) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, message.getMessage_id()); // message_id
ps.setObject(2, message.getSender_id()); // sender_id
ps.setString(3, message.getReceiver_type()); // receiver_type (private)
ps.setObject(4, message.getReceiver_id()); // receiver_id (same as sender_id for saved messages)
ps.setString(5, message.getContent()); // content
ps.setString(6, message.getMessage_type()); // message_type
ps.setTimestamp(7, Timestamp.valueOf(message.getSend_at())); // send_at
ps.setString(8, message.getStatus()); // status
if (message.getReply_to_id() != null)
ps.setObject(9, message.getReply_to_id()); // reply_to_id
else
ps.setNull(9, Types.OTHER);
ps.setBoolean(10, message.isIs_edited()); // is_edited
if (message.getEdited_at() != null)
ps.setTimestamp(11, Timestamp.valueOf(message.getEdited_at())); // edited_at
else
ps.setNull(11, Types.TIMESTAMP);
if (message.getOriginal_message_id() != null)
ps.setObject(12, message.getOriginal_message_id()); // original_message_id
else
ps.setNull(12, Types.OTHER);
if (message.getForwarded_by() != null)
ps.setObject(13, message.getForwarded_by()); // forwarded_by
else
ps.setNull(13, Types.OTHER);
if (message.getForwarded_from() != null)
ps.setObject(14, message.getForwarded_from()); // forwarded_from
else
ps.setNull(14, Types.OTHER);
ps.setBoolean(15, message.getIs_deleted_globally()); // is_deleted_globally
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
} }
@@ -7,6 +7,10 @@ import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID; import java.util.UUID;
public class PrivateChatDatabase { public class PrivateChatDatabase {
@@ -29,6 +33,34 @@ public class PrivateChatDatabase {
return new ArrayList<>(); return new ArrayList<>();
} }
public static UUID getOrCreateSavedMessagesChat(UUID userId) {
String query = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
try (Connection conn = ConnectionDb.connect()) {
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setObject(1, userId);
stmt.setObject(2, userId); // Saved Messages is self-chat
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return (UUID) rs.getObject("chat_id"); // Chat already exists
} else {
// Create a new chat
UUID chatId = UUID.randomUUID();
String insert = "INSERT INTO private_chat (chat_id, user1_id, user2_id) VALUES (?, ?, ?)";
try (PreparedStatement insertStmt = conn.prepareStatement(insert)) {
insertStmt.setObject(1, chatId);
insertStmt.setObject(2, userId);
insertStmt.setObject(3, userId);
insertStmt.executeUpdate();
return chatId;
}
}
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static UUID findChatIdByUsers(UUID user1, UUID user2) { public static UUID findChatIdByUsers(UUID user1, UUID user2) {
UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2; UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2;
@@ -152,7 +152,7 @@ public class userDatabase {
} }
public boolean save(User user) { public boolean save(User user) {
String query = "INSERT INTO users (user_id, internal_uuid, username, password, profile_name) VALUES (?, ?, ?, ?, ?)"; String query = "INSERT INTO users (user_id, internal_uuid, username, password, profile_name, bio, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)";
try { try {
boolean var5; boolean var5;
@@ -165,6 +165,8 @@ public class userDatabase {
stmt.setString(3, user.getUsername()); stmt.setString(3, user.getUsername());
stmt.setString(4, user.getPassword()); stmt.setString(4, user.getPassword());
stmt.setString(5, user.getProfile_name()); stmt.setString(5, user.getProfile_name());
stmt.setString(6, user.getBio());
stmt.setString(7, user.getImage_url());
var5 = stmt.executeUpdate() > 0; var5 = stmt.executeUpdate() > 0;
} }
@@ -223,7 +225,7 @@ public class userDatabase {
} }
private User extractUser(ResultSet rs) throws SQLException { private User extractUser(ResultSet rs) throws SQLException {
return new User(rs.getString("user_id"), UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), rs.getString("profile_name")); return new User(rs.getString("user_id"), UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), rs.getString("profile_name"), rs.getString("bio"), rs.getString("image_url"));
} }
public boolean deleteByUUID(UUID uuid) { public boolean deleteByUUID(UUID uuid) {
@@ -288,7 +290,9 @@ public class userDatabase {
UUID.fromString(rs.getString("internal_uuid")), UUID.fromString(rs.getString("internal_uuid")),
rs.getString("username"), rs.getString("username"),
rs.getString("password"), rs.getString("password"),
rs.getString("profile_name") rs.getString("profile_name"),
rs.getString("bio"),
rs.getString("image_url")
); );
user.setBio(rs.getString("bio")); user.setBio(rs.getString("bio"));
@@ -349,6 +353,61 @@ public class userDatabase {
} }
} }
public static String getProfileName(UUID userId) {
String sql = "SELECT profile_name FROM users WHERE internal_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String profileName = rs.getString("profile_name");
return profileName;
}
} catch (SQLException e) {
e.printStackTrace();
}
return "Unknown";
}
public static String getProfilePicture(UUID userId) {
String sql = "SELECT image_url FROM users WHERE internal_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String imageUrl = rs.getString("image_url");
return imageUrl;
}
} catch (SQLException e) {
e.printStackTrace();
}
return "Unknown";
}
public static String getUserId(UUID userId) {
String sql = "SELECT user_id FROM users WHERE internal_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String user_id = rs.getString("user_id");
return user_id;
}
} catch (SQLException e) {
e.printStackTrace();
}
return "Unknown";
}
} }
@@ -14,6 +14,7 @@ public class ChatEntry {
private LocalDateTime lastMessageTime; private LocalDateTime lastMessageTime;
private boolean archived = false; private boolean archived = false;
private UUID otherUser; private UUID otherUser;
private boolean savedMessages = false;
private boolean isOwner = false; private boolean isOwner = false;
@@ -140,4 +141,12 @@ public class ChatEntry {
public UUID getOtherUserId(){ public UUID getOtherUserId(){
return otherUser; return otherUser;
} }
public boolean isSavedMessages() {
return savedMessages;
}
public void setSavedMessages(boolean savedMessages) {
this.savedMessages = savedMessages;
}
} }
@@ -1,7 +1,7 @@
package org.to.telegramfinalproject.Models; package org.to.telegramfinalproject.Models;
import java.time.LocalDateTime;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.UUID; import java.util.UUID;
public class ContactEntry { public class ContactEntry {
@@ -10,6 +10,9 @@ public class ContactEntry {
private String profileName; private String profileName;
private String imageUrl; private String imageUrl;
private boolean isBlocked; private boolean isBlocked;
private LocalDateTime lastSeenTime;
private String contact_displayId;
public ContactEntry(UUID contactId, String userId, String profileName, String imageUrl, boolean isBlocked) { public ContactEntry(UUID contactId, String userId, String profileName, String imageUrl, boolean isBlocked) {
this.contactId = contactId; this.contactId = contactId;
@@ -19,6 +22,16 @@ public class ContactEntry {
this.isBlocked = isBlocked; this.isBlocked = isBlocked;
} }
public ContactEntry(UUID contactId, String userId,String contact_displayId , String profileName, String imageUrl, boolean isBlocked, LocalDateTime lastSeenTime){
this.contactId = contactId;
this.userId = userId;
this.contact_displayId = contact_displayId;
this.profileName = profileName;
this.imageUrl = imageUrl;
this.isBlocked = isBlocked;
this.lastSeenTime = lastSeenTime;
}
public UUID getContactId() { public UUID getContactId() {
return contactId; return contactId;
} }
@@ -39,9 +52,25 @@ public class ContactEntry {
return isBlocked; return isBlocked;
} }
public LocalDateTime getLastSeenTime() {
return lastSeenTime;
}
public void setLastSeenTime(LocalDateTime lastSeenTime) {
this.lastSeenTime = lastSeenTime;
}
public String getContact_displayId() {
return contact_displayId;
}
public void setContact_displayId(String contact_displayId) {
this.contact_displayId = contact_displayId;
}
@Override @Override
public String toString() { public String toString() {
return profileName + " (" + userId + ")" + (isBlocked ? " [Blocked]" : ""); return profileName + " (@" + contact_displayId + ")" + (isBlocked ? " [Blocked]" : "") + " Last Seen:" + (lastSeenTime != null ? " " + lastSeenTime.toString() : "");
} }
@@ -185,7 +185,7 @@ public class JsonUtil {
obj.put("last_message_time", entry.getLastMessageTime() == null ? JSONObject.NULL : entry.getLastMessageTime().toString()); obj.put("last_message_time", entry.getLastMessageTime() == null ? JSONObject.NULL : entry.getLastMessageTime().toString());
obj.put("is_owner", entry.isOwner()); obj.put("is_owner", entry.isOwner());
obj.put("is_admin", entry.isAdmin()); obj.put("is_admin", entry.isAdmin());
obj.put("is_saved_messages", entry.isSavedMessages());
jsonArray.put(obj); jsonArray.put(obj);
} }
@@ -16,15 +16,14 @@ public class Message {
private String status; private String status;
private UUID reply_to_id; private UUID reply_to_id;
private boolean is_edited; private boolean is_edited;
private boolean is_deleted_globally;
private UUID original_message_id; private UUID original_message_id;
private UUID forwarded_by; private UUID forwarded_by;
private UUID forwarded_from; private UUID forwarded_from;
private List<FileAttachment> attachments; private List<FileAttachment> attachments;
private boolean is_deleted_globally;
private LocalDateTime edited_at;
private transient String sender_name; private transient String sender_name;
private transient String receiver_name; private transient String receiver_name;
private LocalDateTime edited_at;
// ✅ Full Constructor // ✅ Full Constructor
@@ -48,6 +47,27 @@ public class Message {
this.forwarded_from = forwarded_from; this.forwarded_from = forwarded_from;
} }
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
String message_type, LocalDateTime send_at, String status,
UUID reply_to_id, boolean is_edited, UUID original_message_id,
UUID forwarded_by, UUID forwarded_from,boolean is_deleted_globally, LocalDateTime edited_at) {
this.message_id = message_id;
this.sender_id = sender_id;
this.receiver_type = receiver_type;
this.receiver_id = receiver_id;
this.content = content;
this.message_type = message_type;
this.send_at = send_at;
this.status = status;
this.reply_to_id = reply_to_id;
this.is_edited = is_edited;
this.original_message_id = original_message_id;
this.forwarded_by = forwarded_by;
this.forwarded_from = forwarded_from;
this.is_deleted_globally = is_deleted_globally;
this.edited_at = edited_at;
}
// ✅ Short Constructors // ✅ Short Constructors
//for normal messages //for normal messages
@@ -178,5 +198,18 @@ public class Message {
this.edited_at = edited_at; this.edited_at = edited_at;
} }
public void setIs_deleted_globally(boolean is_deleted_globally) {
this.is_deleted_globally = is_deleted_globally;
}
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;
}
} }
@@ -22,12 +22,14 @@ public class User {
private List<Message> unreadMessages; private List<Message> unreadMessages;
private List<ChatEntry> chatList; private List<ChatEntry> chatList;
public User(String user_id, UUID internal_uuid, String username, String password, String profile_name) { public User(String user_id, UUID internal_uuid, String username, String password, String profile_name, String bio, String image_url) {
this.user_id = user_id; this.user_id = user_id;
this.internal_uuid = internal_uuid; this.internal_uuid = internal_uuid;
this.username = username; this.username = username;
this.password = password; this.password = password;
this.profile_name = profile_name; this.profile_name = profile_name;
this.bio = bio;
this.image_url = image_url;
} }
public void setUser_id(String user_id) { public void setUser_id(String user_id) {
@@ -20,7 +20,7 @@ public class AuthService {
} else { } else {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
password = PasswordHashing.hash(password); password = PasswordHashing.hash(password);
User user = new User(user_id, uuid, username, password, profile_name); User user = new User(user_id, uuid, username, password, profile_name, "", "");
return this.userDb.save(user); return this.userDb.save(user);
} }
} else { } else {
@@ -1,5 +1,6 @@
package org.to.telegramfinalproject.Server; package org.to.telegramfinalproject.Server;
import javafx.geometry.Side;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Database.*; import org.to.telegramfinalproject.Database.*;
@@ -111,10 +112,13 @@ public class ClientHandler implements Runnable {
JSONObject c = new JSONObject(); JSONObject c = new JSONObject();
c.put("user_id", contact.getUser_id().toString()); c.put("user_id", contact.getUser_id().toString());
c.put("contact_id", contact.getContact_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()); c.put("is_blocked", contact.getIs_blocked());
c.put("profile_name", target.getProfile_name()); c.put("profile_name", target.getProfile_name());
c.put("image_url", target.getImage_url()); c.put("image_url", target.getImage_url());
c.put("last_seen", Contact.getLast_seen());
contactList.put(c); contactList.put(c);
} }
@@ -179,6 +183,10 @@ public class ClientHandler implements Runnable {
); );
entry.setOtherUserId(otherId); entry.setOtherUserId(otherId);
if (currentUser.getInternal_uuid() == otherId) {
entry.setSavedMessages(true);
}
if (archivedChatIds.contains(chat.getChat_id())) { if (archivedChatIds.contains(chat.getChat_id())) {
archivedChatList.add(entry); archivedChatList.add(entry);
chatList.add(entry); chatList.add(entry);
@@ -715,6 +723,8 @@ public class ClientHandler implements Runnable {
LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private"); LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private");
boolean isSavedMessages = chat.getUser1_id().equals(currentUser.getInternal_uuid())
&& chat.getUser2_id().equals(currentUser.getInternal_uuid());
ChatEntry entry = new ChatEntry( ChatEntry entry = new ChatEntry(
chat.getChat_id(), chat.getChat_id(),
@@ -728,6 +738,11 @@ public class ClientHandler implements Runnable {
); );
entry.setOtherUserId(otherId); entry.setOtherUserId(otherId);
// Mark it as saved messages if it's the special self-chat
if (isSavedMessages) {
entry.setSavedMessages(true);
}
if (archivedChatIds.contains(chat.getChat_id())) { if (archivedChatIds.contains(chat.getChat_id())) {
archivedChatList.add(entry); archivedChatList.add(entry);
chatList.add(entry); chatList.add(entry);
@@ -2409,8 +2424,103 @@ public class ClientHandler implements Runnable {
break; break;
} }
case "get_user_profile": {
// Get the current user's UUID
UUID user_UUID = this.currentUser.getInternal_uuid();
// Get the profile JSON from SidebarService
JSONObject data = SidebarService.getUserProfile(user_UUID);
if (data == null) {
response = new ResponseModel("error", "Failed to retrieve user profile.");
} else {
response = new ResponseModel("success", "User profile retrieved successfully.", data);
}
break;
}
case "edit_profile_name": {
// Validate input
if (!requestJson.has("new_profile_name")) {
response = new ResponseModel("error", "Missing new profile name.");
break;
}
UUID user_UUID = this.currentUser.getInternal_uuid();
String newProfileName = requestJson.getString("new_profile_name");
response = SidebarService.updateProfileName(user_UUID, newProfileName);
break;
}
case "edit_user_id": {
// Validate input
if (!requestJson.has("new_user_id")) {
response = new ResponseModel("error", "Missing new user ID.");
break;
}
UUID user_UUID = this.currentUser.getInternal_uuid();
String newUserId = requestJson.getString("new_user_id");
response = SidebarService.updateUserId(user_UUID, newUserId);
break;
}
case "edit_bio": {
// Validate input
if (!requestJson.has("new_bio")) {
response = new ResponseModel("error", "Missing new bio.");
break;
}
UUID user_UUID = this.currentUser.getInternal_uuid();
String newBio = requestJson.getString("new_bio").trim();
response = SidebarService.updateBio(user_UUID, newBio);
break;
}
case "edit_profile_picture": {
// Validate input
if (!requestJson.has("new_image_url")) {
response = new ResponseModel("error", "Missing new image url.");
break;
}
UUID user_UUID = this.currentUser.getInternal_uuid();
String newImageUrl = requestJson.getString("new_image_url").trim();
response = SidebarService.updateProfilePicture(user_UUID, newImageUrl);
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");
String search_term = requestJson.getString("search_term");
response = SidebarService.handleSearchContacts(user_id, search_term);
break;
}
case "remove_contact": {
UUID user_id = UUID.fromString(requestJson.getString("user_id"));
UUID contactId = UUID.fromString(requestJson.getString("contact_id"));
response = SidebarService.handleRemoveContact(user_id, contactId);
break;
}
default: default:
response = new ResponseModel("error", "Unknown action: " + action); response = new ResponseModel("error", "Unknown action: " + action);
@@ -0,0 +1,297 @@
package org.to.telegramfinalproject.Server;
import org.json.JSONArray;
import org.json.JSONObject;
import org.to.telegramfinalproject.Database.ContactDatabase;
import org.to.telegramfinalproject.Database.MessageDatabase;
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
import org.to.telegramfinalproject.Database.userDatabase;
import org.to.telegramfinalproject.Models.ContactEntry;
import org.to.telegramfinalproject.Models.Message;
import org.to.telegramfinalproject.Models.ResponseModel;
import org.to.telegramfinalproject.Models.User;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class SidebarService {
private static userDatabase userDB = new userDatabase();
// Returns current user's profile data (name, bio, status, etc.)
public static JSONObject getUserProfile(UUID userUUID) {
User user = userDB.findByInternalUUID(userUUID);
if (user == null) {
return null;
}
JSONObject profile = new JSONObject();
profile.put("user_id", user.getUser_id());
profile.put("profile_name", user.getProfile_name());
profile.put("bio", user.getBio() != null ? user.getBio() : "");
profile.put("status", "ONLINE");
profile.put("profile_picture_url", user.getImage_url());
return profile;
}
// Changes the user's profile picture
public static ResponseModel updateProfilePicture(UUID userUUID, String newImageUrl) {
User user = userDB.findByInternalUUID(userUUID);
if (user == null) {
return new ResponseModel("error", "User not found.");
}
String currentUrl = user.getImage_url();
if (Objects.equals(currentUrl, newImageUrl)) {
return new ResponseModel("error", "No changes made. Profile picture is the same.");
}
user.setImage_url(newImageUrl); // Can be null
boolean success = userDB.updateByUUID(userUUID, user);
if (success) {
return new ResponseModel("success", "Profile picture updated successfully.");
} else {
user.setImage_url(currentUrl);
return new ResponseModel("error", "Failed to update profile picture.");
}
}
// Changes user's bio
public static ResponseModel updateBio(UUID userUUID, String newBio) {
if (newBio.length() > 70) {
return new ResponseModel("error", "Bio is too long.");
}
User user = userDB.findByInternalUUID(userUUID);
if (user == null) {
return new ResponseModel("error", "User not found.");
}
String currentBio = user.getBio();
if (Objects.equals(currentBio, newBio)) {
return new ResponseModel("error", "No changes made. Bio is the same.");
}
user.setBio(newBio);
boolean success = userDB.updateByUUID(userUUID, user);
if (success) {
return new ResponseModel("success", "Bio updated successfully.");
} else {
user.setBio(currentBio);
return new ResponseModel("error", "Failed to update bio.");
}
}
// Changes user-id
public static ResponseModel updateUserId(UUID userUUID, String newUserId) {
if (newUserId == null || newUserId.trim().isEmpty()) {
return new ResponseModel("error", "User ID cannot be empty.");
}
newUserId = newUserId.trim();
if (newUserId.contains(" ")) {
return new ResponseModel("error", "User ID cannot contain spaces.");
}
if (!newUserId.matches("^[a-zA-Z0-9_]+$")) {
return new ResponseModel("error", "User ID can only contain letters, digits, and underscores.");
}
if (userDB.findByUserId(newUserId) != null) {
return new ResponseModel("error", "This user ID is already taken.");
}
User user = userDB.findByInternalUUID(userUUID);
if (user == null) {
return new ResponseModel("error", "User not found.");
}
String currentUserId = user.getUser_id();
if (Objects.equals(currentUserId, newUserId)) {
return new ResponseModel("error", "No changes made. User ID is the same.");
}
user.setUser_id(newUserId);
boolean saved = userDB.updateByUUID(userUUID, user);
if (saved) {
return new ResponseModel("success", "User ID updated successfully.");
} else {
user.setUser_id(currentUserId);
return new ResponseModel("error", "Failed to update user ID due to server error.");
}
}
// Changes user's profile name
public static ResponseModel updateProfileName(UUID userUUID, String newProfileName) {
if (newProfileName == null || newProfileName.trim().isEmpty()) {
return new ResponseModel("error", "Invalid input."); // Invalid input (empty or just spaces)
}
// Fetch the user from database
User user = userDB.findByInternalUUID(userUUID);
if (user == null) {
return new ResponseModel("error", "User not found."); // User doesn't exist
}
String oldName = user.getProfile_name();
if (Objects.equals(oldName, newProfileName)) {
return new ResponseModel("error", "No changes made. Profile name is the same.");
}
// Only set the profile name *after* successful DB update
user.setProfile_name(newProfileName);
boolean saved = userDB.updateByUUID(userUUID, user);
if (saved) {
return new ResponseModel("success", "Profile name updated successfully.");
} else {
user.setProfile_name(oldName);
return new ResponseModel("error", "Failed to update profile name.");
}
}
// Search in user's contact list
public static ResponseModel handleSearchContacts(String userUUID, String searchTerm) {
if (searchTerm == null || searchTerm.trim().isEmpty()) {
return new ResponseModel("error", "Search term cannot be empty.");
}
List<ContactEntry> searchResult = ContactDatabase.searchContacts(UUID.fromString(userUUID), searchTerm);
if (searchResult.isEmpty()) {
return new ResponseModel("error", "No contacts found.");
}
JSONObject data = new JSONObject();
JSONArray contacts = new JSONArray();
for (ContactEntry entry : searchResult) {
contacts.put(new JSONObject()
.put("contact_id", entry.getContactId())
.put("user_id", entry.getUserId())
.put("contact_display_id", entry.getContact_displayId())
.put("profile_name", entry.getProfileName())
.put("image_url", entry.getImageUrl())
.put("is_blocked", entry.isBlocked())
.put("last_seen", entry.getLastSeenTime())
);
}
data.put("contacts", contacts);
if (searchResult.isEmpty()) {
return new ResponseModel("error", "No contacts found.");
}
return new ResponseModel("success", "Search contacts successfully.", data);
}
// Remove a contact in user's contact list
public static ResponseModel handleRemoveContact(UUID userUUID, UUID contactId) {
if (userUUID == null || contactId == null) {
return new ResponseModel("error", "Invalid input.");
}
boolean removed = ContactDatabase.removeContact(userUUID, contactId);
if (removed) {
return new ResponseModel("success", "Contact removed successfully.");
} else {
return new ResponseModel("error", "Contact not found.");
}
}
// 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<Message> 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.");
}
}
// 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;
}
}
@@ -0,0 +1,31 @@
package org.to.telegramfinalproject.UI;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
public class SidebarMenuController {
@FXML private ImageView profileImage;
@FXML private Label usernameLabel;
@FXML private Button myProfileButton;
@FXML private Button newGroupButton;
@FXML private Button newChannelButton;
@FXML private Button contactsButton;
@FXML private Button savedMessagesButton;
@FXML private Button settingsButton;
@FXML private Button telegramFeaturesButton;
@FXML private Button telegramQnAButton;
@FXML private ToggleButton nightModeToggle;
@FXML
public void initialize() {
Image image = new Image(getClass().getResource("/org/to/telegramfinalproject/Images/profile.png").toExternalForm());
profileImage.setImage(image);
// Called automatically after FXML is loaded
usernameLabel.setText("Asal");
}
}
@@ -12,8 +12,11 @@ import java.io.IOException;
public class TelegramApplication extends Application { public class TelegramApplication extends Application {
@Override @Override
public void start(Stage stage) throws IOException { public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Intro.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 1480, 820); Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/sidebar_menu.css").toExternalForm());
stage.setTitle("Telegram"); stage.setTitle("Telegram");
stage.setScene(scene); stage.setScene(scene);
stage.show(); stage.show();
+1 -1
View File
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS messages (
content TEXT, content TEXT,
message_type VARCHAR(20) DEFAULT 'TEXT' CHECK (message_type IN ('TEXT','IMAGE','FILE','VIDEO','AUDIO','STICKER','GIF')), message_type VARCHAR(20) DEFAULT 'TEXT' CHECK (message_type IN ('TEXT','IMAGE','FILE','VIDEO','AUDIO','STICKER','GIF')),
send_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, send_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status SET DEFAULT 'SEND', -- SEND,DELIVERED,READ status VARCHAR(20) DEFAULT 'SEND', -- SEND,DELIVERED,READ
reply_to_id UUID REFERENCES messages(message_id) ON DELETE SET NULL, --(Bouns) reply_to_id UUID REFERENCES messages(message_id) ON DELETE SET NULL, --(Bouns)
is_edited BOOLEAN DEFAULT FALSE, --(Bonus) is_edited BOOLEAN DEFAULT FALSE, --(Bonus)
edited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, edited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -0,0 +1,14 @@
.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;
-fx-text-fill: white;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Separator?>
<VBox fx:id="sidebar" spacing="20" alignment="TOP_LEFT"
prefWidth="250.0"
style="-fx-background-color: #1b2735;"
xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.to.telegramfinalproject.UI.SidebarMenuController">
<padding>
<Insets top="20" left="10" right="10" bottom="10"/>
</padding>
<!-- Profile Section -->
<VBox alignment="TOP_LEFT" spacing="10">
<padding>
<Insets top="10" left="10" bottom="0" right="0"/>
</padding>
<ImageView fx:id="profileImage" fitWidth="80" fitHeight="80" pickOnBounds="true" preserveRatio="true"/>
<Label fx:id="usernameLabel" text="Asal Lotfi" textFill="white" style="-fx-font-size: 14px; -fx-font-weight: bold;"/>
</VBox>
<Separator prefWidth="200"/>
<!-- Menu Buttons -->
<VBox spacing="8" alignment="TOP_LEFT">
<Button fx:id="btnMyProfile" text=" My Profile" styleClass="sidebar-btn"/>
<Button fx:id="btnNewGroup" text=" New Group" styleClass="sidebar-btn"/>
<Button fx:id="btnNewChannel" text=" New Channel" styleClass="sidebar-btn"/>
<Button fx:id="btnContacts" text=" Contacts" styleClass="sidebar-btn"/>
<Button fx:id="btnCalls" text=" Calls" styleClass="sidebar-btn"/>
<Button fx:id="btnSavedMessages" text=" Saved Messages" styleClass="sidebar-btn"/>
<Button fx:id="btnSettings" text=" Settings" styleClass="sidebar-btn"/>
<!-- Night Mode Toggle -->
<HBox spacing="10" alignment="CENTER_LEFT">
<Label text="Night Mode" textFill="white" style="-fx-font-size: 13px;"/>
<ToggleButton fx:id="nightModeToggle"/>
</HBox>
</VBox>
</VBox>