Merge branch 'develop' into SendMessage
# Conflicts: # src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java # src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java
@@ -148,7 +148,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);
|
||||
|
||||
@@ -336,6 +336,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;
|
||||
}
|
||||
@@ -482,79 +483,95 @@ 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<ChatEntry> 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<ChatEntry> 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<ChatEntry> 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<ChatEntry> 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<ChatEntry> 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<ChatEntry> 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")));
|
||||
for (Object obj : Active) {
|
||||
activeChats.add(parseChatEntry((JSONObject) obj));
|
||||
}
|
||||
|
||||
activeChats.add(entry);
|
||||
|
||||
}
|
||||
|
||||
Session.contactEntries.clear();
|
||||
for (Object obj : contactList) {
|
||||
@@ -832,6 +849,8 @@ public class ActionHandler {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void userMenu(UUID internal_uuid) throws IOException {
|
||||
while (true) {
|
||||
|
||||
@@ -1133,85 +1152,157 @@ 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
|
||||
// مپ شمارهٔ نمایش → ایندکس واقعی در activeChats
|
||||
Map<Integer, Integer> displayToActive = new HashMap<>();
|
||||
|
||||
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++) {
|
||||
ChatEntry entry = Session.activeChats.get(i);
|
||||
if (entry.isSavedMessages()) {
|
||||
continue; // Already printed above
|
||||
}
|
||||
if (savedIdxInActive != null && i == savedIdxInActive) continue;
|
||||
|
||||
String time = (entry.getLastMessageTime() == null)
|
||||
? "No messages yet"
|
||||
: entry.getLastMessageTime().toString();
|
||||
System.out.println(index + ". [" + entry.getType() + "] " +
|
||||
entry.getName() + " - Last: " + time);
|
||||
index++;
|
||||
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;
|
||||
ChatEntry selected = Session.activeChats.get(activeIdx);
|
||||
openChat(selected); // برای Saved هم همین مسیر اجرا میشود
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void showArchivedChats() {
|
||||
if (Session.archivedChats == null || Session.archivedChats.isEmpty()) {
|
||||
@@ -1373,6 +1464,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) {
|
||||
|
||||
@@ -1382,41 +1583,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;
|
||||
}
|
||||
chat.setOtherUserId(UUID.fromString(resTarget.getJSONObject("data").getString("target_id")));
|
||||
}
|
||||
|
||||
String otherUserId = resTarget.getJSONObject("data").getString("target_id");
|
||||
chat.setOtherUserId(UUID.fromString(otherUserId));
|
||||
UUID me = UUID.fromString(Session.currentUser.getString("internal_uuid"));
|
||||
boolean isSaved = chat.isSavedMessages() || me.equals(chat.getOtherUserId());
|
||||
|
||||
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "view_profile");
|
||||
req.put("target_id", chat.getOtherUserId()); // internal UUID
|
||||
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.getString("status").equals("success")) {
|
||||
if (res != null && "success".equals(res.optString("status"))) {
|
||||
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("\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");
|
||||
@@ -1427,57 +1644,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" -> sendMessageInteractive(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 "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;
|
||||
@@ -3447,7 +3642,8 @@ public class ActionHandler {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 🔹 فقط ارسال پیام با chat_id و receiver_type
|
||||
//
|
||||
//
|
||||
// JSONObject messageJson = new JSONObject();
|
||||
// messageJson.put("action", "send_message");
|
||||
// messageJson.put("receiver_type", receiverType);
|
||||
@@ -3568,9 +3764,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);
|
||||
|
||||
@@ -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();
|
||||
@@ -247,212 +255,312 @@ 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"));
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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;
|
||||
}
|
||||
|
||||
UUID originalMessageId = null;
|
||||
String originalMessageIdStr = msgJson.optString("original_message_id", null);
|
||||
if (originalMessageIdStr != null && !originalMessageIdStr.equals("null")) {
|
||||
originalMessageId = UUID.fromString(originalMessageIdStr);
|
||||
JSONObject data = res.optJSONObject("data");
|
||||
if (data == null) {
|
||||
System.out.println("❌ Invalid server response for Saved Messages.");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID forwardedBy = null;
|
||||
String forwardedByStr = msgJson.optString("forwarded_by", null);
|
||||
if (forwardedByStr != null && !forwardedByStr.equals("null")) {
|
||||
forwardedBy = UUID.fromString(forwardedByStr);
|
||||
}
|
||||
boolean created = data.optBoolean("created", true); // if server sends it
|
||||
String chatId = data.optString("chat_id", null);
|
||||
|
||||
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.");
|
||||
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 {
|
||||
for (Message msg : messages) {
|
||||
System.out.println("[" + msg.getSend_at() + "] " + msg.getContent());
|
||||
System.out.println("ℹ️ Saved Messages already exists." + (chatId != null ? " chat_id=" + chatId : ""));
|
||||
}
|
||||
}
|
||||
|
||||
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...");
|
||||
while (true) {
|
||||
System.out.println("⚙️ Settings\n");
|
||||
|
||||
String imageUrl = Session.currentUser.optString("image_url", "—");
|
||||
String profileName = Session.currentUser.optString("profile_name", "—");
|
||||
String userId = Session.currentUser.optString("user_id", "—");
|
||||
|
||||
renderUserCard(imageUrl, profileName, userId);
|
||||
System.out.println();
|
||||
renderMenu();
|
||||
|
||||
System.out.print("\nChoose an option (0-4): ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1": openUserProfile(); break;
|
||||
case "2": showPrivacySettings(); break;
|
||||
case "3": showTelegramQA(); break;
|
||||
case "4": showTelegramFeatures(); break;
|
||||
case "0": return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter to continue...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void showPrivacySettings() {
|
||||
while (true) {
|
||||
System.out.println("🔒 Privacy\n");
|
||||
System.out.println("1) Blocked users");
|
||||
System.out.println("2) Change username / password");
|
||||
System.out.println("0) Back");
|
||||
System.out.print("\nChoose: ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1": viewBlockedUsers(); break;
|
||||
case "2": changeCredentialsFlow(); break;
|
||||
case "0": return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void viewBlockedUsers() {
|
||||
System.out.println("🚫 Blocked Users\n");
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "get_blocked_users");
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res == null || !res.optString("status","error").equals("success")) {
|
||||
System.out.println("❌ Failed to fetch blocked users. Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
org.json.JSONArray arr = res.getJSONObject("data").optJSONArray("blocked_users");
|
||||
if (arr == null || arr.isEmpty()) {
|
||||
System.out.println("📭 No blocked users.");
|
||||
System.out.println("\nPress Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
org.json.JSONObject u = arr.getJSONObject(i);
|
||||
String profileName = u.optString("profile_name", "—");
|
||||
String userId = u.optString("user_id", "—");
|
||||
System.out.printf("%d) %s (@%s)\n", i + 1, profileName, userId);
|
||||
}
|
||||
System.out.println("\n0) Back");
|
||||
System.out.print("\nChoose a user to UNBLOCK (number): ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
if (pick.equals("0")) return;
|
||||
int idx;
|
||||
try { idx = Integer.parseInt(pick) - 1; } catch (Exception e) { idx = -1; }
|
||||
if (idx < 0 || idx >= arr.length()) {
|
||||
System.out.println("❌ Invalid index. Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
org.json.JSONObject target = arr.getJSONObject(idx);
|
||||
String targetDisplayId = target.optString("user_id", "");
|
||||
java.util.UUID targetInternalId = java.util.UUID.fromString(target.getString("internal_uuid"));
|
||||
|
||||
System.out.printf("Unblock %s (@%s)? (yes/no): ", target.optString("profile_name","—"), targetDisplayId);
|
||||
if (!scanner.nextLine().trim().equalsIgnoreCase("yes")) return;
|
||||
|
||||
org.json.JSONObject unReq = new org.json.JSONObject();
|
||||
unReq.put("action", "toggle_block");
|
||||
unReq.put("user_id",Session.getUserUUID());
|
||||
unReq.put("target_id", targetInternalId.toString());
|
||||
|
||||
org.json.JSONObject unRes = sendWithResponse(unReq);
|
||||
if (unRes != null && unRes.optString("status","error").equals("success")) {
|
||||
System.out.println("✅ User unblocked.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to unblock.");
|
||||
}
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
|
||||
|
||||
private void changeCredentialsFlow() {
|
||||
System.out.println("🛡️ Change Username / Password\n");
|
||||
|
||||
System.out.print("Enter current password: ");
|
||||
String currentPassword = scanner.nextLine();
|
||||
|
||||
org.json.JSONObject verReq = new org.json.JSONObject();
|
||||
verReq.put("action", "verify_password");
|
||||
verReq.put("current_password", currentPassword);
|
||||
|
||||
org.json.JSONObject verRes = sendWithResponse(verReq);
|
||||
if (verRes == null || !verRes.optString("status","error").equals("success")) {
|
||||
System.out.println("❌ Current password is incorrect.");
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
return;
|
||||
}
|
||||
|
||||
String currentUsername = Session.currentUser.optString("username", "—");
|
||||
System.out.println("\n✅ Verified.");
|
||||
System.out.println("Current username: " + currentUsername);
|
||||
System.out.println("Current password: ******** (hidden)");
|
||||
System.out.println("\nWhat do you want to change?");
|
||||
System.out.println("1) Username");
|
||||
System.out.println("2) Password");
|
||||
System.out.println("3) Both");
|
||||
System.out.println("0) Back");
|
||||
System.out.print("\nChoose: ");
|
||||
String pick = scanner.nextLine().trim();
|
||||
|
||||
switch (pick) {
|
||||
case "1":
|
||||
changeUsername(currentPassword);
|
||||
break;
|
||||
case "2":
|
||||
changePassword(currentPassword);
|
||||
break;
|
||||
case "3":
|
||||
boolean uOk = changeUsername(currentPassword);
|
||||
boolean pOk = changePassword(currentPassword);
|
||||
if (uOk && pOk) System.out.println("✅ Username and password updated.");
|
||||
System.out.println("Press Enter...");
|
||||
scanner.nextLine();
|
||||
break;
|
||||
case "0":
|
||||
return;
|
||||
default:
|
||||
System.out.println("❌ Invalid choice. Press Enter...");
|
||||
scanner.nextLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean changeUsername(String currentPassword) {
|
||||
System.out.print("\nNew username: ");
|
||||
String newUsername = scanner.nextLine().trim();
|
||||
|
||||
if (!isValidUsername(newUsername)) {
|
||||
System.out.println("❌ Invalid username. Use 4–32 chars: letters, digits, underscore.");
|
||||
return false;
|
||||
}
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "update_username");
|
||||
req.put("current_password", currentPassword);
|
||||
req.put("new_username", newUsername);
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.optString("status","error").equals("success")) {
|
||||
Session.currentUser.put("username", newUsername);
|
||||
System.out.println("✅ Username updated.");
|
||||
return true;
|
||||
} else {
|
||||
String msg = (res == null) ? "No response." : res.optString("message","Update failed.");
|
||||
System.out.println("❌ " + msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidUsername(String s) {
|
||||
//4-32 char
|
||||
return s != null && s.matches("^[A-Za-z0-9_]{4,32}$");
|
||||
}
|
||||
|
||||
private boolean changePassword(String currentPassword) {
|
||||
System.out.print("\nNew password: ");
|
||||
String newPassword = scanner.nextLine();
|
||||
System.out.print("Repeat new password: ");
|
||||
String repeat = scanner.nextLine();
|
||||
|
||||
if (!newPassword.equals(repeat)) {
|
||||
System.out.println("❌ Passwords do not match.");
|
||||
return false;
|
||||
}
|
||||
if (!isStrongPassword(newPassword)) {
|
||||
System.out.println("❌ Weak password. Min 8 chars, include letters and digits.");
|
||||
return false;
|
||||
}
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "update_password");
|
||||
req.put("current_password", currentPassword);
|
||||
req.put("new_password", newPassword);
|
||||
|
||||
org.json.JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.optString("status","error").equals("success")) {
|
||||
System.out.println("✅ Password updated.");
|
||||
return true;
|
||||
} else {
|
||||
String msg = (res == null) ? "No response." : res.optString("message","Update failed.");
|
||||
System.out.println("❌ " + msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void renderUserCard(String imageUrl, String profileName, String userId) {
|
||||
int w = 60;
|
||||
String top = "┌" + "─".repeat(w - 2) + "┐";
|
||||
String bot = "└" + "─".repeat(w - 2) + "┘";
|
||||
System.out.println(top);
|
||||
System.out.println(padBoxLine("Profile", w));
|
||||
System.out.println("├" + "─".repeat(w - 2) + "┤");
|
||||
System.out.println(padBoxLine("Image URL: " + imageUrl, w));
|
||||
System.out.println(padBoxLine("Profile Name: "+ profileName, w));
|
||||
System.out.println(padBoxLine("User ID: " + userId, w));
|
||||
System.out.println(bot);
|
||||
}
|
||||
|
||||
private String padBoxLine(String text, int width) {
|
||||
final int inner = width - 2;
|
||||
if (text.length() > inner) {
|
||||
text = text.substring(0, inner - 1) + "…";
|
||||
}
|
||||
int spaces = inner - text.length();
|
||||
return "│" + text + " ".repeat(Math.max(0, spaces)) + "│";
|
||||
}
|
||||
|
||||
private void renderMenu() {
|
||||
System.out.println("1) My Account");
|
||||
System.out.println("2) Privacy");
|
||||
System.out.println("3) Telegram Q&A");
|
||||
System.out.println("4) Telegram Features");
|
||||
System.out.println("0) Back");
|
||||
}
|
||||
|
||||
private void showTelegramFeatures() {
|
||||
@@ -462,4 +570,11 @@ public class SidebarHandler {
|
||||
private void showTelegramQA() {
|
||||
System.out.println("❓ Showing Q&A...");
|
||||
}
|
||||
|
||||
private boolean isStrongPassword(String s) {
|
||||
boolean approved = s.matches("\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b");
|
||||
return approved ;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<JSONObject> getBlockedUsers(UUID userId) {
|
||||
String sql = """
|
||||
SELECT u.internal_uuid, u.user_id, u.profile_name
|
||||
FROM contacts c
|
||||
JOIN users u ON u.internal_uuid = c.contact_id
|
||||
WHERE c.user_id = ? AND c.is_blocked = TRUE
|
||||
ORDER BY u.profile_name NULLS LAST, u.user_id
|
||||
""";
|
||||
List<JSONObject> out = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("internal_uuid", rs.getObject("internal_uuid").toString());
|
||||
j.put("user_id", rs.getString("user_id"));
|
||||
j.put("profile_name", rs.getString("profile_name"));
|
||||
out.add(j);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean unblockContact(UUID user_id, UUID contact_id) {
|
||||
String sql = "UPDATE contacts SET is_blocked = FALSE WHERE user_id = ? AND contact_id = ?";
|
||||
|
||||
@@ -307,14 +307,87 @@ public class PrivateChatDatabase {
|
||||
return null;
|
||||
}
|
||||
|
||||
// public static UUID getOtherParticipant(UUID chatId, UUID me) {
|
||||
// List<UUID> members = getMembers(chatId);
|
||||
// for (UUID u : members) {
|
||||
// if (!u.equals(me)) return u;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
public static UUID getOtherParticipant(UUID chatId, UUID me) {
|
||||
List<UUID> members = getMembers(chatId);
|
||||
List<UUID> members = getMembers(chatId); // باید [user1_id, user2_id] بده
|
||||
if (members == null || members.isEmpty()) return null;
|
||||
|
||||
boolean isMember = false;
|
||||
UUID other = null;
|
||||
|
||||
for (UUID u : members) {
|
||||
if (!u.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")
|
||||
);
|
||||
}
|
||||
public static boolean isParticipant(java.util.UUID chatId, java.util.UUID userId) {
|
||||
String sql = """
|
||||
SELECT 1
|
||||
@@ -337,5 +410,4 @@ public class PrivateChatDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -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;}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -221,42 +222,87 @@ public class ClientHandler implements Runnable {
|
||||
// }
|
||||
|
||||
|
||||
//
|
||||
// List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
|
||||
// for (PrivateChat chat : privateChats) {
|
||||
// UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ?
|
||||
// chat.getUser2_id() : chat.getUser1_id();
|
||||
//
|
||||
// User otherUser = userDatabase.findByInternalUUID(otherId);
|
||||
// if (otherUser == null) continue;
|
||||
//
|
||||
// LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private");
|
||||
//
|
||||
//
|
||||
// ChatEntry entry = new ChatEntry(
|
||||
// chat.getChat_id(),
|
||||
// otherUser.getUser_id(),
|
||||
// otherUser.getProfile_name(),
|
||||
// otherUser.getImage_url(),
|
||||
// "private",
|
||||
// lastMessageTime,
|
||||
// false,
|
||||
// false
|
||||
// );
|
||||
// entry.setOtherUserId(otherId);
|
||||
//
|
||||
// if (currentUser.getInternal_uuid() == otherId) {
|
||||
// entry.setSavedMessages(true);
|
||||
// }
|
||||
//
|
||||
// if (archivedChatIds.contains(chat.getChat_id())) {
|
||||
// archivedChatList.add(entry);
|
||||
// chatList.add(entry);
|
||||
// } else {
|
||||
// activeChatList.add(entry);
|
||||
// chatList.add(entry);
|
||||
// }
|
||||
// }
|
||||
|
||||
List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
|
||||
|
||||
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());
|
||||
|
||||
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(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2136,6 +2182,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 {
|
||||
@@ -2146,6 +2201,8 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
case "get_contact_list": {
|
||||
if (currentUser == null) {
|
||||
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||
@@ -2155,27 +2212,30 @@ public class ClientHandler implements Runnable {
|
||||
List<Contact> contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid());
|
||||
List<ContactEntry> 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;
|
||||
@@ -2584,16 +2644,16 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_saved_messages": {
|
||||
UUID user_Id = UUID.fromString(requestJson.getString("user_id"));
|
||||
// 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;
|
||||
}
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// case "send_saved_messages": {
|
||||
// response = SidebarService.handleSendMessage(requestJson);
|
||||
// break;
|
||||
// }
|
||||
|
||||
case "search_contacts": {
|
||||
String user_id = requestJson.getString("user_id");
|
||||
@@ -2611,6 +2671,66 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -3227,6 +3347,7 @@ public class ClientHandler implements Runnable {
|
||||
|
||||
|
||||
|
||||
|
||||
String content = json.optString("content", "");
|
||||
String messageType = json.optString("message_type", "TEXT");
|
||||
|
||||
@@ -3425,5 +3546,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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -211,7 +211,6 @@ public class SidebarService {
|
||||
}
|
||||
}
|
||||
|
||||
// Get saved messages data
|
||||
// public static ResponseModel handleGetSavedMessages(UUID userId) {
|
||||
// try {
|
||||
//
|
||||
@@ -220,7 +219,7 @@ public class SidebarService {
|
||||
// return new ResponseModel("error", "Failed to create or find saved messages chat.");
|
||||
// }
|
||||
//
|
||||
// List<Message> messages = MessageDatabase.privateChatHistory(chatId);
|
||||
// List<Message> messages = MessageDatabase.privateChatHistory(chatId, userId);
|
||||
//
|
||||
// JSONArray messageArray = new JSONArray();
|
||||
// if (!messages.isEmpty()) {
|
||||
@@ -257,41 +256,36 @@ public class SidebarService {
|
||||
// }
|
||||
|
||||
// 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.");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
@@ -10,12 +9,10 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -125,7 +122,7 @@ public class IntroController {
|
||||
@FXML
|
||||
private void handleStartMessaging(ActionEvent event) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/login_view.fxml"));
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/login_view.fxml"));
|
||||
Scene loginScene = new Scene(loader.load());
|
||||
|
||||
// Get the current stage and its dimensions
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
|
||||
public class MyProfileController {
|
||||
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label displayName;
|
||||
@FXML private Label status;
|
||||
@FXML private Label bio;
|
||||
@FXML private Label userId;
|
||||
|
||||
|
||||
// public void initialize() {
|
||||
// // Load data from your logged-in user object
|
||||
// // Example:
|
||||
// displayName.setText(CurrentUser.getName());
|
||||
// status.setText(CurrentUser.isOnline() ? "online" : "offline");
|
||||
// bio.setText(CurrentUser.getBio());
|
||||
// userId.setText(CurrentUser.getUserId());
|
||||
//
|
||||
// if (CurrentUser.getProfileImagePath() != null) {
|
||||
// profileImage.setImage(new Image(CurrentUser.getProfileImagePath()));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -1,11 +1,25 @@
|
||||
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;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class SidebarMenuController {
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
@FXML private VBox sidebarRoot;
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label usernameLabel;
|
||||
|
||||
@@ -18,14 +32,149 @@ 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;
|
||||
|
||||
// convenience handle
|
||||
private final ThemeManager themeManager = ThemeManager.getInstance();
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
Image image = new Image(getClass().getResource("/org/to/telegramfinalproject/Images/profile.png").toExternalForm());
|
||||
profileImage.setImage(image);
|
||||
// Load default profile image (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");
|
||||
setupButtonActions();
|
||||
setupToggleAction();
|
||||
|
||||
// When the Scene is ready:
|
||||
sidebarRoot.sceneProperty().addListener((obs, oldScene, newScene) -> {
|
||||
if (newScene != null) {
|
||||
// 1) Register with ThemeManager so this scene auto-updates on theme changes
|
||||
themeManager.registerScene(newScene);
|
||||
|
||||
// 2) Make sure we start in LIGHT mode (if that's what you want)
|
||||
// (If you already set the initial mode elsewhere, remove the next line)
|
||||
themeManager.setDarkMode(false);
|
||||
|
||||
// 3) Sync icons & toggle with current mode
|
||||
boolean dark = themeManager.isDarkMode();
|
||||
updateIcons(dark);
|
||||
syncToggleVisual(dark, /*animate=*/true);
|
||||
|
||||
// Ensure the thumb is correctly positioned after layout pass
|
||||
Platform.runLater(() -> syncToggleVisual(themeManager.isDarkMode(), false));
|
||||
}
|
||||
});
|
||||
|
||||
// If theme is changed from somewhere else (another screen), keep sidebar in sync
|
||||
themeManager.darkModeProperty().addListener((o, wasDark, isDark) -> {
|
||||
updateIcons(isDark);
|
||||
syncToggleVisual(isDark, /*animate=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupButtonActions() {
|
||||
myProfileButton.setOnAction(e -> openMyProfile());
|
||||
newGroupButton.setOnAction(e -> createNewGroup());
|
||||
newChannelButton.setOnAction(e -> createNewChannel());
|
||||
contactsButton.setOnAction(e -> openContacts());
|
||||
savedMessagesButton.setOnAction(e -> openSavedMessages());
|
||||
settingsButton.setOnAction(e -> openSettings());
|
||||
telegramFeaturesButton.setOnAction(e -> openTelegramFeatures());
|
||||
telegramQnAButton.setOnAction(e -> openTelegramQnA());
|
||||
}
|
||||
|
||||
private void setupToggleAction() {
|
||||
nightModeToggle.setOnMouseClicked(e -> {
|
||||
boolean newDark = !themeManager.isDarkMode();
|
||||
|
||||
// Update the global theme via ThemeManager
|
||||
themeManager.setDarkMode(newDark);
|
||||
|
||||
// Animate the thumb to its new position (listener will handle icons & final position sync)
|
||||
syncToggleVisual(newDark, /*animate=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
/** Update all button icons to match theme.
|
||||
* darkMode == true => white icons => use *_light.png
|
||||
* darkMode == false => dark icons => use *_dark.png
|
||||
*/
|
||||
private void updateIcons(boolean darkMode) {
|
||||
String suffix = darkMode ? "_light.png" : "_dark.png";
|
||||
myProfileButton.setGraphic(makeIcon(ICON_PATH + "my_profile" + suffix));
|
||||
newGroupButton.setGraphic(makeIcon(ICON_PATH + "new_group" + suffix));
|
||||
newChannelButton.setGraphic(makeIcon(ICON_PATH + "new_channel" + suffix));
|
||||
contactsButton.setGraphic(makeIcon(ICON_PATH + "contacts" + suffix));
|
||||
savedMessagesButton.setGraphic(makeIcon(ICON_PATH + "saved_messages" + suffix));
|
||||
settingsButton.setGraphic(makeIcon(ICON_PATH + "settings" + suffix));
|
||||
telegramFeaturesButton.setGraphic(makeIcon(ICON_PATH + "telegram_features" + suffix));
|
||||
telegramQnAButton.setGraphic(makeIcon(ICON_PATH + "telegram_qna" + suffix));
|
||||
|
||||
// Night-mode moon icon
|
||||
Image moon = loadImage(ICON_PATH + "night_mode" + suffix);
|
||||
if (moon != null) nightModeIcon.setImage(moon);
|
||||
}
|
||||
|
||||
/** Keep the toggle’s CSS class and thumb position in sync with the current mode. */
|
||||
private void syncToggleVisual(boolean darkMode, boolean animate) {
|
||||
// CSS class "on" on the track
|
||||
if (darkMode) {
|
||||
if (!nightModeToggle.getStyleClass().contains("on")) {
|
||||
nightModeToggle.getStyleClass().add("on");
|
||||
}
|
||||
} else {
|
||||
nightModeToggle.getStyleClass().remove("on");
|
||||
}
|
||||
|
||||
// Compute target X for the thumb
|
||||
double offX = 2;
|
||||
double onX = Math.max(2, nightModeToggle.getWidth() - toggleThumb.getWidth() - 4);
|
||||
double targetX = darkMode ? onX : offX;
|
||||
|
||||
if (animate) {
|
||||
TranslateTransition tt = new TranslateTransition(Duration.millis(200), toggleThumb);
|
||||
tt.setToX(targetX);
|
||||
tt.play();
|
||||
} else {
|
||||
toggleThumb.setTranslateX(targetX);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- helpers -------------------------------------------------------------
|
||||
|
||||
private Image loadImage(String path) {
|
||||
URL res = getClass().getResource(path);
|
||||
if (res == null) {
|
||||
System.err.println("Resource not found: " + path);
|
||||
return null;
|
||||
}
|
||||
return new Image(res.toExternalForm());
|
||||
}
|
||||
|
||||
private ImageView makeIcon(String path) {
|
||||
ImageView iv = new ImageView();
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
iv.setImage(img);
|
||||
iv.setFitWidth(22);
|
||||
iv.setFitHeight(22);
|
||||
iv.setPreserveRatio(true);
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
// 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..."); }
|
||||
}
|
||||
|
||||
@@ -5,17 +5,16 @@ 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;
|
||||
|
||||
public class TelegramApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml"));
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/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);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ThemeManager {
|
||||
private static final ThemeManager instance = new ThemeManager();
|
||||
|
||||
private final BooleanProperty darkMode = new SimpleBooleanProperty(false);
|
||||
private final List<Scene> registeredScenes = new ArrayList<>();
|
||||
|
||||
private ThemeManager() {
|
||||
// Whenever darkMode changes, update all registered scenes
|
||||
darkMode.addListener((obs, oldVal, newVal) -> applyThemeToAll());
|
||||
}
|
||||
|
||||
public static ThemeManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public BooleanProperty darkModeProperty() {
|
||||
return darkMode;
|
||||
}
|
||||
|
||||
public boolean isDarkMode() {
|
||||
return darkMode.get();
|
||||
}
|
||||
|
||||
public void setDarkMode(boolean dark) {
|
||||
darkMode.set(dark);
|
||||
}
|
||||
|
||||
public void registerScene(Scene scene) {
|
||||
if (!registeredScenes.contains(scene)) {
|
||||
registeredScenes.add(scene);
|
||||
applyTheme(scene); // Apply current theme immediately
|
||||
}
|
||||
}
|
||||
|
||||
private void applyThemeToAll() {
|
||||
for (Scene scene : registeredScenes) {
|
||||
applyTheme(scene);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTheme(Scene scene) {
|
||||
scene.getStylesheets().clear();
|
||||
if (isDarkMode()) {
|
||||
scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/dark_theme.css").toExternalForm());
|
||||
} else {
|
||||
scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,5 +156,14 @@ CREATE TABLE IF NOT EXISTS message_attachments (
|
||||
|
||||
);
|
||||
|
||||
--Run this part in your pg
|
||||
ALTER TABLE message_attachments
|
||||
ADD COLUMN IF NOT EXISTS media_key UUID;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
UPDATE message_attachments
|
||||
SET media_key = gen_random_uuid()
|
||||
WHERE media_key IS NULL;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.my-profile-root {
|
||||
-fx-background-color: white;
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
-fx-clip-radius: 40; /* Rounded image */
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
-fx-font-size: 18px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.profile-status {
|
||||
-fx-text-fill: green;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: #333;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
-fx-text-fill: #555;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="org.to.telegramfinalproject.UI.MyProfileController"
|
||||
prefWidth="400" prefHeight="600"
|
||||
styleClass="my-profile-root">
|
||||
|
||||
<VBox spacing="15" padding="20">
|
||||
|
||||
<!-- Top Bar -->
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<ImageView fx:id="profileImage" fitHeight="80" fitWidth="80"
|
||||
styleClass="profile-image"/>
|
||||
<VBox>
|
||||
<Label fx:id="displayName" text="Display Name" styleClass="profile-name"/>
|
||||
<Label fx:id="status" text="online" styleClass="profile-status"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<!-- Bio -->
|
||||
<HBox spacing="10" alignment="CENTER_LEFT">
|
||||
<Label text="Bio:" styleClass="field-label"/>
|
||||
<Label fx:id="bio" text="This is the bio..." wrapText="true" styleClass="field-value"/>
|
||||
</HBox>
|
||||
|
||||
<!-- User ID -->
|
||||
<HBox spacing="10" alignment="CENTER_LEFT">
|
||||
<Label text="User ID:" styleClass="field-label"/>
|
||||
<Label fx:id="userId" text="@user_id" styleClass="field-value"/>
|
||||
</HBox>
|
||||
|
||||
</VBox>
|
||||
</AnchorPane>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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="sidebarRoot" spacing="20" alignment="TOP_LEFT"
|
||||
prefWidth="250.0"
|
||||
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>
|
||||
|
||||
<!-- Menu Buttons -->
|
||||
<VBox spacing="8" alignment="TOP_LEFT">
|
||||
|
||||
<!-- Thin Telegram-style line -->
|
||||
<Separator orientation="HORIZONTAL" styleClass="thin-separator" />
|
||||
|
||||
<Button fx:id="myProfileButton" text=" My Profile" styleClass="sidebar-btn"/>
|
||||
|
||||
<!-- Thin Telegram-style line -->
|
||||
<Separator orientation="HORIZONTAL" styleClass="thin-separator" />
|
||||
|
||||
<Button fx:id="newGroupButton" text=" New Group" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="newChannelButton" text=" New Channel" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="contactsButton" text=" Contacts" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="savedMessagesButton" text=" Saved Messages" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="settingsButton" text=" Settings" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="telegramFeaturesButton" text=" Telegram Features" styleClass="sidebar-btn"/>
|
||||
<Button fx:id="telegramQnAButton" text=" Telegram Q&A" styleClass="sidebar-btn"/>
|
||||
|
||||
<!-- Night Mode Toggle -->
|
||||
<HBox spacing="10" alignment="CENTER_LEFT">
|
||||
<padding>
|
||||
<!-- Match button's left padding -->
|
||||
<Insets left="15"/>
|
||||
</padding>
|
||||
<ImageView fx:id="nightModeIcon" fitWidth="22" fitHeight="22" pickOnBounds="true" preserveRatio="true"/>
|
||||
<Label text="Night Mode" textFill="white" style="-fx-font-size: 13px;"/>
|
||||
<HBox fx:id="nightModeToggle" styleClass="telegram-toggle">
|
||||
<Region fx:id="toggleThumb" styleClass="toggle-thumb"/>
|
||||
</HBox>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</VBox>
|
||||
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 706 B |
|
After Width: | Height: | Size: 495 B |
|
After Width: | Height: | Size: 895 B |
|
After Width: | Height: | Size: 935 B |
|
After Width: | Height: | Size: 691 B |
|
After Width: | Height: | Size: 719 B |
|
After Width: | Height: | Size: 781 B |
|
After Width: | Height: | Size: 834 B |
|
After Width: | Height: | Size: 806 B |
|
After Width: | Height: | Size: 830 B |
|
After Width: | Height: | Size: 510 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 825 B |
|
After Width: | Height: | Size: 886 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,48 +0,0 @@
|
||||
<?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>
|
||||