Merge branch 'refs/heads/Main-UI' into Connection-UI

# Conflicts:
#	src/main/java/org/to/telegramfinalproject/UI/ChatItemController.java
#	src/main/java/org/to/telegramfinalproject/UI/MainController.java
This commit is contained in:
2025-08-29 18:02:30 +03:30
8 changed files with 385 additions and 60 deletions
@@ -1,12 +1,13 @@
package org.to.telegramfinalproject.UI; package org.to.telegramfinalproject.UI;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import java.io.IOException; import java.util.Objects;
public class ChatItemController { public class ChatItemController {
@@ -15,13 +16,74 @@ public class ChatItemController {
@FXML private Label lastMessage; @FXML private Label lastMessage;
@FXML private Label chatTime; @FXML private Label chatTime;
@FXML private Label unreadCount; @FXML private Label unreadCount;
@FXML private StackPane systemAvatar;
@FXML private ImageView profileImageUser;
@FXML private ImageView profileImageSystem;
@FXML private Circle systemCircle;
public void setChatData(String name, String lastMsg, String time, int unread) { // Default avatar resource path
private static final String DEFAULT_AVATAR =
"/org/to/telegramfinalproject/Avatars/default_avatar.png";
/**
* Set chat item data, including a profile image (or default).
*
* @param name Chat name
* @param lastMsg Last message text
* @param time Last message time
* @param unread Unread message count
* @param imageUrl Path/URL of profile image (can be null/empty)
*/
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl) {
chatName.setText(name); chatName.setText(name);
lastMessage.setText(lastMsg); lastMessage.setText(lastMsg);
chatTime.setText(time); chatTime.setText(time);
unreadCount.setVisible(unread > 0);
// Unread count
if (unread > 0) {
unreadCount.setVisible(true);
unreadCount.setText(String.valueOf(unread)); unreadCount.setText(String.valueOf(unread));
} else {
unreadCount.setVisible(false);
}
// Reset all avatar states
profileImageUser.setVisible(false);
profileImageUser.setManaged(false);
systemAvatar.setVisible(false);
systemAvatar.setManaged(false);
if ("Saved Messages".equals(name)) {
systemCircle.setFill(javafx.scene.paint.Color.web("#2ca4ff")); // Telegram blue
profileImageSystem.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Icons/saved_messages_light.png"))
));
systemAvatar.setVisible(true);
systemAvatar.setManaged(true);
} else if ("Archived Chats".equals(name)) {
systemCircle.setFill(javafx.scene.paint.Color.web("#808080")); // gray
profileImageSystem.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Icons/archived_chats_light.png"))
));
systemAvatar.setVisible(true);
systemAvatar.setManaged(true);
} else if (imageUrl != null && !imageUrl.isEmpty()) {
profileImageUser.setImage(new Image(imageUrl, true));
profileImageUser.setVisible(true);
profileImageUser.setManaged(true);
} else {
profileImageUser.setImage(new Image(
Objects.requireNonNull(getClass().getResourceAsStream(
"/org/to/telegramfinalproject/Icons/default_profile.png"))
));
profileImageUser.setVisible(true);
profileImageUser.setManaged(true);
}
} }
public void setUnread(int unread) { public void setUnread(int unread) {
@@ -245,6 +245,10 @@ public class MainController {
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview(); String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
String timeText = formatChatTime(chat.getLastMessageTime()); String timeText = formatChatTime(chat.getLastMessageTime());
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
Node chatItem = loader.load();
ChatItemController controller = loader.getController();
controller.setChatData(name, lastMsg, time, unread, "/org/to/telegramfinalproject/Avatars/default_profile.png");
cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount()); cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount());
item.setOnMouseClicked(e -> openChat(chat)); item.setOnMouseClicked(e -> openChat(chat));
@@ -48,7 +48,7 @@ public class SidebarMenuController {
@FXML @FXML
public void initialize() { public void initialize() {
// Load default profile image // Load default profile image
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/profile.png"); Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_profile.png");
if (profile != null) profileImage.setImage(profile); if (profile != null) profileImage.setImage(profile);
setupButtonActions(); setupButtonActions();

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

@@ -124,17 +124,17 @@
/* Input bubble */ /* Input bubble */
.msg-input { .msg-input {
-fx-background-color: white; -fx-background-color: #1b2b39; /* dark bluish background */
-fx-control-inner-background: #1b2b39;
-fx-text-fill: #e8f1f8;
-fx-prompt-text-fill: #8ea1b2;
-fx-background-radius: 18; -fx-background-radius: 18;
-fx-padding: 6 12 6 12; -fx-padding: 6 12 6 12;
-fx-text-fill: #0f141a;
-fx-prompt-text-fill: #9aa6b2;
-fx-border-color: transparent; -fx-border-color: transparent;
-fx-control-inner-background: white;
-fx-focus-color: transparent; -fx-focus-color: transparent;
-fx-faint-focus-color: transparent; -fx-faint-focus-color: transparent;
} }
.msg-input .scroll-pane { .msg-input .scroll-pane {
-fx-vbar-policy: never; -fx-vbar-policy: never;
-fx-hbar-policy: never; -fx-hbar-policy: never;
@@ -144,6 +144,7 @@
-fx-background-color: transparent; -fx-background-color: transparent;
} }
/* Context menu */
.context-menu { .context-menu {
-fx-background-color: #1b2735; -fx-background-color: #1b2735;
-fx-background-radius: 6; -fx-background-radius: 6;
@@ -162,17 +163,134 @@
-fx-background-radius: 4; -fx-background-radius: 4;
} }
/* ===== Search panel ===== */
.chat-search-pane { /* ===== Search panel (dark) ===== */
-fx-background-color: #1b2735; /* matches sidebar */ .chat-search-pane { -fx-background-color: #1b2735; }
.chat-search-bar { -fx-background-color: transparent; -fx-padding: 8 10; }
.search-placeholder { -fx-text-fill: #8ea1b2; -fx-font-size: 13px; }
/* ===== Global search bar ===== */
.global-search {
-fx-background-color: #1b2b39;
-fx-background-radius: 15;
-fx-text-fill: #e8f1f8;
-fx-prompt-text-fill: #8ea1b2;
} }
.chat-search-bar { /* ===== Chat list ===== */
.chat-list { -fx-background-color: #0f1c26; }
.chat-list .label { -fx-text-fill: #e8f1f8; }
/* ===== Sidebar / Left Pane ===== */
.sidebar-pane {
-fx-background-color: #1b2735; /* Telegram dark */
}
.sidebar-topbar {
-fx-background-color: transparent; -fx-background-color: transparent;
-fx-padding: 8 10;
} }
.search-placeholder { .menu-btn {
-fx-background-color: transparent;
-fx-cursor: hand;
}
.global-search {
-fx-background-color: #2a3a4a;
-fx-background-radius: 15;
-fx-text-fill: #e8f1f8;
-fx-prompt-text-fill: #8ea1b2;
-fx-border-color: transparent;
}
/* Chat list */
.chat-list {
-fx-background-color: #0f1c26;
}
.chat-scroll {
-fx-background-color: transparent;
}
/* ===== Search Panel ===== */
.chat-search-pane {
-fx-background-color: #1b2735;
}
.search-label {
-fx-text-fill: #8ea1b2; -fx-text-fill: #8ea1b2;
-fx-font-size: 13px; -fx-font-size: 11px;
-fx-padding: 10 0 5 10;
}
.search-separator {
-fx-background-color: #2e3948;
}
.search-scope-bar {
-fx-padding: 8 10;
-fx-border-color: #2e3948;
-fx-border-width: 0 0 1 0;
}
.search-scope {
-fx-text-fill: #8ea1b2;
-fx-font-size: 11px;
}
.close-btn {
-fx-background-color: transparent;
-fx-cursor: hand;
-fx-text-fill: #e8f1f8;
}
/* ===== Right Chat Panel ===== */
.chat-display {
-fx-background-color: #0f1c26;
}
/* ===== Overlay ===== */
.overlay {
-fx-background-color: rgba(0,0,0,0.3);
}
/* Chat item container */
.chat-item {
-fx-background-color: #0f1c26;
-fx-padding: 10;
-fx-border-color: #2e3948;
-fx-border-width: 0 0 1 0;
}
.chat-item:hover {
-fx-background-color: #223446;
-fx-cursor: hand;
}
/* Chat details */
.chat-name {
-fx-font-size: 14;
-fx-font-weight: bold;
-fx-text-fill: #e8f1f8;
}
.chat-last-message {
-fx-text-fill: #8ea1b2;
-fx-font-size: 12;
}
/* Time + unread */
.chat-time {
-fx-font-size: 11;
-fx-text-fill: #8ea1b2;
}
/* Unread counter (same for both modes) */
.unread-count {
-fx-background-color: #2196f3; /* blue bubble */
-fx-text-fill: white !important; /* always white */
-fx-font-size: 10px;
-fx-padding: 3 6;
-fx-background-radius: 10;
-fx-font-weight: bold;
} }
@@ -51,12 +51,12 @@
-fx-text-fill: black; -fx-text-fill: black;
} }
/* Thin Telegram-like SplitPane divider (light) */ /* SplitPane divider */
.split-pane:horizontal > .split-pane-divider { .split-pane:horizontal > .split-pane-divider {
-fx-background-color: transparent; /* no big solid block */ -fx-background-color: transparent;
-fx-border-color: #cccccc; /* the visible thin line */ -fx-border-color: #cccccc;
-fx-border-width: 0 1px 0 1px; /* 1px vertical line */ -fx-border-width: 0 1px 0 1px;
-fx-padding: 0 0 0 0.5px; /* big invisible hitbox for the mouse */ -fx-padding: 0 0 0 0.5px;
} }
.placeholder-label { .placeholder-label {
@@ -144,6 +144,7 @@
-fx-background-color: transparent; -fx-background-color: transparent;
} }
/* Context menu */
.context-menu { .context-menu {
-fx-background-color: white; -fx-background-color: white;
-fx-background-radius: 6; -fx-background-radius: 6;
@@ -162,17 +163,133 @@
-fx-background-radius: 4; -fx-background-radius: 4;
} }
/* ===== Search panel ===== */
/* ===== Search panel (light) ===== */
.chat-search-pane { -fx-background-color: #f5f5f5; }
.chat-search-bar { -fx-background-color: transparent; -fx-padding: 8 10; }
.search-placeholder { -fx-text-fill: #7e8a97; -fx-font-size: 13px; }
/* ===== Global search bar ===== */
.global-search {
-fx-background-color: #f5f5f5;
-fx-background-radius: 15;
-fx-text-fill: #0f141a;
-fx-prompt-text-fill: #9aa6b2;
}
/* ===== Chat list ===== */
.chat-list { -fx-background-color: #ffffff; }
.chat-list .label { -fx-text-fill: #0f141a; }
/* ===== Sidebar / Left Pane ===== */
.sidebar-pane {
-fx-background-color: #ffffff;
}
.sidebar-topbar {
-fx-background-color: transparent;
}
.menu-btn {
-fx-background-color: transparent;
-fx-cursor: hand;
}
.global-search {
-fx-background-color: #f5f5f5;
-fx-background-radius: 15;
-fx-text-fill: #0f141a;
-fx-prompt-text-fill: #7e8a97;
-fx-border-color: transparent;
}
/* Chat list */
.chat-list {
-fx-background-color: transparent;
}
.chat-scroll {
-fx-background-color: transparent;
}
/* ===== Search Panel ===== */
.chat-search-pane { .chat-search-pane {
-fx-background-color: #f5f5f5; -fx-background-color: #f5f5f5;
} }
.chat-search-bar { .search-label {
-fx-background-color: transparent; -fx-text-fill: #7e8a97;
-fx-padding: 8 10; -fx-font-size: 11px;
-fx-padding: 10 0 5 10;
} }
.search-placeholder { .search-separator {
-fx-background-color: #e6e6e6;
}
.search-scope-bar {
-fx-padding: 8 10;
-fx-border-color: #e6e6e6;
-fx-border-width: 0 0 1 0;
}
.search-scope {
-fx-text-fill: #7e8a97; -fx-text-fill: #7e8a97;
-fx-font-size: 13px; -fx-font-size: 11px;
}
.close-btn {
-fx-background-color: transparent;
-fx-cursor: hand;
}
/* ===== Right Chat Panel ===== */
.chat-display {
-fx-background-color: #fafafa;
}
/* ===== Overlay ===== */
.overlay {
-fx-background-color: rgba(0,0,0,0.3);
}
/* Chat item container */
.chat-item {
-fx-background-color: #ffffff;
-fx-padding: 10;
-fx-border-color: #ddd;
-fx-border-width: 0 0 1 0;
}
.chat-item:hover {
-fx-background-color: #f5f5f5;
-fx-cursor: hand;
}
/* Chat details */
.chat-name {
-fx-font-size: 14;
-fx-font-weight: bold;
-fx-text-fill: #0f141a;
}
.chat-last-message {
-fx-text-fill: #7e8a97;
-fx-font-size: 12;
}
/* Time + unread */
.chat-time {
-fx-font-size: 11;
-fx-text-fill: #7e8a97;
}
/* Unread counter (same for both modes) */
.unread-count {
-fx-background-color: #2196f3; /* blue bubble */
-fx-text-fill: white !important; /* always white */
-fx-font-size: 10px;
-fx-padding: 3 6;
-fx-background-radius: 10;
-fx-font-weight: bold;
} }
@@ -5,26 +5,50 @@
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml" <HBox xmlns:fx="http://javafx.com/fxml"
fx:controller="org.to.telegramfinalproject.UI.ChatItemController" fx:controller="org.to.telegramfinalproject.UI.ChatItemController"
spacing="10" alignment="CENTER_LEFT" spacing="10" alignment="CENTER_LEFT"
style="-fx-padding: 10; -fx-background-color: white; -fx-border-color: #ddd; -fx-border-width: 0 0 1 0;"> styleClass="chat-item">
<!-- Profile Image --> <!-- Avatar container with fixed size -->
<ImageView fx:id="profileImage" fitWidth="40" fitHeight="40" preserveRatio="true" <StackPane fx:id="avatarContainer"
style="-fx-background-radius: 50; -fx-border-radius: 50;"/> prefWidth="40" prefHeight="40"
minWidth="40" minHeight="40"
maxWidth="40" maxHeight="40"
alignment="CENTER">
<!-- User profile image -->
<ImageView fx:id="profileImageUser"
fitWidth="60" fitHeight="60"
preserveRatio="true"
visible="true" managed="true"/>
<!-- System avatar (circle + icon) -->
<StackPane fx:id="systemAvatar"
prefWidth="40" prefHeight="40"
visible="false" managed="false"
alignment="CENTER">
<Circle fx:id="systemCircle"
radius="20"
fill="gray"/>
<ImageView fx:id="profileImageSystem"
fitWidth="28" fitHeight="28"
preserveRatio="true"/>
</StackPane>
</StackPane>
<!-- Chat Details --> <!-- Chat Details -->
<VBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> <VBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS">
<Label fx:id="chatName" style="-fx-font-size: 14; -fx-font-weight: bold;"/> <Label fx:id="chatName" styleClass="chat-name"/>
<Label fx:id="lastMessage" style="-fx-text-fill: gray; -fx-font-size: 12;"/> <Label fx:id="lastMessage" styleClass="chat-last-message"/>
</VBox> </VBox>
<!-- Time + Unread --> <!-- Time + Unread -->
<VBox alignment="CENTER_RIGHT" spacing="5"> <VBox alignment="CENTER_RIGHT" spacing="5">
<Label fx:id="chatTime" style="-fx-font-size: 11; -fx-text-fill: gray;"/> <Label fx:id="chatTime" styleClass="chat-time"/>
<Label fx:id="unreadCount" <Label fx:id="unreadCount" styleClass="unread-count"/>
style="-fx-background-color: #2196f3; -fx-text-fill: white; -fx-font-size: 10;
-fx-padding: 3 6; -fx-background-radius: 10;"/>
</VBox> </VBox>
</HBox> </HBox>
@@ -21,18 +21,19 @@
<items> <items>
<!-- LEFT: Sidebar --> <!-- LEFT: Sidebar -->
<VBox fx:id="leftPane" minWidth="72" style="-fx-background-color: #FFFFFF;"> <VBox fx:id="leftPane" minWidth="72" styleClass="sidebar-pane">
<!-- Top bar (always visible) --> <!-- Top bar (always visible) -->
<HBox spacing="10"> <HBox spacing="10" styleClass="search-bar-container">
<padding> <padding>
<Insets bottom="10" left="10" right="10" top="10"/> <Insets bottom="10" left="10" right="10" top="10"/>
</padding> </padding>
<children> <children>
<Button fx:id="menuButton" minHeight="29.0" minWidth="40" <Button fx:id="menuButton"
minHeight="29.0" minWidth="40"
onAction="#toggleSidebar" onAction="#toggleSidebar"
prefHeight="29.0" prefWidth="40.0" prefHeight="29.0" prefWidth="40.0"
style="-fx-background-color: transparent;"> styleClass="menu-btn">
<graphic> <graphic>
<ImageView fx:id="menuIcon" fitHeight="24" fitWidth="17" <ImageView fx:id="menuIcon" fitHeight="24" fitWidth="17"
preserveRatio="true" smooth="true"> preserveRatio="true" smooth="true">
@@ -46,7 +47,7 @@
<TextField fx:id="searchBar" <TextField fx:id="searchBar"
prefHeight="36" prefWidth="430.0" prefHeight="36" prefWidth="430.0"
promptText="Search" promptText="Search"
style="-fx-background-color: #f5f5f5; -fx-background-radius: 15;"/> styleClass="global-search"/>
</children> </children>
</HBox> </HBox>
@@ -55,12 +56,12 @@
<!-- Chat list (default) --> <!-- Chat list (default) -->
<ScrollPane fx:id="scrollPane" fitToWidth="true" hbarPolicy="NEVER" <ScrollPane fx:id="scrollPane" fitToWidth="true" hbarPolicy="NEVER"
style="-fx-background-color: transparent;"
vbarPolicy="AS_NEEDED" vbarPolicy="AS_NEEDED"
visible="true" managed="true"> visible="true" managed="true"
styleClass="chat-scroll">
<content> <content>
<VBox fx:id="chatListContainer" spacing="5" <VBox fx:id="chatListContainer" spacing="5"
style="-fx-background-color: transparent;"> styleClass="chat-list">
<padding> <padding>
<Insets bottom="10" left="10" right="10" top="10"/> <Insets bottom="10" left="10" right="10" top="10"/>
</padding> </padding>
@@ -69,18 +70,18 @@
</ScrollPane> </ScrollPane>
<!-- Search panel (hidden initially) --> <!-- Search panel (hidden initially) -->
<VBox fx:id="chatSearchPane" visible="false" managed="false" <VBox fx:id="chatSearchPane"
visible="false" managed="false"
styleClass="chat-search-pane"> styleClass="chat-search-pane">
<!-- Section label --> <!-- Section label -->
<Label text="Search messages in" <Label text="Search messages in" styleClass="search-label"/>
style="-fx-text-fill: #7e8a97; -fx-font-size: 11px; -fx-padding: 10 0 5 10;"/>
<!-- Tiny separator line --> <!-- Tiny separator line -->
<Separator style="-fx-background-color: #e6e6e6;" prefHeight="0.5"/> <Separator styleClass="search-separator" prefHeight="0.5"/>
<!-- User profile + "This chat" + close --> <!-- User profile + "This chat" + close -->
<HBox spacing="10" alignment="CENTER_LEFT" style="-fx-padding: 8 10; -fx-border-color: #e6e6e6; -fx-border-width: 0 0 1 0;"> <HBox spacing="10" alignment="CENTER_LEFT" styleClass="search-scope-bar">
<!-- Avatar --> <!-- Avatar -->
<ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true"> <ImageView fx:id="searchChatAvatar" fitWidth="32" fitHeight="32" preserveRatio="true">
<image> <image>
@@ -90,12 +91,11 @@
<!-- scope --> <!-- scope -->
<VBox alignment="CENTER_LEFT"> <VBox alignment="CENTER_LEFT">
<Label text="This chat" style="-fx-text-fill: #7e8a97; -fx-font-size: 11px;"/> <Label text="This chat" styleClass="search-scope"/>
</VBox> </VBox>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<!-- Close button --> <!-- Close button -->
<Button text="✕" onAction="#closeSearchPanel" <Button text="✕" onAction="#closeSearchPanel" styleClass="close-btn"/>
style="-fx-background-color: transparent; -fx-cursor: hand;"/>
</HBox> </HBox>
<!-- Placeholder --> <!-- Placeholder -->
@@ -111,7 +111,7 @@
</VBox> </VBox>
<!-- RIGHT: Chat panel --> <!-- RIGHT: Chat panel -->
<StackPane fx:id="chatDisplayArea" minWidth="360" style="-fx-background-color: #FAFAFA;"> <StackPane fx:id="chatDisplayArea" minWidth="360" styleClass="chat-display">
<children> <children>
<Label fx:id="placeholderLabel" alignment="CENTER" <Label fx:id="placeholderLabel" alignment="CENTER"
prefHeight="21.0" prefWidth="248.0" prefHeight="21.0" prefWidth="248.0"
@@ -128,7 +128,7 @@
<Pane fx:id="overlay" <Pane fx:id="overlay"
mouseTransparent="true" mouseTransparent="true"
onMouseClicked="#closeSidebar" onMouseClicked="#closeSidebar"
style="-fx-background-color: rgba(0,0,0,0.3);" styleClass="overlay"
visible="false"/> visible="false"/>
</children> </children>
</StackPane> </StackPane>