diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index a483f1e..9175d22 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -446,7 +446,31 @@ public class ActionHandler { send(req); } + public void createGroupUI(String id, String name, String url, String user ){ + JSONObject req = new JSONObject(); + req.put("action", "create_group"); + req.put("user_id", user); + req.put("group_id", id); + req.put("group_name", name); + req.put("image_url", url.isBlank() ? JSONObject.NULL : url); + send(req); + + } + + + public void createChannelUI(String id, String name, String url , String user){ + + JSONObject req = new JSONObject(); + req.put("action", "create_channel"); + req.put("user_id", user); + req.put("channel_id", id); + req.put("channel_name", name); + req.put("image_url", url.isBlank() ? JSONObject.NULL : url); + + send(req); + + } public void createChannel() { String channelId = null; diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 62581a1..32eff13 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -143,7 +143,7 @@ import java.util.concurrent.LinkedBlockingQueue; public class TelegramClient { private static final String SERVER_HOST = "localhost"; - private static final int SERVER_PORT = 8000; + private static final int SERVER_PORT = 8080; private static TelegramClient instance; diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java index 45a0000..fdcc9fb 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatPageController.java @@ -1,6 +1,7 @@ package org.to.telegramfinalproject.UI; import javafx.application.Platform; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.geometry.Side; import javafx.scene.control.*; @@ -75,6 +76,25 @@ public class ChatPageController { @FXML private ImageView sendIcon; + // ===== For Searching System ===== + @FXML private VBox composerPane; + @FXML private VBox joinPane; + @FXML private VBox addContactPane; + + //For search handeling + @FXML private Button joinButton; + @FXML private Button addContactButton; + + //Handle View chat + @FXML private Button unblockBtn; + @FXML private VBox readOnlyPane; + @FXML private Label readOnlyLabel; + + + private ChatViewMode currentMode = ChatViewMode.NORMAL; + + + // ===== Time formatter for messages ===== private static final DateTimeFormatter FMT_HHMM = DateTimeFormatter.ofPattern("HH:mm"); private static final DateTimeFormatter FMT_DATE_TIME = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); @@ -533,39 +553,126 @@ public class ChatPageController { return new Image(url.toExternalForm()); } - public void showChat(ChatEntry entry) { +// public void showChat(ChatEntry entry) { +// this.currentChat = entry; +// +// chatTitle.setText(entry.getName()); +// +// // آواتار پیش‌فرض بر اساس نوع +// // ChatPageController.showChat(...) +// if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { +// Image img = AvatarLocalResolver.load(entry.getImageUrl()); +// if (img != null) { +// userAvatar.setImage(img); +// } else { +// // ⬇️ فال‌بک بر اساس نوع +// setDefaultHeaderAvatarByType(entry.getType()); +// } +// } else { +// setDefaultHeaderAvatarByType(entry.getType()); +// } +//// userAvatar.setClip(new Circle(20, 20, 20)); +// AvatarFX.circleClip(userAvatar, 36); +// +// +// +// fetchAndRenderHeader(entry); +// +// // پیام‌ها +// messageContainer.getChildren().clear(); +// loadMessages(entry); +// markAsRead(entry); +// +// Platform.runLater(() -> messageInput.requestFocus()); +// } + +// +// public void showChat(ChatEntry entry) { +// this.currentChat = entry; +// this.chatName = entry.getName(); // برای لاگ/منو +// +// chatTitle.setText(entry.getName()); +// if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { +// Image img = AvatarLocalResolver.load(entry.getImageUrl()); +// if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType()); +// } else { +// setDefaultHeaderAvatarByType(entry.getType()); +// } +// AvatarFX.circleClip(userAvatar, 36); +// +// fetchAndRenderHeader(entry); +// +// messageContainer.getChildren().clear(); +// loadMessages(entry); +// markAsRead(entry); +// +// applyMode(ChatViewMode.NORMAL); +// +// Platform.runLater(() -> messageInput.requestFocus()); +// } + + +public void showChat(ChatEntry entry) { + this.currentChat = entry; + this.chatName = entry.getName(); + + chatTitle.setText(entry.getName()); + if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { + Image img = AvatarLocalResolver.load(entry.getImageUrl()); + if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType()); + } else { + setDefaultHeaderAvatarByType(entry.getType()); + } + AvatarFX.circleClip(userAvatar, 36); + + // حالت اولیه (بدون انتظار هدر) + if ("channel".equalsIgnoreCase(entry.getType())) { + boolean canPostLocal = entry.isOwner() || entry.isAdmin() + || (entry.getPermissions()!=null && entry.getPermissions().optBoolean("can_post", false)); + applyMode(canPostLocal ? ChatViewMode.NORMAL : ChatViewMode.READ_ONLY); + } else { + applyMode(ChatViewMode.NORMAL); + } + + messageContainer.getChildren().clear(); + loadMessages(entry); + markAsRead(entry); + + // حالا هدر بیاد، دوباره نهایی‌اش می‌کنیم + fetchAndRenderHeader(entry); +} + + + + + public void showChat(ChatEntry entry, ChatViewMode mode) { this.currentChat = entry; + // --- Header --- chatTitle.setText(entry.getName()); - - // آواتار پیش‌فرض بر اساس نوع - // ChatPageController.showChat(...) if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { Image img = AvatarLocalResolver.load(entry.getImageUrl()); - if (img != null) { - userAvatar.setImage(img); - } else { - // ⬇️ فال‌بک بر اساس نوع - setDefaultHeaderAvatarByType(entry.getType()); - } + if (img != null) userAvatar.setImage(img); else setDefaultHeaderAvatarByType(entry.getType()); } else { setDefaultHeaderAvatarByType(entry.getType()); } -// userAvatar.setClip(new Circle(20, 20, 20)); AvatarFX.circleClip(userAvatar, 36); - - fetchAndRenderHeader(entry); - // پیام‌ها + // --- Messages --- messageContainer.getChildren().clear(); loadMessages(entry); markAsRead(entry); - Platform.runLater(() -> messageInput.requestFocus()); - } + // --- حالت UI (Composer / Join / Add Contact) --- + applyMode(mode); + // فوکوس اگر در حالت نرمال هستیم + if (mode == ChatViewMode.NORMAL) { + Platform.runLater(() -> messageInput.requestFocus()); + } + } private void loadMessages(ChatEntry entry) { JSONObject req = new JSONObject(); @@ -967,12 +1074,17 @@ public class ChatPageController { req.put("receiver_id", entry.getId().toString()); req.put("receiver_type", entry.getType()); // باید "private" باشه - // 👇 اضافه کن: آی‌دی کاربر فعلی (current user) - UUID viewerId = UUID.fromString(Session.getUserUUID()); // هر جایی که نگه می‌داری - if ("private".equalsIgnoreCase(entry.getType()) && viewerId != null) { - req.put("viewer_id", viewerId.toString()); + String viewer = Session.getUserUUID(); // internal_uuid کاربر فعلی + if (viewer != null && !viewer.isBlank()) { + req.put("viewer_id", viewer); } +// // 👇 اضافه کن: آی‌دی کاربر فعلی (current user) +// UUID viewerId = UUID.fromString(Session.getUserUUID()); // هر جایی که نگه می‌داری +// if ("private".equalsIgnoreCase(entry.getType()) && viewerId != null) { +// req.put("viewer_id", viewerId.toString()); +// } + new Thread(() -> { JSONObject resp; try { @@ -1000,21 +1112,46 @@ public class ChatPageController { } } +// private void updatePrivateHeader(ChatEntry entry, JSONObject data) { +// String name = nz(data.optString("profile_name", entry.getName())); +// chatTitle.setText(name); +// +// // other_user_id برای ریل‌تایم status +// String other = data.optString("other_user_id", ""); +// if (!other.isBlank()) { +// try { entry.setOtherUserId(java.util.UUID.fromString(other)); } catch (Exception ignore) {} +// } +// +// // تصویر +// String img = data.optString("image_url", ""); +// if (hasVal(img)) { +// try { +// Image im = AvatarLocalResolver.load(img); // ⬅️ +// if (im != null) userAvatar.setImage(im); +// userAvatar.setClip(new Circle(20, 20, 20)); +// } catch (Exception ignore) {} +// } +// +// chatStatus.setText(userStatusText( +// data.optBoolean("online", false), +// data.optString("last_seen", null) +// )); +// +// } + private void updatePrivateHeader(ChatEntry entry, JSONObject data) { String name = nz(data.optString("profile_name", entry.getName())); chatTitle.setText(name); - // other_user_id برای ریل‌تایم status String other = data.optString("other_user_id", ""); if (!other.isBlank()) { - try { entry.setOtherUserId(java.util.UUID.fromString(other)); } catch (Exception ignore) {} + try { entry.setOtherUserId(UUID.fromString(other)); } catch (Exception ignore) {} } - // تصویر String img = data.optString("image_url", ""); if (hasVal(img)) { try { - Image im = AvatarLocalResolver.load(img); // ⬅️ + Image im = AvatarLocalResolver.load(img); if (im != null) userAvatar.setImage(im); userAvatar.setClip(new Circle(20, 20, 20)); } catch (Exception ignore) {} @@ -1025,8 +1162,20 @@ public class ChatPageController { data.optString("last_seen", null) )); + // ⭐️ بلاک؟ + boolean blocked = data.optBoolean("blocked", false) + || data.optBoolean("is_blocked", false) + || data.optBoolean("blocked_by_me", false); + + if (blocked) { + if (readOnlyLabel != null) readOnlyLabel.setText(""); // فقط UNBLOCK را نشان بده + applyMode(ChatViewMode.BLOCKED); + } else { + applyMode(ChatViewMode.NORMAL); + } } + private void updateGroupHeader(ChatEntry entry, JSONObject data) { chatTitle.setText(nz(data.optString("group_name", entry.getName()))); @@ -1046,23 +1195,51 @@ public class ChatPageController { : (members + " members")); } +// private void updateChannelHeader(ChatEntry entry, JSONObject data) { +// chatTitle.setText(nz(data.optString("channel_name", entry.getName()))); +// +// String img = data.optString("image_url", ""); +// if (hasVal(img)) { +// try { +// Image im = AvatarLocalResolver.load(img); // ⬅️ +// if (im != null) userAvatar.setImage(im); +// userAvatar.setClip(new Circle(20, 20, 20)); +// } catch (Exception ignore) {} +// } +// +// +// int subs = data.optInt("member_count", 0); +// chatStatus.setText(subs + " subscribers"); +// } + + private void updateChannelHeader(ChatEntry entry, JSONObject data) { chatTitle.setText(nz(data.optString("channel_name", entry.getName()))); String img = data.optString("image_url", ""); if (hasVal(img)) { try { - Image im = AvatarLocalResolver.load(img); // ⬅️ + Image im = AvatarLocalResolver.load(img); if (im != null) userAvatar.setImage(im); userAvatar.setClip(new Circle(20, 20, 20)); } catch (Exception ignore) {} } - int subs = data.optInt("member_count", 0); chatStatus.setText(subs + " subscribers"); + + boolean canPost = canPostToChannel(entry, data); + if (canPost) { + applyMode(ChatViewMode.NORMAL); + Platform.runLater(() -> messageInput.requestFocus()); + } else { + if (readOnlyLabel != null) readOnlyLabel.setText("YOU CAN’T SEND MESSAGES IN THIS CHANNEL"); + applyMode(ChatViewMode.READ_ONLY); + } } + + public void onUserStatusChanged(String userUuid, String status, String lastSeenIso) { if (currentChat == null || !"private".equalsIgnoreCase(currentChat.getType())) return; var other = currentChat.getOtherUserId(); @@ -1112,4 +1289,203 @@ public class ChatPageController { java.util.Objects.requireNonNull(getClass().getResourceAsStream(path)) )); } + + @FXML + private void onJoinClicked() { + if (currentChat == null) return; + + // 1) internal_uuid کاربر فعلی (UUID) + String myInternalUuid = Session.currentUser != null + ? Session.currentUser.optString("internal_uuid", "") + : ""; + if (myInternalUuid.isBlank()) { + addSystemMessage("Join failed: missing current user internal_uuid."); + return; + } + + // 2) internal_uuid مقصد (گروه/کانال) + String targetId = currentChat.getId().toString(); + + // 3) نوع و نام اکشن + String t = currentChat.getType(); + String action = "group".equalsIgnoreCase(t) ? "join_group" : "join_channel"; + + // 4) درخواست طبق قرارداد سرور (کلیدها: user_id = UUID کاربر، id = UUID مقصد) + JSONObject req = new JSONObject() + .put("action", action) + .put("user_id", myInternalUuid) // ← UUID + .put("id", targetId); // ← UUID گروه/کانال + + JSONObject res = ActionHandler.sendWithResponse(req); + if (res != null && "success".equalsIgnoreCase(res.optString("status"))) { + MainController.getInstance().onJoinedOrAdded(currentChat); + applyMode(ChatViewMode.NORMAL); + Platform.runLater(() -> messageInput.requestFocus()); + } else { + addSystemMessage("Join failed: " + (res != null ? res.optString("message","") : "no response")); + } + } + + + @FXML + private void onAddContactClicked() { + if (currentChat == null) return; + + String myUserId = Session.currentUser != null + ? Session.currentUser.optString("user_id", "") + : ""; + + // 2) internal_uuid طرف مقابل + UUID other = currentChat.getOtherUserId(); + if (other == null) { + // اگر otherUserId هنوز نگرفته‌ای، بهتره قبلش از هدر/پروفایل بیاری. + addSystemMessage("Cannot add: other user UUID is missing."); + return; + } + + // 3) درخواست طبق قرارداد سرور + JSONObject req = new JSONObject() + .put("action", "add_contact") + .put("user_id", myUserId) // ← stringِ user_id (غیر UUID) + .put("contact_id", other.toString()); // ← UUID طرف مقابل + + // 4) ارسال + JSONObject res = ActionHandler.sendWithResponse(req); + if (res != null && "success".equalsIgnoreCase(res.optString("status"))) { + // به لیست چت‌ها اضافه و سوییچ به حالت نرمال + MainController.getInstance().onJoinedOrAdded(currentChat); + applyMode(ChatViewMode.NORMAL); + Platform.runLater(() -> messageInput.requestFocus()); + } else { + addSystemMessage("Add contact failed: " + (res != null ? res.optString("message","") : "no response")); + } + } + + +// private void applyMode(ChatViewMode mode) { +// currentMode = mode; +// +// boolean normal = (mode == ChatViewMode.NORMAL); +// boolean needsJoin = (mode == ChatViewMode.NEEDS_JOIN); +// boolean needsAdd = (mode == ChatViewMode.NEEDS_ADD_CONTACT); +// +// composerPane.setVisible(normal); +// composerPane.setManaged(normal); +// +// joinPane.setVisible(needsJoin); +// joinPane.setManaged(needsJoin); +// +// addContactPane.setVisible(needsAdd); +// addContactPane.setManaged(needsAdd); +// +// if (needsJoin && joinButton != null && currentChat != null) { +// String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP"; +// joinButton.setText(("Join " + what).toUpperCase()); // => JOIN CHANNEL / JOIN GROUP +// } +// if (needsAdd && addContactButton != null) { +// addContactButton.setText("ADD CONTACT"); +// } +// } + + + + private void applyMode(ChatViewMode mode) { + currentMode = mode; + + boolean normal = (mode == ChatViewMode.NORMAL); + boolean needsJoin = (mode == ChatViewMode.NEEDS_JOIN); + boolean needsAdd = (mode == ChatViewMode.NEEDS_ADD_CONTACT); + boolean readOnly = (mode == ChatViewMode.READ_ONLY); + boolean blocked = (mode == ChatViewMode.BLOCKED); + + // Composer فقط در حالت نرمال + composerPane.setVisible(normal); + composerPane.setManaged(normal); + + // Join / Add + joinPane.setVisible(needsJoin); + joinPane.setManaged(needsJoin); + addContactPane.setVisible(needsAdd); + addContactPane.setManaged(needsAdd); + + // پنل پایین برای READ_ONLY/BLOCKED + boolean showRO = readOnly || blocked; + if (readOnlyPane != null) { + readOnlyPane.setVisible(showRO); + readOnlyPane.setManaged(showRO); + } + + // متن آبی برای READ_ONLY + if (readOnlyLabel != null) { + readOnlyLabel.setVisible(readOnly); + readOnlyLabel.setManaged(readOnly); + } + + // دکمهٔ قرمز UNBLOCK فقط در BLOCKED + if (unblockBtn != null) { + unblockBtn.setVisible(blocked); + unblockBtn.setManaged(blocked); + } + + // متن دکمه‌های Join/Add + if (needsJoin && joinButton != null && currentChat != null) { + String what = "channel".equalsIgnoreCase(currentChat.getType()) ? "CHANNEL" : "GROUP"; + joinButton.setText(("Join " + what).toUpperCase()); + } + if (needsAdd && addContactButton != null) { + addContactButton.setText("ADD CONTACT"); + } + } + + + @FXML + private void onUnblockClicked() { + if (currentChat == null) return; + UUID other = currentChat.getOtherUserId(); + if (other == null && currentChat.getDisplayId() == null) return; + + // ⚠️ با API خودت هماهنگ کن: + // این یک الگوی معمول است: user_id (کاربر فعلی) + contact_id (کسی که بلاک شده) + org.json.JSONObject req = new org.json.JSONObject() + .put("action", "unblock_user") // یا "unblock_contact" طبق سرور + .put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id")) + .put("contact_id", (other != null) ? other.toString() : currentChat.getDisplayId()); + + org.json.JSONObject res = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req); + boolean ok = (res != null) && ("ok".equalsIgnoreCase(res.optString("status")) + || "success".equalsIgnoreCase(res.optString("status"))); + + if (ok) { + // برگرد به حالت نرمال: کامپوزر باز شود + applyMode(ChatViewMode.NORMAL); + Platform.runLater(() -> messageInput.requestFocus()); + } else { + // می‌تونی یک نوتیف کوچک بزنی + addSystemMessage("Unblock failed."); + } + } + + + + private boolean canPostToChannel(ChatEntry entry, JSONObject headerData) { + // 1) اگر سرور صراحتاً can_post داد، همان را بگیر + if (headerData != null && headerData.has("can_post")) { + return headerData.optBoolean("can_post", false); + } + // 2) یا اگر is_owner / is_admin را داد + if (headerData != null && (headerData.has("is_owner") || headerData.has("is_admin"))) { + return headerData.optBoolean("is_owner", false) || headerData.optBoolean("is_admin", false); + } + // 3) فال‌بک به اطلاعات لوکال: ChatEntry + permissions محلی + if (entry != null) { + if (entry.isOwner() || entry.isAdmin()) return true; + if (entry.getPermissions() != null && entry.getPermissions().optBoolean("can_post", false)) { + return true; + } + } + return false; + } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/UI/ChatViewMode.java b/src/main/java/org/to/telegramfinalproject/UI/ChatViewMode.java new file mode 100644 index 0000000..459dbdd --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/ChatViewMode.java @@ -0,0 +1,12 @@ +package org.to.telegramfinalproject.UI; + + +//For search handling +public enum ChatViewMode { + NORMAL, // member/contact; can send messages + NEEDS_JOIN, // group/channel preview; show Join button + NEEDS_ADD_CONTACT, // private preview; show Add Contact button + READ_ONLY, + BLOCKED + } + diff --git a/src/main/java/org/to/telegramfinalproject/UI/MainController.java b/src/main/java/org/to/telegramfinalproject/UI/MainController.java index b1ccaf8..16bffad 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/MainController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/MainController.java @@ -40,6 +40,7 @@ public class MainController { GLOBAL, CHAT } + private ChatViewMode currentMode = ChatViewMode.NORMAL; // حالت فعلی: NORMAL/NEEDS_JOIN/NEEDS_ADD_CONTACT private SearchMode currentSearchMode = SearchMode.GLOBAL; private UUID currentChatId; // if in CHAT mode, which chat to search in @@ -423,6 +424,9 @@ public class MainController { FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml")); Node chatPage = loader.load(); + + + ChatPageController controller = loader.getController(); controller.showChat(chat); @@ -439,6 +443,30 @@ public class MainController { } } + + private void openChatWithMode(ChatEntry chat, ChatViewMode mode) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml")); + Node chatPage = loader.load(); + + ChatPageController controller = loader.getController(); + controller.showChat(chat, mode); // ⬅️ متد جدید در ChatPageController + + this.chatPageController = controller; + Session.currentChatId = chat.getId().toString(); + + chatDisplayArea.getChildren().setAll(chatPage); + + chat.setUnreadCount(0); + ChatItemController item = itemControllers.get(chat.getId()); + if (item != null) item.setUnread(0); + + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @FXML private void toggleSidebar() { if (isSidebarOpen) { @@ -832,39 +860,52 @@ public class MainController { private void openSearchResult(SearchResult r) { switch (r.type) { case USER: { + // 1) اگه قبلاً چت پرایوت با این یوزر داری، از همون استفاده کن + UUID existingChatId = findExistingPrivateChatId(r.uuid); - java.util.UUID chatId = findExistingPrivateChatId(r.uuid); - if (chatId == null) { - chatId = fetchOrCreatePrivateChat(r.uuid); - if (chatId == null) { - System.out.println("❌ Failed to create/find private chat."); - return; - } + ChatEntry ce = new ChatEntry(); + ce.setType("private"); + ce.setName(r.title); + ce.setDisplayId(r.displayId); + try { ce.setOtherUserId(r.uuid); } catch (Exception ignore) {} + + ChatViewMode mode; + if (existingChatId != null) { + ce.setId(existingChatId.toString()); + mode = ChatViewMode.NORMAL; + } else { + // هنوز چتی وجود ندارد → Preview (بدون ساخت چت) + // برای Preview از uuid خودِ طرف مقابل به‌عنوان id موقت استفاده می‌کنیم + ce.setId(r.uuid.toString()); + mode = isContact(r.uuid) ? ChatViewMode.NORMAL : ChatViewMode.NEEDS_ADD_CONTACT; } - org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry(); - ce.setId(chatId.toString()); // ⬅️ internal chat_id - ce.setDisplayId(r.displayId); // username - ce.setName(r.title); // profile_name - ce.setType("private"); - - openChat(ce); + openChatWithMode(ce, mode); break; } + case GROUP: case CHANNEL: { org.to.telegramfinalproject.Models.ChatEntry existing = findExistingChat(r.uuid, r.receiverType); if (existing != null) { - openChat(existing); + openChatWithMode(existing, ChatViewMode.NORMAL); } else { org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry(); ce.setId(r.uuid.toString()); // internal_uuid group/channel ce.setDisplayId(r.displayId); // group_id/channel_id ce.setName(r.title); ce.setType(r.receiverType); - openChat(ce); + //openChat(ce); + ChatViewMode mode; + try { + mode = isInAnyChatList(r.uuid) ? ChatViewMode.NORMAL : ChatViewMode.NEEDS_JOIN; + } catch (Exception e) { + mode = ChatViewMode.NEEDS_JOIN; + } + openChatWithMode(ce, mode); + } break; } @@ -968,4 +1009,87 @@ public class MainController { return false; } + + + //Search + private boolean isInAnyChatList(UUID chatId) { + var lists = List.of( + Session.chatList != null ? Session.chatList : List.of(), + Session.activeChats != null ? Session.activeChats : List.of(), + Session.archivedChats != null ? Session.archivedChats : List.of() + ); + for (var lst : lists) { + for (var c : lst) { + try { + if (chatId.equals(UUID.fromString(c.getId().toString()))) return true; + } catch (Exception ignore) {} + } + } + return false; + } + + + private boolean isContact(UUID userUuid) { + if (Session.contactEntries == null) return false; + try { + for (var c : Session.contactEntries) { + UUID id = c.getContactId(); + if (userUuid.equals(id)) return true; + } + } catch (Exception ignore) {} + return false; + } + + + + private ChatViewMode computeMode(ChatEntry ce) { + try { + UUID id = UUID.fromString(ce.getId().toString()); + if (isInAnyChatList(id)) return ChatViewMode.NORMAL; + } catch (Exception ignore) {} + + String t = ce.getType(); + if ("group".equalsIgnoreCase(t) || "channel".equalsIgnoreCase(t)) { + return ChatViewMode.NEEDS_JOIN; + } + if ("private".equalsIgnoreCase(t)) { + UUID other = null; + try { other = ce.getOtherUserId(); } catch (Exception ignore) {} + return (other != null && isContact(other)) + ? ChatViewMode.NORMAL + : ChatViewMode.NEEDS_ADD_CONTACT; + } + return ChatViewMode.NORMAL; + } + + public void onJoinedOrAdded(ChatEntry ce) { + try { + UUID id = UUID.fromString(ce.getId().toString()); + if (!isInAnyChatList(id)) { + if (Session.chatList == null) Session.chatList = new ArrayList<>(); + Session.chatList.add(ce); + } + // اگر activeChats استفاده می‌کنی: + if (Session.activeChats != null && Session.activeChats.stream().noneMatch(c -> id.equals(c.getId()))) { + Session.activeChats.add(ce); + } + } catch (Exception ignore) {} + + // (اختیاری) مرتب‌سازی بر اساس زمان آخرین پیام + Comparator byTimeDesc = (a,b) -> { + LocalDateTime t1 = a.getLastMessageTime(), t2 = b.getLastMessageTime(); + if (t1 == null && t2 == null) return 0; + if (t1 == null) return 1; + if (t2 == null) return -1; + return t2.compareTo(t1); + }; + if (Session.chatList != null) Session.chatList.sort(byTimeDesc); + if (Session.activeChats != null) Session.activeChats.sort(byTimeDesc); + + refreshChatListUI(); + } + + + + } \ No newline at end of file diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/chat.css b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css new file mode 100644 index 0000000..5def59d --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/CSS/chat.css @@ -0,0 +1,76 @@ +/* دقیقاً فقط روی این دو دکمه اعمال می‌شود */ +#joinAction, #addContactAction { + -fx-background-color: transparent, transparent, transparent, transparent; + -fx-background-insets: 0,0,0,0; + -fx-background-radius: 0,0,0,0; + -fx-border-color: transparent; + -fx-border-width: 0; + -fx-text-fill: #1e88e5; /* آبی لینک */ + -fx-font-weight: 700; + -fx-font-size: 12px; + -fx-padding: 12 0 12 0; + -fx-cursor: hand; + -fx-effect: null; +} + +#joinAction:hover, #addContactAction:hover { + -fx-underline: true; +} + +#joinAction:focused, #addContactAction:focused { + -fx-underline: true; + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; +} + +#joinAction:armed, #addContactAction:armed { + -fx-opacity: .85; +} + +/* ظرف بنر پایین */ +.chat-footer-banner { + -fx-background-color: transparent; +} + +/* دکمهٔ لینک‌مانند (مشترک) */ +.footer-link-btn { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 0; + -fx-padding: 6 0 6 0; /* نازک مثل لینک */ + -fx-border-color: transparent; + -fx-font-size: 14px; + -fx-font-weight: 700; + -fx-text-fill: #1a73e8; /* پیش‌فرض آبی (برای Join/Add) */ +} + +/* کانتینر بنر پایین چت */ +.chat-footer-banner { + -fx-background-color: transparent; + -fx-alignment: center; +} + +/* متن آبی برای حالت Read-only */ +.chat-footer-banner .banner-text-blue { + -fx-text-fill: #1E88E5; /* آبی */ + -fx-font-weight: 700; + -fx-background-color: transparent; +} + +/* لینک‌استایل دکمه‌ها در بنر */ +.chat-footer-banner .footer-link-btn { + -fx-background-color: transparent; + -fx-text-fill: #1E88E5; /* آبی پیش‌فرض */ + -fx-font-weight: 700; + -fx-padding: 6 12; + -fx-background-insets: 0; + -fx-cursor: hand; +} +.chat-footer-banner .footer-link-btn:hover { + -fx-underline: true; +} + +/* نسخه قرمز برای UNBLOCK */ +.chat-footer-banner .footer-link-btn.danger { + -fx-text-fill: #D32F2F; /* قرمز */ +} diff --git a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml index e577564..2660490 100644 --- a/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml +++ b/src/main/resources/org/to/telegramfinalproject/Fxml/chat_page.fxml @@ -6,17 +6,19 @@ + + + + - - - + @@ -33,9 +35,7 @@ @@ -44,9 +44,7 @@ + + +