fix chat header
This commit is contained in:
@@ -172,11 +172,25 @@ public class IncomingMessageListener implements Runnable {
|
||||
bumpChatListFromUpdate(data);
|
||||
}
|
||||
|
||||
case "user_status_changed" -> {
|
||||
|
||||
displayRealTimeMessage(action, msg);
|
||||
Platform.runLater(() -> {
|
||||
var mc = org.to.telegramfinalproject.UI.MainController.getInstance();
|
||||
var cp = (mc != null) ? mc.getChatPageController() : null;
|
||||
if (cp != null) cp.onUserStatusChanged(
|
||||
msg.optString("user_id",""),
|
||||
msg.optString("status",""),
|
||||
msg.optString("last_seen","")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
case "message_edited", "message_deleted_global", "message_reacted", "message_unreacted",
|
||||
"user_status_changed", "blocked_by_user", "unblocked_by_user", "message_seen" -> {
|
||||
|
||||
case "message_edited", "message_deleted_global", "message_reacted", "message_unreacted"
|
||||
, "blocked_by_user", "unblocked_by_user", "message_seen" -> {
|
||||
displayRealTimeMessage(action, msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatInfoDatabase {
|
||||
|
||||
/**
|
||||
* هدر چت را بر اساس نوع آن برمیگرداند.
|
||||
* private: name,image_url, online,last_seen
|
||||
* group : name,image_url, member_count
|
||||
* channel: name,image_url, member_count
|
||||
*/
|
||||
public static JSONObject getHeaderInfo(String type, UUID receiverId, UUID viewerId) throws SQLException {
|
||||
try (Connection conn = ConnectionDb.connect()) {
|
||||
switch (type.toLowerCase()) {
|
||||
case "private":
|
||||
return getPrivateHeader(conn, receiverId, viewerId);
|
||||
case "group":
|
||||
return getGroupHeader(conn, receiverId);
|
||||
case "channel":
|
||||
return getChannelHeader(conn, receiverId);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported receiver_type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** هدر چت خصوصی: other-user نسبت به viewerId را پیدا میکنیم */
|
||||
private static JSONObject getPrivateHeader(Connection conn, UUID chatId, UUID viewerId) throws SQLException {
|
||||
if (viewerId == null) throw new IllegalArgumentException("viewerId is required for private header.");
|
||||
|
||||
final String sql = """
|
||||
SELECT
|
||||
u.profile_name AS name,
|
||||
COALESCE(u.image_url, '') AS image_url,
|
||||
(u.status = 'online') AS online,
|
||||
u.status AS status,
|
||||
u.last_seen AS last_seen
|
||||
FROM private_chat pc
|
||||
JOIN users u
|
||||
ON u.internal_uuid = CASE WHEN pc.user1_id = ? THEN pc.user2_id ELSE pc.user1_id END
|
||||
WHERE pc.chat_id = ?
|
||||
AND (pc.user1_id = ? OR pc.user2_id = ?)
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
int i = 1;
|
||||
ps.setObject(i++, viewerId);
|
||||
ps.setObject(i++, chatId);
|
||||
ps.setObject(i++, viewerId);
|
||||
ps.setObject(i++, viewerId);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Private chat not found or viewer is not a participant.");
|
||||
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "private");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
|
||||
// خواندن بولینِ online با سازگاری DB
|
||||
Object onlineObj = rs.getObject("online");
|
||||
boolean online = (onlineObj instanceof Boolean) ? (Boolean) onlineObj
|
||||
: rs.getInt("online") == 1;
|
||||
j.put("online", online);
|
||||
j.put("status", rs.getString("status")); // رشتهی وضعیت هم میآید
|
||||
|
||||
Timestamp ts = rs.getTimestamp("last_seen");
|
||||
j.put("last_seen", ts == null ? JSONObject.NULL : ts.toInstant().toString());
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** هدر گروه: نام، عکس و تعداد اعضا */
|
||||
private static JSONObject getGroupHeader(Connection conn, UUID groupInternalId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT
|
||||
g.group_name AS name,
|
||||
COALESCE(g.image_url, '') AS image_url,
|
||||
(SELECT COUNT(*) FROM group_members gm WHERE gm.group_id = g.internal_uuid) AS member_count
|
||||
FROM groups g
|
||||
WHERE g.internal_uuid = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, groupInternalId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Group not found.");
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "group");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
j.put("member_count", rs.getInt("member_count"));
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** هدر کانال: نام، عکس و تعداد سابسکرایبر */
|
||||
private static JSONObject getChannelHeader(Connection conn, UUID channelInternalId) throws SQLException {
|
||||
String sql = """
|
||||
SELECT
|
||||
c.channel_name AS name,
|
||||
COALESCE(c.image_url, '') AS image_url,
|
||||
(SELECT COUNT(*) FROM channel_subscribers cs WHERE cs.channel_id = c.internal_uuid) AS member_count
|
||||
FROM channels c
|
||||
WHERE c.internal_uuid = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, channelInternalId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) throw new SQLException("Channel not found.");
|
||||
JSONObject j = new JSONObject();
|
||||
j.put("type", "channel");
|
||||
j.put("name", rs.getString("name"));
|
||||
j.put("image_url", rs.getString("image_url"));
|
||||
j.put("member_count", rs.getInt("member_count"));
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,22 +448,19 @@ public class MessageDatabase {
|
||||
|
||||
|
||||
public static List<Message> getUnreadMessages(UUID userId) {
|
||||
return getUnreadMessages(userId, 200); // یک سقف معقول
|
||||
return getUnreadMessages(userId, 200);
|
||||
}
|
||||
|
||||
public static List<Message> getUnreadMessages(UUID userId, int limit) {
|
||||
List<Message> messages = new ArrayList<>();
|
||||
|
||||
// توجه: نام جدول private_chats/gm/cs را با اسامی واقعی دیتابیست هماهنگ کن
|
||||
String sql = """
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
LEFT JOIN message_receipts r
|
||||
ON r.message_id = m.message_id
|
||||
AND r.user_id = ?
|
||||
ON r.message_id = m.message_id AND r.user_id = ?
|
||||
LEFT JOIN deleted_messages d
|
||||
ON d.message_id = m.message_id
|
||||
AND d.user_id = ?
|
||||
ON d.message_id = m.message_id AND d.user_id = ?
|
||||
WHERE r.message_id IS NULL
|
||||
AND d.message_id IS NULL
|
||||
AND m.is_deleted_globally = FALSE
|
||||
@@ -474,14 +471,14 @@ public class MessageDatabase {
|
||||
FROM private_chat pc
|
||||
WHERE pc.chat_id = m.receiver_id
|
||||
AND (pc.user1_id = ? OR pc.user2_id = ?)
|
||||
)))
|
||||
OR (m.receiver_type = 'group' AND EXISTS (
|
||||
))
|
||||
OR (m.receiver_type = 'group' AND EXISTS (
|
||||
SELECT 1
|
||||
FROM group_members gm
|
||||
WHERE gm.group_id = m.receiver_id
|
||||
AND gm.user_id = ?
|
||||
))
|
||||
OR (m.receiver_type = 'channel' AND EXISTS (
|
||||
OR (m.receiver_type = 'channel' AND EXISTS (
|
||||
SELECT 1
|
||||
FROM channel_subscribers cs
|
||||
WHERE cs.channel_id = m.receiver_id
|
||||
@@ -499,16 +496,14 @@ public class MessageDatabase {
|
||||
ps.setObject(i++, userId); // r.user_id
|
||||
ps.setObject(i++, userId); // d.user_id
|
||||
ps.setObject(i++, userId); // m.sender_id <> ?
|
||||
ps.setObject(i++, userId); // pc.user1_id=userId
|
||||
ps.setObject(i++, userId); // pc.user2_id=userId
|
||||
ps.setObject(i++, userId); // pc.user1_id
|
||||
ps.setObject(i++, userId); // pc.user2_id
|
||||
ps.setObject(i++, userId); // gm.user_id
|
||||
ps.setObject(i++, userId); // cs.user_id
|
||||
ps.setInt(i++, Math.max(1, limit));
|
||||
ps.setInt(i, Math.max(1, limit));
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
messages.add(mapMessage(rs)); // همان mapMessage که گفتیم
|
||||
}
|
||||
while (rs.next()) messages.add(mapMessage(rs));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
@@ -517,6 +512,7 @@ public class MessageDatabase {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static java.util.UUID readUUID(ResultSet rs, String col) throws SQLException {
|
||||
Object o = rs.getObject(col);
|
||||
if (o == null) return null;
|
||||
|
||||
@@ -2808,6 +2808,37 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
case "get_header_info": {
|
||||
try {
|
||||
String type = requestJson.getString("receiver_type"); // "private" | "group" | "channel"
|
||||
UUID receiverId = UUID.fromString(requestJson.getString("receiver_id"));
|
||||
|
||||
UUID viewerId = null;
|
||||
if ("private".equalsIgnoreCase(type)) {
|
||||
String v = requestJson.optString("viewer_id",
|
||||
requestJson.optString("my_id", null));
|
||||
if (v == null) {
|
||||
response = new ResponseModel("error", "viewer_id (or my_id) is required for private chats.");
|
||||
break;
|
||||
}
|
||||
viewerId = UUID.fromString(v);
|
||||
}
|
||||
|
||||
org.json.JSONObject data = org.to.telegramfinalproject.Database.ChatInfoDatabase
|
||||
.getHeaderInfo(type, receiverId, viewerId);
|
||||
|
||||
response = new ResponseModel("success", "Header info fetched.", data);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
response = new ResponseModel("error", "Failed to fetch header info.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
response = new ResponseModel("error", "Unknown action: " + action);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -536,7 +537,7 @@ public class ChatPageController {
|
||||
|
||||
chatTitle.setText(entry.getName());
|
||||
|
||||
// Default avatar
|
||||
// آواتار پیشفرض بر اساس نوع
|
||||
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
||||
userAvatar.setImage(new Image(entry.getImageUrl()));
|
||||
} else if ("channel".equals(entry.getType())) {
|
||||
@@ -557,43 +558,9 @@ public class ChatPageController {
|
||||
}
|
||||
userAvatar.setClip(new Circle(20, 20, 20));
|
||||
|
||||
// === Private chat? Fetch other user status ===
|
||||
if ("private".equalsIgnoreCase(entry.getType()) && entry.getOtherUserId() != null) {
|
||||
UUID otherId = entry.getOtherUserId();
|
||||
fetchAndRenderHeader(entry);
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "get_other_user_status");
|
||||
req.put("user_id", otherId.toString());
|
||||
|
||||
JSONObject resp = ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equals(resp.optString("status"))) {
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
boolean online = data.optBoolean("online", false);
|
||||
String lastSeenIso = data.optString("last_seen", null);
|
||||
|
||||
String statusText;
|
||||
if (online) {
|
||||
statusText = "online";
|
||||
} else {
|
||||
// fallback if no timestamp
|
||||
statusText = (lastSeenIso == null || lastSeenIso.isEmpty())
|
||||
? "last seen recently"
|
||||
: "last seen " + formatWhen(parseWhen(lastSeenIso));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> chatStatus.setText(statusText));
|
||||
}
|
||||
}).start();
|
||||
} else if ("group".equalsIgnoreCase(entry.getType())) {
|
||||
chatStatus.setText("Group");
|
||||
} else if ("channel".equalsIgnoreCase(entry.getType())) {
|
||||
chatStatus.setText("Channel");
|
||||
} else {
|
||||
chatStatus.setText("");
|
||||
}
|
||||
|
||||
// Load chat messages
|
||||
// پیامها
|
||||
messageContainer.getChildren().clear();
|
||||
loadMessages(entry);
|
||||
markAsRead(entry);
|
||||
@@ -601,6 +568,7 @@ public class ChatPageController {
|
||||
Platform.runLater(() -> messageInput.requestFocus());
|
||||
}
|
||||
|
||||
|
||||
private void loadMessages(ChatEntry entry) {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "get_messages_UI");
|
||||
@@ -993,4 +961,140 @@ public class ChatPageController {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fetchAndRenderHeader(ChatEntry entry) {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "get_header_info");
|
||||
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());
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
JSONObject resp;
|
||||
try {
|
||||
resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||
} catch (Exception ex) { ex.printStackTrace(); return; }
|
||||
|
||||
if (resp == null || !"success".equalsIgnoreCase(resp.optString("status"))) {
|
||||
System.err.println("get_header_info failed: " + (resp != null ? resp.optString("message") : "null resp"));
|
||||
return;
|
||||
}
|
||||
JSONObject data = resp.optJSONObject("data");
|
||||
if (data == null) return;
|
||||
|
||||
Platform.runLater(() -> renderHeaderFromData(entry, data));
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void renderHeaderFromData(ChatEntry entry, JSONObject data) {
|
||||
String t = entry.getType() == null ? "" : entry.getType().toLowerCase();
|
||||
switch (t) {
|
||||
case "private" -> updatePrivateHeader(entry, data);
|
||||
case "group" -> updateGroupHeader(entry, data);
|
||||
case "channel" -> updateChannelHeader(entry, data);
|
||||
default -> chatStatus.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
userAvatar.setImage(new Image(img, true));
|
||||
userAvatar.setClip(new Circle(20, 20, 20));
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
chatStatus.setText(userStatusText(
|
||||
data.optBoolean("online", false),
|
||||
data.optString("last_seen", null)
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private void updateGroupHeader(ChatEntry entry, JSONObject data) {
|
||||
chatTitle.setText(nz(data.optString("group_name", entry.getName())));
|
||||
|
||||
String img = data.optString("image_url", "");
|
||||
if (hasVal(img)) {
|
||||
try {
|
||||
userAvatar.setImage(new Image(img, true));
|
||||
userAvatar.setClip(new Circle(20, 20, 20));
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
int members = data.optInt("member_count", 0);
|
||||
int online = data.optInt("online_count", -1);
|
||||
chatStatus.setText(online >= 0 ? (members + " members, " + online + " online")
|
||||
: (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 {
|
||||
userAvatar.setImage(new Image(img, true));
|
||||
userAvatar.setClip(new Circle(20, 20, 20));
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
int subs = data.optInt("member_count", 0);
|
||||
chatStatus.setText(subs + " subscribers");
|
||||
}
|
||||
|
||||
public void onUserStatusChanged(String userUuid, String status, String lastSeenIso) {
|
||||
if (currentChat == null || !"private".equalsIgnoreCase(currentChat.getType())) return;
|
||||
var other = currentChat.getOtherUserId();
|
||||
if (other == null || !other.toString().equalsIgnoreCase(userUuid)) return;
|
||||
|
||||
if ("online".equalsIgnoreCase(status)) {
|
||||
chatStatus.setText("online");
|
||||
} else {
|
||||
if (hasVal(lastSeenIso)) {
|
||||
var ts = parseWhen(lastSeenIso);
|
||||
chatStatus.setText(ts != null ? ("last seen " + formatWhen(ts)) : "last seen recently");
|
||||
} else {
|
||||
chatStatus.setText("last seen recently");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String userStatusText(boolean online, String lastSeenIso) {
|
||||
if (online) return "online";
|
||||
|
||||
LocalDateTime ts = parseWhen(lastSeenIso);
|
||||
if (ts == null) return "Last seen recently";
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate d = ts.toLocalDate();
|
||||
|
||||
if (d.isEqual(today)) {
|
||||
return FMT_HHMM.format(ts);
|
||||
}
|
||||
|
||||
long days = ChronoUnit.DAYS.between(d, today);
|
||||
if (days > 30) {
|
||||
return "Last seen long time ago";
|
||||
}
|
||||
return "recently";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,4 +963,11 @@ public class MainController {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user