Time Formatter
This commit is contained in:
@@ -13,11 +13,16 @@ import javafx.stage.FileChooser;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
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;
|
||||
|
||||
public class ChatPageController {
|
||||
@@ -78,15 +83,47 @@ public class ChatPageController {
|
||||
|
||||
private void initCurrentUserId() {
|
||||
try {
|
||||
// از سشنی که سمت کلاینت داری:
|
||||
String meStr = org.to.telegramfinalproject.Client.Session
|
||||
.currentUser.getString("internal_uuid");
|
||||
me = UUID.fromString(meStr);
|
||||
} 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
|
||||
public void initialize() {
|
||||
@@ -327,10 +364,10 @@ public class ChatPageController {
|
||||
|
||||
// Header
|
||||
chatTitle.setText(entry.getName());
|
||||
chatStatus.setText(""); // اگر last seen داری اینجا بگذار
|
||||
chatStatus.setText("");
|
||||
if (entry.getImageUrl() != null && !entry.getImageUrl().isEmpty()) {
|
||||
try {
|
||||
userAvatar.setImage(new Image(entry.getImageUrl())); // یا لود از ریسورس خودت
|
||||
userAvatar.setImage(new Image(entry.getImageUrl()));
|
||||
userAvatar.setClip(new Circle(18, 18, 18));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
@@ -339,10 +376,8 @@ public class ChatPageController {
|
||||
messageContainer.getChildren().clear();
|
||||
loadMessages(entry);
|
||||
|
||||
// مارک بهعنوان خوانده
|
||||
markAsRead(entry);
|
||||
|
||||
// فوکوس روی ورودی
|
||||
Platform.runLater(() -> messageInput.requestFocus());
|
||||
}
|
||||
|
||||
@@ -363,7 +398,6 @@ public class ChatPageController {
|
||||
}
|
||||
if (resp == null) return;
|
||||
|
||||
// بدون opt* :
|
||||
String status = "";
|
||||
try {
|
||||
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();
|
||||
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;
|
||||
if (me != null && senderId != null && !senderId.isEmpty()) {
|
||||
try {
|
||||
outgoing = me.equals(UUID.fromString(senderId));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
String myId = Session.currentUser != null
|
||||
? Session.currentUser.optString("internal_uuid", "")
|
||||
: "";
|
||||
|
||||
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;
|
||||
switch (type.toUpperCase()) {
|
||||
case "TEXT":
|
||||
text = content;
|
||||
break;
|
||||
case "IMAGE":
|
||||
text = "[Image]";
|
||||
break;
|
||||
case "AUDIO":
|
||||
text = "[Audio]";
|
||||
break;
|
||||
case "VIDEO":
|
||||
text = "[Video]";
|
||||
break;
|
||||
case "FILE":
|
||||
text = "[File]";
|
||||
break;
|
||||
default:
|
||||
text = "[Message]";
|
||||
case "IMAGE": text = "[Image]"; break;
|
||||
case "AUDIO": text = "[Audio]"; break;
|
||||
case "VIDEO": text = "[Video]"; break;
|
||||
case "FILE": text = "[File]"; break;
|
||||
default: text = content; break;
|
||||
}
|
||||
addBubble(outgoing, text);
|
||||
|
||||
LocalDateTime ts = parseWhen(whenStr);
|
||||
addBubble(outgoing, display, text, ts);
|
||||
}
|
||||
|
||||
messageScrollPane.layout();
|
||||
messageScrollPane.setVvalue(1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void markAsRead(ChatEntry entry) {
|
||||
JSONObject readReq = new JSONObject();
|
||||
readReq.put("action", "mark_as_read");
|
||||
@@ -442,14 +475,19 @@ public class ChatPageController {
|
||||
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);
|
||||
msg.setWrapText(true);
|
||||
|
||||
boolean dark = themeManager.isDarkMode();
|
||||
String mine = dark ? "#2b7cff" : "#d8ecff";
|
||||
String mine = dark ? "#2b7cff" : "#d8ecff";
|
||||
String theirs = dark ? "#2c333a" : "#ffffff";
|
||||
String bg = outgoing ? mine : theirs;
|
||||
String bg = outgoing ? mine : theirs;
|
||||
|
||||
msg.setStyle(
|
||||
"-fx-background-color:" + bg + ";" +
|
||||
@@ -459,7 +497,9 @@ public class ChatPageController {
|
||||
);
|
||||
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.setSpacing(6);
|
||||
row.setAlignment(outgoing
|
||||
@@ -470,4 +510,5 @@ public class ChatPageController {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ public class MainController {
|
||||
// === Sidebar ===
|
||||
@FXML private Pane overlay;
|
||||
@FXML private ImageView menuIcon;
|
||||
private SidebarMenuController sidebarController; //save sidebar controller
|
||||
|
||||
|
||||
// === STATE ===
|
||||
private static MainController instance;
|
||||
@@ -173,29 +175,32 @@ public class MainController {
|
||||
// }
|
||||
|
||||
|
||||
private final DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm");
|
||||
private final DateTimeFormatter dayFmt = DateTimeFormatter.ofPattern("dd/MM"); // همون سال
|
||||
private final DateTimeFormatter fullDateFmt= DateTimeFormatter.ofPattern("yyyy/MM/dd"); // سال متفاوت
|
||||
private static final java.time.format.DateTimeFormatter FMT_HHMM =
|
||||
java.time.format.DateTimeFormatter.ofPattern("HH:mm");
|
||||
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 "";
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate d = ts.toLocalDate();
|
||||
var today = java.time.LocalDate.now();
|
||||
var d = ts.toLocalDate();
|
||||
|
||||
if (d.isEqual(today)) {
|
||||
// امروز → فقط ساعت
|
||||
return timeFmt.format(ts);
|
||||
//today
|
||||
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() {
|
||||
chatListContainer.getChildren().clear();
|
||||
|
||||
@@ -239,9 +244,9 @@ public class MainController {
|
||||
ChatItemController cc = fx.getController();
|
||||
|
||||
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));
|
||||
chatListContainer.getChildren().add(item);
|
||||
|
||||
@@ -284,11 +289,10 @@ public class MainController {
|
||||
Node chatPage = loader.load();
|
||||
|
||||
ChatPageController controller = loader.getController();
|
||||
controller.showChat(chat); // ✅ متد جدید در ChatPageController
|
||||
controller.showChat(chat);
|
||||
|
||||
chatDisplayArea.getChildren().setAll(chatPage);
|
||||
|
||||
// اختیاری: صفر کردن badge و مارککردن بهعنوان خوانده در UI
|
||||
ChatItemController item = itemControllers.get(chat.getId());
|
||||
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
|
||||
private void openSidebar() {
|
||||
try {
|
||||
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);
|
||||
sidebarRoot.setTranslateX(-getSidebarWidth());
|
||||
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);
|
||||
slideIn.setToX(0);
|
||||
slideIn.play();
|
||||
|
||||
overlay.setVisible(true);
|
||||
overlay.setMouseTransparent(false);
|
||||
|
||||
isSidebarOpen = true;
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void closeSidebar() {
|
||||
if (sidebarRoot != null && isSidebarOpen) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user