Time Formatter

This commit is contained in:
2025-08-28 23:27:03 +03:30
parent ff6f771e5c
commit 9d1737a90f
3 changed files with 171 additions and 63 deletions
@@ -13,11 +13,16 @@ import javafx.stage.FileChooser;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Client.ActionHandler; import org.to.telegramfinalproject.Client.ActionHandler;
import org.to.telegramfinalproject.Client.Session;
import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ChatEntry;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID; import java.util.UUID;
public class ChatPageController { public class ChatPageController {
@@ -78,15 +83,47 @@ public class ChatPageController {
private void initCurrentUserId() { private void initCurrentUserId() {
try { try {
// از سشنی که سمت کلاینت داری:
String meStr = org.to.telegramfinalproject.Client.Session String meStr = org.to.telegramfinalproject.Client.Session
.currentUser.getString("internal_uuid"); .currentUser.getString("internal_uuid");
me = UUID.fromString(meStr); me = UUID.fromString(meStr);
} catch (Exception ignore) { } catch (Exception ignore) {
me = null; // اگر به هر دلیلی نبود، خروجی‌ها رو ورودی فرض نکن me = null;
} }
} }
//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");
private static final String YESTERDAY_LABEL = "Yesterday";
private String formatWhen(LocalDateTime ts) {
if (ts == null) return "";
LocalDate today = LocalDate.now();
LocalDate d = ts.toLocalDate();
if (d.isEqual(today)) {
return FMT_HHMM.format(ts);
} else if (d.isEqual(today.minusDays(1))) {
return YESTERDAY_LABEL + " " + FMT_HHMM.format(ts);
} else {
return FMT_DATE_TIME.format(ts);
}
}
/** ISO → LocalDateTime (با پشتیبانی از Offset/Z) */
private LocalDateTime parseWhen(String iso) {
if (iso == null || iso.isEmpty()) return null;
try {
return OffsetDateTime.parse(iso).toLocalDateTime();
} catch (Exception ignore) {
try {
return LocalDateTime.parse(iso);
} catch (Exception e) {
return null;
}
}
}
@FXML @FXML
public void initialize() { public void initialize() {
@@ -327,10 +364,10 @@ public class ChatPageController {
// Header // Header
chatTitle.setText(entry.getName()); chatTitle.setText(entry.getName());
chatStatus.setText(""); // اگر last seen داری اینجا بگذار chatStatus.setText("");
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) { if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
try { try {
userAvatar.setImage(new Image(entry.getImageUrl())); // یا لود از ریسورس خودت userAvatar.setImage(new Image(entry.getImageUrl()));
userAvatar.setClip(new Circle(18, 18, 18)); userAvatar.setClip(new Circle(18, 18, 18));
} catch (Exception ignored) { } catch (Exception ignored) {
} }
@@ -339,10 +376,8 @@ public class ChatPageController {
messageContainer.getChildren().clear(); messageContainer.getChildren().clear();
loadMessages(entry); loadMessages(entry);
// مارک به‌عنوان خوانده
markAsRead(entry); markAsRead(entry);
// فوکوس روی ورودی
Platform.runLater(() -> messageInput.requestFocus()); Platform.runLater(() -> messageInput.requestFocus());
} }
@@ -363,7 +398,6 @@ public class ChatPageController {
} }
if (resp == null) return; if (resp == null) return;
// بدون opt* :
String status = ""; String status = "";
try { try {
status = resp.getString("status"); status = resp.getString("status");
@@ -391,49 +425,48 @@ public class ChatPageController {
} }
private void renderMessages(JSONArray arr) { private void renderMessages(org.json.JSONArray arr) {
messageContainer.getChildren().clear(); messageContainer.getChildren().clear();
for (int i = 0; i < arr.length(); i++) {
JSONObject m = arr.getJSONObject(i);
String senderId = m.optString("sender_id", "");
String type = m.optString("message_type", "TEXT");
String content = m.optString("content", "");
boolean outgoing = false; String myId = Session.currentUser != null
if (me != null && senderId != null && !senderId.isEmpty()) { ? Session.currentUser.optString("internal_uuid", "")
try { : "";
outgoing = me.equals(UUID.fromString(senderId));
} catch (Exception ignore) { for (int i = 0; i < arr.length(); i++) {
} org.json.JSONObject m = arr.getJSONObject(i);
}
String senderId = m.has("sender_id") && !m.isNull("sender_id") ? m.getString("sender_id") : "";
String senderName = m.has("sender_name") && !m.isNull("sender_name") ? m.getString("sender_name") : "";
String type = m.has("message_type") && !m.isNull("message_type") ? m.getString("message_type") : "TEXT";
String content = m.has("content") && !m.isNull("content") ? m.getString("content") : "";
String whenStr = m.has("send_at") && !m.isNull("send_at") ? m.getString("send_at") : null;
boolean outgoing = senderId.equalsIgnoreCase(myId);
String display = outgoing ? "You"
: (!senderName.isEmpty() ? senderName
: (senderId.isEmpty() ? "Unknown"
: senderId.substring(0, Math.min(8, senderId.length()))));
String text; String text;
switch (type.toUpperCase()) { switch (type.toUpperCase()) {
case "TEXT": case "IMAGE": text = "[Image]"; break;
text = content; case "AUDIO": text = "[Audio]"; break;
break; case "VIDEO": text = "[Video]"; break;
case "IMAGE": case "FILE": text = "[File]"; break;
text = "[Image]"; default: text = content; break;
break;
case "AUDIO":
text = "[Audio]";
break;
case "VIDEO":
text = "[Video]";
break;
case "FILE":
text = "[File]";
break;
default:
text = "[Message]";
} }
addBubble(outgoing, text);
LocalDateTime ts = parseWhen(whenStr);
addBubble(outgoing, display, text, ts);
} }
messageScrollPane.layout(); messageScrollPane.layout();
messageScrollPane.setVvalue(1.0); messageScrollPane.setVvalue(1.0);
} }
private void markAsRead(ChatEntry entry) { private void markAsRead(ChatEntry entry) {
JSONObject readReq = new JSONObject(); JSONObject readReq = new JSONObject();
readReq.put("action", "mark_as_read"); readReq.put("action", "mark_as_read");
@@ -442,7 +475,12 @@ public class ChatPageController {
ActionHandler.sendWithResponse(readReq); ActionHandler.sendWithResponse(readReq);
} }
private void addBubble(boolean outgoing, String content) { private void addBubble(boolean outgoing, String displayName, String content, LocalDateTime sentAt) {
Label meta = new Label(displayName + "" + formatWhen(sentAt));
meta.setStyle("-fx-font-size: 11; -fx-text-fill: #7e8a97;");
meta.setWrapText(true);
Label msg = new Label(content); Label msg = new Label(content);
msg.setWrapText(true); msg.setWrapText(true);
@@ -459,7 +497,9 @@ public class ChatPageController {
); );
msg.setMinHeight(Region.USE_PREF_SIZE); msg.setMinHeight(Region.USE_PREF_SIZE);
javafx.scene.layout.HBox row = new javafx.scene.layout.HBox(msg); VBox bubble = new VBox(4, meta, msg);
javafx.scene.layout.HBox row = new javafx.scene.layout.HBox(bubble);
row.setFillHeight(true); row.setFillHeight(true);
row.setSpacing(6); row.setSpacing(6);
row.setAlignment(outgoing row.setAlignment(outgoing
@@ -470,4 +510,5 @@ public class ChatPageController {
} }
} }
@@ -48,6 +48,8 @@ public class MainController {
// === Sidebar === // === Sidebar ===
@FXML private Pane overlay; @FXML private Pane overlay;
@FXML private ImageView menuIcon; @FXML private ImageView menuIcon;
private SidebarMenuController sidebarController; //save sidebar controller
// === STATE === // === STATE ===
private static MainController instance; private static MainController instance;
@@ -173,29 +175,32 @@ public class MainController {
// } // }
private final DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm"); private static final java.time.format.DateTimeFormatter FMT_HHMM =
private final DateTimeFormatter dayFmt = DateTimeFormatter.ofPattern("dd/MM"); // همون سال java.time.format.DateTimeFormatter.ofPattern("HH:mm");
private final DateTimeFormatter fullDateFmt= DateTimeFormatter.ofPattern("yyyy/MM/dd"); // سال متفاوت private static final java.time.format.DateTimeFormatter FMT_DATE_TIME =
java.time.format.DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
private static final String YESTERDAY_LABEL = "Yesterday";
private String formatListTimestamp(LocalDateTime ts) { // ...
private String formatChatTime(java.time.LocalDateTime ts) {
if (ts == null) return ""; if (ts == null) return "";
LocalDate today = LocalDate.now(); var today = java.time.LocalDate.now();
LocalDate d = ts.toLocalDate(); var d = ts.toLocalDate();
if (d.isEqual(today)) { if (d.isEqual(today)) {
// امروز → فقط ساعت //today
return timeFmt.format(ts); return FMT_HHMM.format(ts);
//yesterday
} else if (d.isEqual(today.minusDays(1))) {
return YESTERDAY_LABEL;
} else {
return FMT_DATE_TIME.format(ts);
} }
// اگر همان سال است → تاریخ کوتاه + ساعت
if (d.getYear() == today.getYear()) {
return dayFmt.format(ts) + " " + timeFmt.format(ts); // مثال: 15/08 13:28
}
// سال متفاوت → تاریخ کامل + ساعت
return fullDateFmt.format(ts) + " " + timeFmt.format(ts); // مثال: 2024/12/31 21:10
} }
private void populateChatListFromSession() { private void populateChatListFromSession() {
chatListContainer.getChildren().clear(); chatListContainer.getChildren().clear();
@@ -239,9 +244,9 @@ public class MainController {
ChatItemController cc = fx.getController(); ChatItemController cc = fx.getController();
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview(); String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
String time = formatListTimestamp(chat.getLastMessageTime()); String timeText = formatChatTime(chat.getLastMessageTime());
cc.setChatData(chat.getName(), preview, time, chat.getUnreadCount()); cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount());
item.setOnMouseClicked(e -> openChat(chat)); item.setOnMouseClicked(e -> openChat(chat));
chatListContainer.getChildren().add(item); chatListContainer.getChildren().add(item);
@@ -284,11 +289,10 @@ public class MainController {
Node chatPage = loader.load(); Node chatPage = loader.load();
ChatPageController controller = loader.getController(); ChatPageController controller = loader.getController();
controller.showChat(chat); // ✅ متد جدید در ChatPageController controller.showChat(chat);
chatDisplayArea.getChildren().setAll(chatPage); chatDisplayArea.getChildren().setAll(chatPage);
// اختیاری: صفر کردن badge و مارک‌کردن به‌عنوان خوانده در UI
ChatItemController item = itemControllers.get(chat.getId()); ChatItemController item = itemControllers.get(chat.getId());
if (item != null) item.setUnread(0); if (item != null) item.setUnread(0);
@@ -307,30 +311,62 @@ public class MainController {
} }
} }
// @FXML
// private void openSidebar() {
// try {
// if (sidebarRoot == null) {
// sidebarRoot = FXMLLoader.load(getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml"));
// StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT);
// sidebarRoot.setTranslateX(-getSidebarWidth());
// mainRoot.getChildren().add(sidebarRoot);
// }
//
// TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot);
// slideIn.setToX(0);
// slideIn.play();
//
// overlay.setVisible(true);
// overlay.setMouseTransparent(false);
//
// isSidebarOpen = true;
//
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
@FXML @FXML
private void openSidebar() { private void openSidebar() {
try { try {
if (sidebarRoot == null) { if (sidebarRoot == null) {
sidebarRoot = FXMLLoader.load(getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml")); FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/to/telegramfinalproject/Fxml/Sidebar_menu.fxml"));
sidebarRoot = loader.load();
sidebarController = loader.getController();
StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT); StackPane.setAlignment(sidebarRoot, Pos.CENTER_LEFT);
sidebarRoot.setTranslateX(-getSidebarWidth()); sidebarRoot.setTranslateX(-getSidebarWidth());
mainRoot.getChildren().add(sidebarRoot); mainRoot.getChildren().add(sidebarRoot);
} }
if (sidebarController != null && org.to.telegramfinalproject.Client.Session.currentUser != null) {
sidebarController.setUserFromSession(
org.to.telegramfinalproject.Client.Session.currentUser);
}
TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot); TranslateTransition slideIn = new TranslateTransition(Duration.millis(250), sidebarRoot);
slideIn.setToX(0); slideIn.setToX(0);
slideIn.play(); slideIn.play();
overlay.setVisible(true); overlay.setVisible(true);
overlay.setMouseTransparent(false); overlay.setMouseTransparent(false);
isSidebarOpen = true; isSidebarOpen = true;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@FXML @FXML
private void closeSidebar() { private void closeSidebar() {
if (sidebarRoot != null && isSidebarOpen) { if (sidebarRoot != null && isSidebarOpen) {
@@ -12,6 +12,7 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Duration; import javafx.util.Duration;
import org.json.JSONObject;
import java.net.URL; import java.net.URL;
@@ -184,5 +185,35 @@ public class SidebarMenuController {
public void setUserFromSession(JSONObject user) {
if (user == null) return;
// نام نمایشی (اول profile_name بعد username)
String displayName = user.optString("profile_name",
user.optString("username", ""));
usernameLabel.setText(displayName);
// عکس پروفایل: هم URL اینترنتی هم مسیر ریسورس را پشتیبانی کن
String img = user.optString("image_url", "");
Image pic = tryLoadImage(img);
if (pic == null) {
pic = loadImage("/org/to/telegramfinalproject/Avatars/profile.png");
}
if (pic != null) profileImage.setImage(pic);
}
// کمک‌کننده: هم URL و هم ریسورس کلاس‌پث را امتحان می‌کند
private Image tryLoadImage(String src) {
if (src == null || src.isBlank()) return null;
try {
// اگر ریسورس داخل پروژه است (با / شروع شود یا در resources موجود باشد)
var res = getClass().getResource(src);
if (res != null) return new Image(res.toExternalForm(), true);
// در غیر این صورت فرض کن URL است (http/https/file)
return new Image(src, true);
} catch (Exception ignored) {
return null;
}
}
} }