Time Formatter
This commit is contained in:
@@ -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,14 +475,19 @@ 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);
|
||||||
|
|
||||||
boolean dark = themeManager.isDarkMode();
|
boolean dark = themeManager.isDarkMode();
|
||||||
String mine = dark ? "#2b7cff" : "#d8ecff";
|
String mine = dark ? "#2b7cff" : "#d8ecff";
|
||||||
String theirs = dark ? "#2c333a" : "#ffffff";
|
String theirs = dark ? "#2c333a" : "#ffffff";
|
||||||
String bg = outgoing ? mine : theirs;
|
String bg = outgoing ? mine : theirs;
|
||||||
|
|
||||||
msg.setStyle(
|
msg.setStyle(
|
||||||
"-fx-background-color:" + bg + ";" +
|
"-fx-background-color:" + bg + ";" +
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user