Merge branch 'Connection-UI' into Main-UI
# Conflicts: # src/main/java/org/to/telegramfinalproject/UI/MainController.java # src/main/resources/org/to/telegramfinalproject/CSS/dark_theme.css # src/main/resources/org/to/telegramfinalproject/CSS/light_theme.css
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// org.to.telegramfinalproject.UI.AppRouter
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public final class AppRouter {
|
||||
private static Stage stage;
|
||||
private static Scene scene;
|
||||
|
||||
private AppRouter() {}
|
||||
|
||||
public static void init(Stage st, Scene sc) {
|
||||
stage = st;
|
||||
scene = sc;
|
||||
}
|
||||
|
||||
public static void showIntro() { setRoot("/org/to/telegramfinalproject/Fxml/intro.fxml"); }
|
||||
public static void showLogin() { setRoot("/org/to/telegramfinalproject/Fxml/login_view.fxml"); }
|
||||
public static void showRegister(){ setRoot("/org/to/telegramfinalproject/Fxml/register_view.fxml"); }
|
||||
public static void showMain() { setRoot("/org/to/telegramfinalproject/Fxml/main.fxml"); }
|
||||
|
||||
private static void setRoot(String fxmlPath) {
|
||||
try {
|
||||
System.out.println("Router: setRoot -> " + fxmlPath);
|
||||
FXMLLoader fx = new FXMLLoader(AppRouter.class.getResource(fxmlPath));
|
||||
Parent root = fx.load();
|
||||
if (scene != null) scene.setRoot(root);
|
||||
else if (stage != null) stage.setScene(new Scene(root, 1480, 820));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
||||
public final class AvatarFX {
|
||||
|
||||
public static void circleClip(ImageView iv, double sizePx) {
|
||||
iv.setFitWidth(sizePx);
|
||||
iv.setFitHeight(sizePx);
|
||||
iv.setPreserveRatio(true);
|
||||
iv.setSmooth(true);
|
||||
|
||||
Circle c = new Circle();
|
||||
c.radiusProperty().bind(Bindings.min(iv.fitWidthProperty(), iv.fitHeightProperty()).divide(2));
|
||||
c.centerXProperty().bind(iv.fitWidthProperty().divide(2));
|
||||
c.centerYProperty().bind(iv.fitHeightProperty().divide(2));
|
||||
iv.setClip(c);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -21,10 +22,6 @@ public class ChatItemController {
|
||||
@FXML private ImageView profileImageSystem;
|
||||
@FXML private Circle systemCircle;
|
||||
|
||||
// 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).
|
||||
*
|
||||
@@ -34,7 +31,7 @@ public class ChatItemController {
|
||||
* @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) {
|
||||
public void setChatData(String name, String lastMsg, String time, int unread, String imageUrl, String chatType) {
|
||||
chatName.setText(name);
|
||||
lastMessage.setText(lastMsg);
|
||||
chatTime.setText(time);
|
||||
@@ -72,17 +69,52 @@ public class ChatItemController {
|
||||
systemAvatar.setManaged(true);
|
||||
|
||||
} else if (imageUrl != null && !imageUrl.isEmpty()) {
|
||||
profileImageUser.setImage(new Image(imageUrl, true));
|
||||
// profileImageUser.setImage(new Image(imageUrl, true));
|
||||
// profileImageUser.setVisible(true);
|
||||
// profileImageUser.setManaged(true);
|
||||
|
||||
Image img = AvatarLocalResolver.load(imageUrl);
|
||||
if (img != null) {
|
||||
profileImageUser.setImage(img);
|
||||
AvatarFX.circleClip(profileImageUser, 40);
|
||||
} else {
|
||||
String path;
|
||||
if ("group".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||
} else if ("channel".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||
} else {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
profileImageUser.setImage(new Image(
|
||||
java.util.Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||
));
|
||||
}
|
||||
profileImageUser.setVisible(true);
|
||||
profileImageUser.setManaged(true);
|
||||
|
||||
} else {
|
||||
String path;
|
||||
if ("group".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_group_profile.png";
|
||||
} else if ("channel".equalsIgnoreCase(chatType)) {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_channel_profile.png";
|
||||
} else {
|
||||
path = "/org/to/telegramfinalproject/Avatars/default_user_profile.png";
|
||||
}
|
||||
|
||||
profileImageUser.setImage(new Image(
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Icons/default_profile.png"))
|
||||
Objects.requireNonNull(getClass().getResourceAsStream(path))
|
||||
));
|
||||
profileImageUser.setVisible(true);
|
||||
profileImageUser.setManaged(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnread(int unread) {
|
||||
boolean show = unread > 0;
|
||||
unreadCount.setVisible(show);
|
||||
unreadCount.setManaged(show);
|
||||
if (show) unreadCount.setText(String.valueOf(unread));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -118,25 +118,34 @@ public class IntroController {
|
||||
slider.setDaemon(true);
|
||||
slider.start();
|
||||
}
|
||||
//
|
||||
// @FXML
|
||||
// private void handleStartMessaging(ActionEvent event) {
|
||||
// try {
|
||||
// FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/login_view.fxml"));
|
||||
// Scene loginScene = new Scene(loader.load());
|
||||
//
|
||||
// // Get the current stage and its dimensions
|
||||
// Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
|
||||
// double currentWidth = stage.getWidth();
|
||||
// double currentHeight = stage.getHeight();
|
||||
//
|
||||
// // Set the new scene and apply the previous size
|
||||
// stage.setScene(loginScene);
|
||||
// stage.setWidth(currentWidth);
|
||||
// stage.setHeight(currentHeight);
|
||||
// stage.show();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
@FXML
|
||||
private void handleStartMessaging(ActionEvent event) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/login_view.fxml"));
|
||||
Scene loginScene = new Scene(loader.load());
|
||||
|
||||
// Get the current stage and its dimensions
|
||||
Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
|
||||
double currentWidth = stage.getWidth();
|
||||
double currentHeight = stage.getHeight();
|
||||
|
||||
// Set the new scene and apply the previous size
|
||||
stage.setScene(loginScene);
|
||||
stage.setWidth(currentWidth);
|
||||
stage.setHeight(currentHeight);
|
||||
stage.show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
AppRouter.showLogin(); // همون Scene میمونه، فقط Root عوض میشه
|
||||
}
|
||||
|
||||
@FXML private void goRegister() { AppRouter.showRegister(); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,13 +9,18 @@ import javafx.scene.control.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Client.TelegramClient;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.to.telegramfinalproject.UI.AppRouter.showRegister;
|
||||
|
||||
public class LoginController {
|
||||
|
||||
@FXML private TextField usernameField;
|
||||
@@ -23,7 +28,7 @@ public class LoginController {
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private TextField visiblePasswordField;
|
||||
|
||||
@FXML private Button togglePasswordBtn;
|
||||
@FXML private Button toggleVisibilityBtn;
|
||||
@FXML private Label errorLabel;
|
||||
|
||||
private ClientConnection connection;
|
||||
@@ -48,63 +53,131 @@ public class LoginController {
|
||||
visiblePasswordField.setManaged(passwordVisible);
|
||||
passwordField.setVisible(!passwordVisible);
|
||||
passwordField.setManaged(!passwordVisible);
|
||||
togglePasswordBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
toggleVisibilityBtn.setText(passwordVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
// @FXML
|
||||
// private void handleLogin() {
|
||||
// String username = usernameField.getText();
|
||||
// String password = passwordField.getText();
|
||||
//
|
||||
// // 1. Check for empty fields
|
||||
// if (username.isEmpty() || password.isEmpty()) {
|
||||
// showError("Please fill in all required fields.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. Check if username exists
|
||||
// userDatabase userDb = new userDatabase();
|
||||
// if (!userDb.existsByUsername(username)) {
|
||||
// showError("This username doesn’t exist.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 3. Check if password is correct
|
||||
// User user = userDb.findByUsername(username);
|
||||
// if (!PasswordHashing.verify(password, user.getPassword())) {
|
||||
// showError("Incorrect password.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 4. Attempt to send to server
|
||||
// try {
|
||||
// JSONObject request = new JSONObject();
|
||||
// request.put("action", "login");
|
||||
// request.put("user_id", JSONObject.NULL);
|
||||
// request.put("username", username);
|
||||
// request.put("password", password);
|
||||
// request.put("profile_name", JSONObject.NULL);
|
||||
//
|
||||
// if (connection != null) {
|
||||
// connection.send(request.toString());
|
||||
// String responseStr = connection.receive();
|
||||
// JSONObject response = new JSONObject(responseStr);
|
||||
// System.out.println("Status: " + response.getString("status"));
|
||||
// System.out.println("Message: " + response.getString("message"));
|
||||
//
|
||||
// // Simulate successful login (since main.fxml isn’t ready)
|
||||
// Alert alert = new Alert(Alert.AlertType.INFORMATION, "Login successful!");
|
||||
// alert.show();
|
||||
// }
|
||||
//
|
||||
// } catch (Exception ex) {
|
||||
// showError("Unable to connect to server. Please try again later.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleLogin() {
|
||||
String username = usernameField.getText();
|
||||
String password = passwordField.getText();
|
||||
|
||||
String u = usernameField.getText().trim();
|
||||
String p = passwordField.getText();
|
||||
// 1. Check for empty fields
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
if (u.isEmpty() || p.isEmpty()) {
|
||||
showError("Please fill in all required fields.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Check if username exists
|
||||
userDatabase userDb = new userDatabase();
|
||||
if (!userDb.existsByUsername(username)) {
|
||||
if (!userDb.existsByUsername(u)) {
|
||||
showError("This username doesn’t exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Check if password is correct
|
||||
User user = userDb.findByUsername(username);
|
||||
if (!PasswordHashing.verify(password, user.getPassword())) {
|
||||
User user = userDb.findByUsername(u);
|
||||
if (!PasswordHashing.verify(p, user.getPassword())) {
|
||||
showError("Incorrect password.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Attempt to send to server
|
||||
try {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "login");
|
||||
request.put("user_id", JSONObject.NULL);
|
||||
request.put("username", username);
|
||||
request.put("password", password);
|
||||
request.put("profile_name", JSONObject.NULL);
|
||||
setUiBusy(true);
|
||||
|
||||
if (connection != null) {
|
||||
connection.send(request.toString());
|
||||
String responseStr = connection.receive();
|
||||
JSONObject response = new JSONObject(responseStr);
|
||||
System.out.println("Status: " + response.getString("status"));
|
||||
System.out.println("Message: " + response.getString("message"));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
var handler = cli.getHandler();
|
||||
|
||||
// Simulate successful login (since main.fxml isn’t ready)
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Login successful!");
|
||||
alert.show();
|
||||
|
||||
handler.login(u,p);
|
||||
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setUiBusy(false);
|
||||
|
||||
if (!handler.wasSuccess() || Session.currentUser == null) {
|
||||
showError(handler.getLastMessage().isEmpty() ? "Login failed." : handler.getLastMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// موفق: Session از داخل send پر شده
|
||||
goMain(); // بدون Alert → مستقیم به main.fxml
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setUiBusy(false);
|
||||
showError("Connection error.");
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
showError("Unable to connect to server. Please try again later.");
|
||||
}
|
||||
}, "login-thread").start();
|
||||
}
|
||||
|
||||
private void goMain() {
|
||||
AppRouter.showMain();
|
||||
}
|
||||
|
||||
|
||||
private void setUiBusy(boolean b) {
|
||||
usernameField.setDisable(b);
|
||||
passwordField.setDisable(b);
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void switchToRegister() throws IOException {
|
||||
switchScene("register_view.fxml");
|
||||
showRegister();
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
@@ -127,6 +200,7 @@ public class LoginController {
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||
pause.setOnFinished(event -> {
|
||||
errorLabel.setText(message);
|
||||
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
pause.play();
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.User;
|
||||
import org.to.telegramfinalproject.Security.PasswordHashing;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class LoginForm {
|
||||
|
||||
@FXML
|
||||
private Button loginButton;
|
||||
@FXML
|
||||
private Button backButton;
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private PasswordField passwordField;
|
||||
|
||||
private ClientConnection connection;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
try {
|
||||
connection = new ClientConnection("localhost", 8000);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Could not connect to server: " + e.getMessage());
|
||||
}
|
||||
|
||||
loginButton.setOnAction(e -> {
|
||||
String username = usernameField.getText();
|
||||
String password = passwordField.getText();
|
||||
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "login");
|
||||
request.put("user_id", JSONObject.NULL);
|
||||
request.put("username", username);
|
||||
request.put("password", password);
|
||||
request.put("profile_name", JSONObject.NULL);
|
||||
if (connection!=null) {
|
||||
connection.send(request.toString());
|
||||
}
|
||||
userDatabase userDb = new userDatabase();
|
||||
User user = userDb.findByUsername(username);
|
||||
|
||||
if(!userDb.existsByUsername(username)){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid username");
|
||||
alert.show();
|
||||
}
|
||||
else if(!PasswordHashing.verify(password,user.getPassword())){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password");
|
||||
alert.show();
|
||||
}
|
||||
else if(!PasswordHashing.verify(password,user.getPassword()) && !userDb.existsByUsername(username)){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password and username");
|
||||
alert.show();
|
||||
}
|
||||
else{
|
||||
try {
|
||||
String responseStr = connection.receive();
|
||||
JSONObject response = new JSONObject(responseStr);
|
||||
System.out.println("Status: " + response.getString("status"));
|
||||
System.out.println("Message: " + response.getString("message"));
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, " Message: " + response.getString("message"));
|
||||
alert.show();
|
||||
} catch (Exception ex) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
|
||||
alert.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
backButton.setOnAction(e -> {
|
||||
switchScene("login_view.fxml");
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
try {
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
||||
Parent root = loader.load();
|
||||
|
||||
|
||||
Stage stage = (Stage) backButton.getScene().getWindow();
|
||||
stage.setScene(new Scene(root));
|
||||
stage.show();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,43 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import org.to.telegramfinalproject.Client.Session;
|
||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||
import org.to.telegramfinalproject.Client.ActionHandler;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
public class MainController {
|
||||
|
||||
// === LEFT PANE ===
|
||||
@FXML private VBox chatListContainer; // inside the scrollPane
|
||||
@FXML private ScrollPane scrollPane; // chat list scroll
|
||||
|
||||
// === Global Search ===
|
||||
@FXML private TextField searchBar;
|
||||
@FXML private VBox globalSearchPane;
|
||||
@FXML private VBox noResultsBox;
|
||||
@FXML private ImageView noResultIcon;
|
||||
@FXML private ScrollPane globalSearchScroll;
|
||||
@FXML private VBox globalSearchResultsContainer;
|
||||
private enum SearchMode {
|
||||
GLOBAL,
|
||||
CHAT
|
||||
}
|
||||
private SearchMode currentSearchMode = SearchMode.GLOBAL;
|
||||
private UUID currentChatId; // if in CHAT mode, which chat to search in
|
||||
|
||||
// === Search In Chat ===
|
||||
@FXML private VBox chatSearchPane; // search results panel
|
||||
@FXML private ListView<String> chatSearchResults;
|
||||
@FXML private MenuButton scopeDropdown;
|
||||
|
||||
// === TOP BAR ===
|
||||
@FXML private TextField searchBar; // global search bar
|
||||
@FXML private Button menuButton;
|
||||
|
||||
// === RIGHT PANE ===
|
||||
@FXML private VBox leftPane;
|
||||
@@ -41,6 +59,14 @@ public class MainController {
|
||||
// === Sidebar ===
|
||||
@FXML private Pane overlay;
|
||||
@FXML private ImageView menuIcon;
|
||||
private SidebarMenuController sidebarController; //save sidebar controller
|
||||
|
||||
// === Time formatter for messages ===
|
||||
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";
|
||||
|
||||
// === STATE ===
|
||||
private static MainController instance;
|
||||
@@ -57,10 +83,82 @@ public class MainController {
|
||||
public static MainController getInstance() {
|
||||
return instance;
|
||||
}
|
||||
private final Map<UUID, ChatItemController> itemControllers = new HashMap<>();
|
||||
|
||||
|
||||
//For realtime handling
|
||||
public void onChatUpdated(UUID chatId, String chatType, LocalDateTime lastTs,
|
||||
boolean isIncoming, String lastPreview) {
|
||||
|
||||
boolean exists =
|
||||
(Session.chatList != null && Session.chatList.stream().anyMatch(c -> chatId.equals(c.getId()))) ||
|
||||
(Session.activeChats != null && Session.activeChats.stream().anyMatch(c -> chatId.equals(c.getId()))) ||
|
||||
(Session.archivedChats != null && Session.archivedChats.stream().anyMatch(c -> chatId.equals(c.getId())));
|
||||
if (!exists) return;
|
||||
|
||||
boolean isOpen = Session.currentChatId != null &&
|
||||
Session.currentChatId.equals(chatId.toString());
|
||||
|
||||
forEachChat(chatId, chat -> {
|
||||
chat.setLastMessageTime(lastTs);
|
||||
if (lastPreview != null) chat.setLastMessagePreview(lastPreview);
|
||||
if (isIncoming && !isOpen) chat.setUnreadCount(chat.getUnreadCount() + 1); // ✅ یکدست با unreadCount
|
||||
});
|
||||
|
||||
java.util.Comparator<ChatEntry> byTimeDesc = (c1, c2) -> {
|
||||
var t1 = c1.getLastMessageTime();
|
||||
var t2 = c2.getLastMessageTime();
|
||||
if (t1 == null && t2 == null) return 0;
|
||||
if (t1 == null) return 1;
|
||||
if (t2 == null) return -1;
|
||||
return t2.compareTo(t1);
|
||||
};
|
||||
if (Session.chatList != null) Session.chatList.sort(byTimeDesc);
|
||||
if (Session.activeChats != null) Session.activeChats.sort(byTimeDesc);
|
||||
if (Session.archivedChats != null) Session.archivedChats.sort(byTimeDesc);
|
||||
|
||||
refreshChatListUI();
|
||||
refreshBadgesIfAny();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MainController
|
||||
private void forEachChat(UUID chatId, java.util.function.Consumer<ChatEntry> fn) {
|
||||
java.util.List<java.util.List<ChatEntry>> lists = java.util.List.of(
|
||||
Session.chatList != null ? Session.chatList : java.util.List.of(),
|
||||
Session.activeChats != null ? Session.activeChats : java.util.List.of(),
|
||||
Session.archivedChats != null ? Session.archivedChats : java.util.List.of()
|
||||
);
|
||||
for (var lst : lists) {
|
||||
for (var c : lst) {
|
||||
if (chatId.equals(c.getId())) {
|
||||
fn.accept(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void refreshChatListUI() {
|
||||
Platform.runLater(() -> {
|
||||
chatListContainer.getChildren().clear();
|
||||
itemControllers.clear();
|
||||
populateChatListFromSession(); // همون متدی که نودها رو میسازه و میچسبونه
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void refreshBadgesIfAny() {
|
||||
// آپدیت نشانها اگر لازم است
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
addSampleChats();
|
||||
// Populate chat list
|
||||
populateChatListFromSession();
|
||||
|
||||
// Register the scene for automatic CSS updates
|
||||
Platform.runLater(() -> {
|
||||
@@ -70,17 +168,15 @@ public class MainController {
|
||||
// Listen for theme changes to update icons & labels manually
|
||||
ThemeManager.getInstance().darkModeProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal) {
|
||||
// Dark mode ON
|
||||
updateIconsForDarkMode();
|
||||
updateLabelsForDarkMode();
|
||||
} else {
|
||||
// Light mode ON
|
||||
updateIconsForLightMode();
|
||||
updateLabelsForLightMode();
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth scroll feel
|
||||
// Smooth scroll feel for chat list
|
||||
scrollPane.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm());
|
||||
scrollPane.setPannable(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
@@ -99,23 +195,47 @@ public class MainController {
|
||||
mainSplitPane.getDividers().get(0).positionProperty().addListener((o, ov, nv) -> clampDivider());
|
||||
}
|
||||
|
||||
// Search in chat field listener
|
||||
searchBar.textProperty().addListener((obs, oldV, newV) -> {
|
||||
if (!chatSearchPane.isVisible()) return; // only react if in search mode
|
||||
// Search bar enter action
|
||||
searchBar.setOnAction(e -> {
|
||||
String keyword = searchBar.getText().trim();
|
||||
if (keyword.isEmpty()) return;
|
||||
|
||||
if (newV.trim().isEmpty()) {
|
||||
chatSearchResults.setVisible(false);
|
||||
chatSearchResults.setManaged(false);
|
||||
} else {
|
||||
chatSearchResults.setVisible(true);
|
||||
chatSearchResults.setManaged(true);
|
||||
chatSearchResults.getItems().setAll(
|
||||
"Result 1: " + newV,
|
||||
"Result 2: " + newV,
|
||||
"Result 3: " + newV
|
||||
);
|
||||
if (currentSearchMode == SearchMode.GLOBAL) {
|
||||
performGlobalSearch(keyword);
|
||||
} else if (currentSearchMode == SearchMode.CHAT && currentChatId != null) {
|
||||
performChatSearch(keyword, currentChatId);
|
||||
}
|
||||
});
|
||||
|
||||
// Typing in search bar shows global search panel
|
||||
searchBar.textProperty().addListener((obs, o, n) -> {
|
||||
if (n != null && !n.isBlank()) {
|
||||
if (currentSearchMode == SearchMode.GLOBAL) {
|
||||
showGlobalSearchPanel();
|
||||
} else if (currentSearchMode == SearchMode.CHAT) {
|
||||
showSearchPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chatSearchResults.setOnMouseClicked(e -> {
|
||||
int idx = chatSearchResults.getSelectionModel().getSelectedIndex();
|
||||
if (idx >= 0 && idx < searchBacking.size()) {
|
||||
openSearchResult(searchBacking.get(idx));
|
||||
}
|
||||
});
|
||||
|
||||
// Smooth scroll feel for global search results
|
||||
globalSearchScroll.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/scrollpane.css").toExternalForm()
|
||||
);
|
||||
globalSearchScroll.setPannable(true);
|
||||
globalSearchScroll.setFitToWidth(true);
|
||||
globalSearchScroll.setFitToHeight(false);
|
||||
globalSearchScroll.getContent().setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY() * 0.003; // match chat list smoothness
|
||||
globalSearchScroll.setVvalue(globalSearchScroll.getVvalue() - deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
// Called from ChatPageController when user clicks search button
|
||||
@@ -123,6 +243,8 @@ public class MainController {
|
||||
scrollPane.setVisible(false);
|
||||
scrollPane.setManaged(false);
|
||||
|
||||
currentSearchMode = SearchMode.CHAT;
|
||||
|
||||
chatSearchPane.setVisible(true);
|
||||
chatSearchPane.setManaged(true);
|
||||
|
||||
@@ -134,59 +256,185 @@ public class MainController {
|
||||
chatSearchPane.setVisible(false);
|
||||
chatSearchPane.setManaged(false);
|
||||
|
||||
currentSearchMode = SearchMode.GLOBAL;
|
||||
currentChatId = null;
|
||||
|
||||
scrollPane.setVisible(true);
|
||||
scrollPane.setManaged(true);
|
||||
|
||||
searchBar.clear();
|
||||
}
|
||||
|
||||
private void addSampleChats() {
|
||||
addChat("Archived Chats", "Your archived chats", "10:45", 0);
|
||||
addChat("Saved Messages", "Keep messages for later", "Yesterday", 0);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
addChat("Alice", "Hey, how are you?", "14:12", 3);
|
||||
addChat("Bob", "Let's meet tomorrow", "12:08", 1);
|
||||
public void showGlobalSearchPanel() {
|
||||
scrollPane.setVisible(false);
|
||||
scrollPane.setManaged(false);
|
||||
|
||||
chatSearchPane.setVisible(false);
|
||||
chatSearchPane.setManaged(false);
|
||||
|
||||
globalSearchPane.setVisible(true);
|
||||
globalSearchPane.setManaged(true);
|
||||
}
|
||||
|
||||
private void addChat(String name, String lastMsg, String time, int unread) {
|
||||
try {
|
||||
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");
|
||||
@FXML
|
||||
public void closeGlobalSearch() {
|
||||
globalSearchPane.setVisible(false);
|
||||
globalSearchPane.setManaged(false);
|
||||
|
||||
chatItem.setOnMouseClicked(e -> openChat(name));
|
||||
chatListContainer.getChildren().add(chatItem);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
scrollPane.setVisible(true);
|
||||
scrollPane.setManaged(true);
|
||||
|
||||
searchBar.clear();
|
||||
}
|
||||
|
||||
private String formatChatTime(java.time.LocalDateTime ts) {
|
||||
if (ts == null) return "";
|
||||
var today = java.time.LocalDate.now();
|
||||
var d = ts.toLocalDate();
|
||||
|
||||
if (d.isEqual(today)) {
|
||||
//today
|
||||
return FMT_HHMM.format(ts);
|
||||
//yesterday
|
||||
} else if (d.isEqual(today.minusDays(1))) {
|
||||
return YESTERDAY_LABEL;
|
||||
} else {
|
||||
return FMT_DATE_TIME.format(ts);
|
||||
}
|
||||
}
|
||||
|
||||
private void openChat(String chatName) {
|
||||
private void populateChatListFromSession() {
|
||||
chatListContainer.getChildren().clear();
|
||||
|
||||
var list = (org.to.telegramfinalproject.Client.Session.activeChats != null
|
||||
&& !org.to.telegramfinalproject.Client.Session.activeChats.isEmpty())
|
||||
? org.to.telegramfinalproject.Client.Session.activeChats
|
||||
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||
|
||||
if (list == null || list.isEmpty()) return;
|
||||
|
||||
ChatEntry saved = null;
|
||||
java.util.List<ChatEntry> others = new java.util.ArrayList<>();
|
||||
for (ChatEntry c : list) {
|
||||
if (saved == null && isSavedMessages(c)) {
|
||||
saved = c;
|
||||
} else {
|
||||
others.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
// اگر “Archived Chats” یا هدر دیگری داری، قبلش اضافه کن (اختیاری)
|
||||
// addArchivedHeaderIfYouHaveOne();
|
||||
|
||||
// 1) همیشه Saved اول بیاد (اگر وجود داشت)
|
||||
if (saved != null) {
|
||||
addChatNode(saved);
|
||||
}
|
||||
|
||||
for (ChatEntry c : others) {
|
||||
addChatNode(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private void addChatNode(ChatEntry chat ) {
|
||||
// try {
|
||||
// FXMLLoader fx = new FXMLLoader(getClass().getResource(
|
||||
// "/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||
// Node item = fx.load();
|
||||
// ChatItemController cc = fx.getController();
|
||||
//
|
||||
// String lastPreview = "";
|
||||
// String time = (chat.getLastMessageTime() != null)
|
||||
// ? timeFmt.format(chat.getLastMessageTime()) : "";
|
||||
//
|
||||
// cc.setChatData(chat.getName(), lastPreview, time, 0);
|
||||
//
|
||||
// item.setOnMouseClicked(e -> openChat(String.valueOf(chat)));
|
||||
// chatListContainer.getChildren().add(item);
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
// private void addChatNode(ChatEntry chat) {
|
||||
// try {
|
||||
// FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||
// Node item = fx.load();
|
||||
// ChatItemController cc = fx.getController();
|
||||
//
|
||||
// String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||
// 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();
|
||||
//
|
||||
// cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), "/org/to/telegramfinalproject/Avatars/default_profile.png");
|
||||
// item.setOnMouseClicked(e -> openChat(chat));
|
||||
// chatListContainer.getChildren().add(item);
|
||||
//
|
||||
// itemControllers.put(chat.getId(), cc);
|
||||
//
|
||||
// } catch (Exception ex) {
|
||||
// ex.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private void addChatNode(ChatEntry chat) {
|
||||
try {
|
||||
FXMLLoader fx = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_item.fxml"));
|
||||
Node item = fx.load();
|
||||
ChatItemController cc = fx.getController();
|
||||
|
||||
String preview = chat.getLastMessagePreview() == null ? "" : chat.getLastMessagePreview();
|
||||
|
||||
String timeText = chat.getLastMessageTime() == null
|
||||
? ""
|
||||
: formatChatTime(chat.getLastMessageTime());
|
||||
|
||||
// If chat has a profile picture, pass it; otherwise null
|
||||
String imageUrl = (chat.getImageUrl() != null && !chat.getImageUrl().isEmpty())
|
||||
? chat.getImageUrl()
|
||||
: null;
|
||||
|
||||
cc.setChatData(chat.getName(), preview, timeText, chat.getUnreadCount(), imageUrl, chat.getType()); item.setOnMouseClicked(e -> openChat(chat));
|
||||
chatListContainer.getChildren().add(item);
|
||||
|
||||
itemControllers.put(chat.getId(), cc);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String mapTypeToLabel(String t) {
|
||||
switch (t.toUpperCase()) {
|
||||
case "IMAGE": return "[Image]";
|
||||
case "AUDIO": return "[Audio]";
|
||||
case "VIDEO": return "[Video]";
|
||||
case "FILE": return "[File]";
|
||||
default: return "[Message]";
|
||||
}
|
||||
}
|
||||
|
||||
private void openChat(ChatEntry chat) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/Fxml/chat_page.fxml"));
|
||||
Node chatPage = loader.load();
|
||||
|
||||
ChatPageController controller = loader.getController();
|
||||
controller.setChat("Alice", "/org/to/telegramfinalproject/Avatars/profile_test.png");
|
||||
controller.showChat(chat);
|
||||
|
||||
this.chatPageController = controller;
|
||||
Session.currentChatId = chat.getId().toString();
|
||||
|
||||
chatDisplayArea.getChildren().clear();
|
||||
chatDisplayArea.getChildren().add(chatPage);
|
||||
chatDisplayArea.getChildren().setAll(chatPage);
|
||||
chat.setUnreadCount(0);
|
||||
ChatItemController item = itemControllers.get(chat.getId());
|
||||
if (item != null) item.setUnread(0);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,25 +447,56 @@ 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();
|
||||
}
|
||||
@@ -263,11 +542,13 @@ public class MainController {
|
||||
private void updateIconsForDarkMode() {
|
||||
// Example: switch images to white versions
|
||||
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_light.png")));
|
||||
noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_light.png")));
|
||||
}
|
||||
|
||||
private void updateIconsForLightMode() {
|
||||
// Example: switch images to black versions
|
||||
menuIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/menu_dark.png")));
|
||||
noResultIcon.setImage(new Image(getClass().getResourceAsStream("/org/to/telegramfinalproject/Icons/no_result_dark.png")));
|
||||
}
|
||||
|
||||
private void updateLabelsForDarkMode() {
|
||||
@@ -286,4 +567,411 @@ public class MainController {
|
||||
public void closeOverlay(Node overlayNode) {
|
||||
mainRoot.getChildren().remove(overlayNode);
|
||||
}
|
||||
|
||||
|
||||
// ===== Search UI state =====
|
||||
private final java.util.List<SearchResult> searchBacking = new java.util.ArrayList<>();
|
||||
|
||||
@FXML private AnchorPane chatHost; // کانتینر مناسب در مین برای قرار دادن ChatPage
|
||||
|
||||
private ChatPageController chatPageController;
|
||||
|
||||
|
||||
|
||||
public ChatPageController getChatPageController() {
|
||||
return chatPageController;
|
||||
}
|
||||
|
||||
|
||||
private enum SRType { USER, GROUP, CHANNEL, MESSAGE }
|
||||
|
||||
private static class SearchResult {
|
||||
final SRType type;
|
||||
final String title; // name / sender_name / ...
|
||||
final String subtitle; // @"id" / context string / time
|
||||
final String receiverType; // برای MESSAGE/GROUP/CHANNEL: private|group|channel, برای USER: null
|
||||
final java.util.UUID uuid; // uuid آن موجودیت (user/group/channel/chat holder) یا receiver_id پیام
|
||||
final String displayId; // id قابل نمایش (مثل username یا group_id/channel_id)
|
||||
final String messageId; // فقط برای MESSAGE
|
||||
final String time; // نمایش
|
||||
|
||||
SearchResult(SRType t, String title, String subtitle, String receiverType,
|
||||
java.util.UUID uuid, String displayId, String messageId, String time) {
|
||||
this.type = t; this.title = title; this.subtitle = subtitle;
|
||||
this.receiverType = receiverType; this.uuid = uuid;
|
||||
this.displayId = displayId; this.messageId = messageId; this.time = time;
|
||||
}
|
||||
|
||||
String toDisplay() {
|
||||
switch (type) {
|
||||
case MESSAGE:
|
||||
String left = (title == null || title.isBlank()) ? "Message" : title;
|
||||
String right = (time == null ? "" : (" • " + time));
|
||||
return "🗨 " + left + right + (subtitle==null?"":(" — " + subtitle));
|
||||
case USER: return "👤 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||
case GROUP: return "👥 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||
case CHANNEL: return "📣 " + title + (subtitle==null?"":(" — " + subtitle));
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
private void performChatSearch(String keyword, UUID chatId) {
|
||||
if (keyword == null || keyword.isBlank()) {
|
||||
chatSearchResults.getItems().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
showSearchPanel();
|
||||
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "search_in_chat");
|
||||
req.put("keyword", keyword);
|
||||
req.put("chat_id", chatId.toString());
|
||||
req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"));
|
||||
|
||||
new Thread(() -> {
|
||||
org.json.JSONObject resp;
|
||||
try {
|
||||
resp = ActionHandler.sendWithResponse(req);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp == null || !"success".equals(resp.optString("status"))) return;
|
||||
org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results");
|
||||
if (arr == null) arr = new org.json.JSONArray();
|
||||
|
||||
java.util.List<String> tmp = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
org.json.JSONObject it = arr.getJSONObject(i);
|
||||
String time = it.optString("time", "");
|
||||
String content = it.optString("content", "[No content]");
|
||||
tmp.add("🗨 " + content + (time.isEmpty() ? "" : " • " + time));
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
chatSearchResults.getItems().setAll(tmp);
|
||||
chatSearchResults.setVisible(true);
|
||||
chatSearchResults.setManaged(true);
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
public void performGlobalSearch(String keyword) {
|
||||
if (keyword == null || keyword.isBlank()) {
|
||||
chatSearchResults.getItems().clear();
|
||||
searchBacking.clear();
|
||||
return;
|
||||
}
|
||||
if (org.to.telegramfinalproject.Client.Session.currentUser == null ||
|
||||
!org.to.telegramfinalproject.Client.Session.currentUser.has("user_id")) {
|
||||
System.out.println("You must be logged in to search.");
|
||||
return;
|
||||
}
|
||||
|
||||
showGlobalSearchPanel();
|
||||
|
||||
// Request to server
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "search");
|
||||
req.put("keyword", keyword);
|
||||
req.put("user_id", org.to.telegramfinalproject.Client.Session.currentUser.getString("user_id"));
|
||||
|
||||
new Thread(() -> {
|
||||
org.json.JSONObject resp;
|
||||
try {
|
||||
resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return;
|
||||
}
|
||||
if (resp == null || !"success".equals(resp.optString("status"))) return;
|
||||
|
||||
org.json.JSONArray arr = resp.optJSONObject("data").optJSONArray("results");
|
||||
if (arr == null) arr = new org.json.JSONArray();
|
||||
|
||||
final java.util.List<SearchResult> tmp = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < arr.length(); i++) {
|
||||
org.json.JSONObject it = arr.getJSONObject(i);
|
||||
String type = it.optString("type","");
|
||||
|
||||
switch (type) {
|
||||
case "user": {
|
||||
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||
String name = it.optString("name","Unknown");
|
||||
String id = it.optString("id", "");
|
||||
tmp.add(new SearchResult(
|
||||
SRType.USER, name,
|
||||
id.isBlank()? null : "@"+id,
|
||||
null, uuid, id, null, null
|
||||
));
|
||||
break;
|
||||
}
|
||||
case "group": {
|
||||
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||
String name = it.optString("name","Unknown group");
|
||||
String id = it.optString("id", "");
|
||||
tmp.add(new SearchResult(
|
||||
SRType.GROUP, name,
|
||||
id.isBlank()? null : id,
|
||||
"group", uuid, id, null, null
|
||||
));
|
||||
break;
|
||||
}
|
||||
case "channel": {
|
||||
java.util.UUID uuid = java.util.UUID.fromString(it.getString("uuid"));
|
||||
String name = it.optString("name","Unknown channel");
|
||||
String id = it.optString("id", "");
|
||||
tmp.add(new SearchResult(
|
||||
SRType.CHANNEL, name,
|
||||
id.isBlank()? null : id,
|
||||
"channel", uuid, id, null, null
|
||||
));
|
||||
break;
|
||||
}
|
||||
case "message": {
|
||||
String senderName = it.optString("sender_name", it.optString("sender","Unknown"));
|
||||
String time = it.optString("time", "");
|
||||
String content = it.optString("content","[No content]");
|
||||
String rType = it.optString("receiver_type",""); // private|group|channel
|
||||
java.util.UUID rUuid = java.util.UUID.fromString(it.getString("receiver_id"));
|
||||
String ctx = "";
|
||||
if ("group".equals(rType)) ctx = it.optString("group_name","");
|
||||
if ("channel".equals(rType)) ctx = it.optString("channel_name","");
|
||||
String subtitle = (ctx.isBlank()? "" : ctx) + (content.isBlank()? "" : (subtitleSep(ctx)+content));
|
||||
tmp.add(new SearchResult(
|
||||
SRType.MESSAGE, senderName, subtitle, rType, rUuid,
|
||||
it.optString("receiver_display_id", ""),
|
||||
it.optString("message_id", null),
|
||||
time
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform.runLater(() -> renderSearchResults(tmp));
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String subtitleSep(String s){ return s==null || s.isBlank()? "" : " — "; }
|
||||
|
||||
// private void renderSearchResults(java.util.List<SearchResult> results) {
|
||||
// searchBacking.clear();
|
||||
// searchBacking.addAll(results);
|
||||
//
|
||||
// javafx.collections.ObservableList<SearchResult> view =
|
||||
// javafx.collections.FXCollections.observableArrayList(results);
|
||||
// globalSearchResults.setItems(view);
|
||||
// globalSearchResults.setVisible(true);
|
||||
// globalSearchResults.setManaged(true);
|
||||
// }
|
||||
|
||||
private void renderSearchResults(List<SearchResult> results) {
|
||||
globalSearchResultsContainer.getChildren().clear();
|
||||
|
||||
if (results.isEmpty()) {
|
||||
noResultsBox.setVisible(true);
|
||||
noResultsBox.setManaged(true);
|
||||
globalSearchScroll.setVisible(false);
|
||||
globalSearchScroll.setManaged(false);
|
||||
return;
|
||||
}
|
||||
|
||||
noResultsBox.setVisible(false);
|
||||
noResultsBox.setManaged(false);
|
||||
globalSearchScroll.setVisible(true);
|
||||
globalSearchScroll.setManaged(true);
|
||||
|
||||
for (SearchResult r : results) {
|
||||
HBox container = new HBox(10);
|
||||
ImageView avatar = new ImageView();
|
||||
avatar.setFitWidth(40);
|
||||
avatar.setFitHeight(40);
|
||||
avatar.setSmooth(true);
|
||||
avatar.setPreserveRatio(true);
|
||||
|
||||
// 🔑 Clip it into a circle
|
||||
Circle clip = new Circle(20, 20, 20); // x,y = center, radius = 20
|
||||
avatar.setClip(clip);
|
||||
|
||||
Label title = new Label(r.title);
|
||||
title.getStyleClass().add("global-search-title");
|
||||
|
||||
Label subtitle = new Label(
|
||||
r.type == SRType.MESSAGE
|
||||
? (r.subtitle != null ? r.subtitle : "")
|
||||
: "Press to see messages"
|
||||
);
|
||||
subtitle.getStyleClass().add("global-search-subtitle");
|
||||
|
||||
VBox texts = new VBox(2, title, subtitle);
|
||||
container.getChildren().addAll(avatar, texts);
|
||||
|
||||
// Default profile per type
|
||||
switch (r.type) {
|
||||
case USER -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))));
|
||||
case GROUP -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_group_profile.png"))));
|
||||
case CHANNEL -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_channel_profile.png"))));
|
||||
case MESSAGE -> avatar.setImage(new Image(Objects.requireNonNull(getClass().getResourceAsStream(
|
||||
"/org/to/telegramfinalproject/Avatars/default_user_profile.png"))));
|
||||
}
|
||||
|
||||
container.setOnMouseClicked(e -> openSearchResult(r));
|
||||
globalSearchResultsContainer.getChildren().add(container);
|
||||
}
|
||||
}
|
||||
|
||||
private void openSearchResult(SearchResult r) {
|
||||
switch (r.type) {
|
||||
case USER: {
|
||||
|
||||
java.util.UUID chatId = findExistingPrivateChatId(r.uuid);
|
||||
if (chatId == null) {
|
||||
chatId = fetchOrCreatePrivateChat(r.uuid);
|
||||
if (chatId == null) {
|
||||
System.out.println("❌ Failed to create/find private chat.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||
ce.setId(chatId.toString()); // ⬅️ internal chat_id
|
||||
ce.setDisplayId(r.displayId); // username
|
||||
ce.setName(r.title); // profile_name
|
||||
ce.setType("private");
|
||||
|
||||
openChat(ce);
|
||||
break;
|
||||
}
|
||||
|
||||
case GROUP:
|
||||
case CHANNEL: {
|
||||
org.to.telegramfinalproject.Models.ChatEntry existing =
|
||||
findExistingChat(r.uuid, r.receiverType);
|
||||
if (existing != null) {
|
||||
openChat(existing);
|
||||
} else {
|
||||
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||
ce.setId(r.uuid.toString()); // internal_uuid group/channel
|
||||
ce.setDisplayId(r.displayId); // group_id/channel_id
|
||||
ce.setName(r.title);
|
||||
ce.setType(r.receiverType);
|
||||
openChat(ce);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MESSAGE: {
|
||||
org.to.telegramfinalproject.Models.ChatEntry existing =
|
||||
findExistingChat(r.uuid, r.receiverType);
|
||||
if (existing != null) {
|
||||
openChat(existing);
|
||||
} else {
|
||||
org.to.telegramfinalproject.Models.ChatEntry ce = new org.to.telegramfinalproject.Models.ChatEntry();
|
||||
ce.setId(r.uuid.toString()); // receiver internal_uuid
|
||||
ce.setType(r.receiverType);
|
||||
ce.setName(guessNameForReceiver(r));
|
||||
ce.setDisplayId(r.displayId);
|
||||
openChat(ce);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private org.to.telegramfinalproject.Models.ChatEntry findExistingChat(java.util.UUID internalId, String type) {
|
||||
var list = (org.to.telegramfinalproject.Client.Session.chatList==null)
|
||||
? java.util.Collections.<org.to.telegramfinalproject.Models.ChatEntry>emptyList()
|
||||
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||
for (var c : list) {
|
||||
if (internalId.toString().equals(c.getId().toString())
|
||||
&& type.equalsIgnoreCase(c.getType())) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private java.util.UUID findExistingPrivateChatId(java.util.UUID otherUserUuid) {
|
||||
var list = (org.to.telegramfinalproject.Client.Session.chatList==null)
|
||||
? java.util.Collections.<org.to.telegramfinalproject.Models.ChatEntry>emptyList()
|
||||
: org.to.telegramfinalproject.Client.Session.chatList;
|
||||
|
||||
for (var c : list) {
|
||||
if ("private".equalsIgnoreCase(c.getType())) {
|
||||
if (otherUserUuid.equals(c.getOtherUserId())) {
|
||||
try { return java.util.UUID.fromString(c.getId().toString()); } catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private java.util.UUID fetchOrCreatePrivateChat(java.util.UUID otherUserUuid) {
|
||||
try {
|
||||
org.json.JSONObject req = new org.json.JSONObject();
|
||||
req.put("action", "get_or_create_private_chat");
|
||||
req.put("user1", org.to.telegramfinalproject.Client.Session.currentUser.getString("internal_uuid"));
|
||||
req.put("user2", otherUserUuid.toString());
|
||||
org.json.JSONObject resp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(req);
|
||||
if (resp != null && "success".equals(resp.optString("status"))) {
|
||||
String chatId = resp.getJSONObject("data").getString("chat_id");
|
||||
return java.util.UUID.fromString(chatId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String guessNameForReceiver(SearchResult r) {
|
||||
if (r.title != null && !r.title.isBlank()) return r.title;
|
||||
if (r.receiverType != null) {
|
||||
switch (r.receiverType) {
|
||||
case "group": return "Group";
|
||||
case "channel": return "Channel";
|
||||
case "private": return "Private Chat";
|
||||
}
|
||||
}
|
||||
return "Chat";
|
||||
}
|
||||
|
||||
private boolean isSavedMessages(ChatEntry c) {
|
||||
if (c == null) return false;
|
||||
|
||||
// اگر تایپ اختصاصی داری
|
||||
if ("saved".equalsIgnoreCase(c.getType())) return true;
|
||||
|
||||
// اگر با نام مشخص ذخیره میکنی
|
||||
String n = c.getName();
|
||||
if (n != null && n.equalsIgnoreCase("Saved Messages")) return true;
|
||||
|
||||
// حالت پرایوت با خودِ کاربر
|
||||
String me = (org.to.telegramfinalproject.Client.Session.currentUser != null)
|
||||
? org.to.telegramfinalproject.Client.Session.currentUser.optString("internal_uuid", "")
|
||||
: "";
|
||||
try {
|
||||
UUID other = c.getOtherUserId(); // اگر این فیلد را داری
|
||||
if ("private".equalsIgnoreCase(c.getType()) &&
|
||||
other != null && other.toString().equalsIgnoreCase(me)) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.to.telegramfinalproject.UI.AppRouter.showLogin;
|
||||
|
||||
public class RegisterController {
|
||||
|
||||
@FXML private TextField userIdField;
|
||||
@@ -38,18 +40,16 @@ public class RegisterController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Sync password and confirm fields with their visible counterparts
|
||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||
visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty());
|
||||
|
||||
try {
|
||||
connection = new ClientConnection("localhost", 8000);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Could not connect to server: " + e.getMessage());
|
||||
org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty());
|
||||
visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
@FXML
|
||||
private void togglePasswordVisibility() {
|
||||
passwordVisible = !passwordVisible;
|
||||
visiblePasswordField.setVisible(passwordVisible);
|
||||
@@ -69,69 +69,152 @@ public class RegisterController {
|
||||
toggleConfirmBtn.setText(confirmVisible ? "👁" : "👁");
|
||||
}
|
||||
|
||||
// @FXML
|
||||
// private void handleRegister() {
|
||||
// String userID = userIdField.getText().trim();
|
||||
// String username = usernameField.getText().trim();
|
||||
// String profileName = profileNameField.getText().trim();
|
||||
// String password = passwordField.getText();
|
||||
// String confirmPass = confirmPasswordField.getText();
|
||||
//
|
||||
// userDatabase userDb = new userDatabase();
|
||||
// String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
//
|
||||
// // 1. Empty fields
|
||||
// if (userID.isEmpty() || username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) {
|
||||
// showError("Please fill in all required fields.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 2. Username exists
|
||||
// if (userDb.existsByUsername(username)) {
|
||||
// showError("This username is already taken.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 3. User ID exists
|
||||
// if (userDb.existsByUserId(userID)) {
|
||||
// showError("This user ID is already taken.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 4. Password mismatch
|
||||
// if (!password.equals(confirmPass)) {
|
||||
// showError("Passwords do not match.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 5. Weak password
|
||||
// if (!password.matches(passwordRegex)) {
|
||||
// showError("Password must be at least 8 characters, include a capital letter, a number, and a special character.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 6. Attempt registration
|
||||
// try {
|
||||
// JSONObject request = new JSONObject();
|
||||
// request.put("action", "register");
|
||||
// request.put("user_id", userID);
|
||||
// request.put("username", username);
|
||||
// request.put("password", password);
|
||||
// request.put("profile_name", profileName);
|
||||
// connection.send(request.toString());
|
||||
//
|
||||
// // Simulate successful registration (since main.fxml isn’t ready)
|
||||
// Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration successful!");
|
||||
// alert.show();
|
||||
//
|
||||
// } catch (Exception ex) {
|
||||
// showError("Failed to register. Please try again later.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleRegister() {
|
||||
String userID = userIdField.getText().trim();
|
||||
String username = usernameField.getText().trim();
|
||||
String userID = userIdField.getText().trim();
|
||||
String username = usernameField.getText().trim();
|
||||
String profileName = profileNameField.getText().trim();
|
||||
String password = passwordField.getText();
|
||||
String password = passwordField.getText();
|
||||
String confirmPass = confirmPasswordField.getText();
|
||||
|
||||
userDatabase userDb = new userDatabase();
|
||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
|
||||
// 1. Empty fields
|
||||
// اعتبارسنجیها
|
||||
if (userID.isEmpty() || username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) {
|
||||
showError("Please fill in all required fields.");
|
||||
return;
|
||||
showError("Please fill in all required fields."); return;
|
||||
}
|
||||
if (userDb.existsByUsername(username)) { showError("This username is already taken."); return; }
|
||||
if (userDb.existsByUserId(userID)) { showError("This user ID is already taken."); return; }
|
||||
if (!password.equals(confirmPass)) { showError("Passwords do not match."); return; }
|
||||
if (!password.matches(passwordRegex)) { showError("Password must be at least 8 characters, include a capital letter, a number, and a special character."); return; }
|
||||
|
||||
// 2. Username exists
|
||||
if (userDb.existsByUsername(username)) {
|
||||
showError("This username is already taken.");
|
||||
return;
|
||||
}
|
||||
// UI را موقتاً disable کن (اختیاری)
|
||||
setBusy(true);
|
||||
|
||||
// 3. User ID exists
|
||||
if (userDb.existsByUserId(userID)) {
|
||||
showError("This user ID is already taken.");
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// 1) مطمئن شو کانکشن/لیسنر روشن است
|
||||
var cli = org.to.telegramfinalproject.Client.TelegramClient.getOrInitForUI();
|
||||
var handler = cli.getHandler();
|
||||
|
||||
// 4. Password mismatch
|
||||
if (!password.equals(confirmPass)) {
|
||||
showError("Passwords do not match.");
|
||||
return;
|
||||
}
|
||||
// 2) Register
|
||||
var regReq = new org.json.JSONObject()
|
||||
.put("action", "register")
|
||||
.put("user_id", userID)
|
||||
.put("username", username)
|
||||
.put("password", password)
|
||||
.put("profile_name", profileName);
|
||||
|
||||
// 5. Weak password
|
||||
if (!password.matches(passwordRegex)) {
|
||||
showError("Password must be at least 8 characters, include a capital letter, a number, and a special character.");
|
||||
return;
|
||||
}
|
||||
var regResp = org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse(regReq);
|
||||
if (regResp == null || !"success".equalsIgnoreCase(regResp.optString("status"))) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
showError(regResp != null ? regResp.optString("message","Register failed.") : "Register failed.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Attempt registration
|
||||
try {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("action", "register");
|
||||
request.put("user_id", userID);
|
||||
request.put("username", username);
|
||||
request.put("password", password);
|
||||
request.put("profile_name", profileName);
|
||||
connection.send(request.toString());
|
||||
// 3) Auto-Login با همان کانکشن
|
||||
handler.login(username, password);
|
||||
|
||||
// Simulate successful registration (since main.fxml isn’t ready)
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration successful!");
|
||||
alert.show();
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
if (org.to.telegramfinalproject.Client.Session.currentUser == null || !handler.wasSuccess()) {
|
||||
showError(handler.getLastMessage().isEmpty() ? "Login failed." : handler.getLastMessage());
|
||||
return;
|
||||
}
|
||||
org.to.telegramfinalproject.UI.AppRouter.showMain();
|
||||
});
|
||||
|
||||
} catch (Exception ex) {
|
||||
showError("Failed to register. Please try again later.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
javafx.application.Platform.runLater(() -> {
|
||||
setBusy(false);
|
||||
showError("Failed to register/login: " + ex.getMessage());
|
||||
});
|
||||
}
|
||||
}, "register-thread").start();
|
||||
}
|
||||
|
||||
private void setBusy(boolean b){
|
||||
userIdField.setDisable(b);
|
||||
usernameField.setDisable(b);
|
||||
profileNameField.setDisable(b);
|
||||
passwordField.setDisable(b);
|
||||
visiblePasswordField.setDisable(b);
|
||||
confirmPasswordField.setDisable(b);
|
||||
visibleConfirmPasswordField.setDisable(b);
|
||||
togglePasswordBtn.setDisable(b);
|
||||
toggleConfirmBtn.setDisable(b);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@FXML
|
||||
private void switchToLogin() throws IOException {
|
||||
switchScene("login_view.fxml");
|
||||
showLogin();
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
@@ -154,6 +237,7 @@ public class RegisterController {
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(50));
|
||||
pause.setOnFinished(event -> {
|
||||
errorLabel.setText(message);
|
||||
errorLabel.setStyle("-fx-text-fill: red;"); // 🔴 force red
|
||||
errorLabel.setVisible(true);
|
||||
});
|
||||
pause.play();
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.ClientConnection;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RegisterForm {
|
||||
@FXML
|
||||
private Button submitButton;
|
||||
@FXML
|
||||
private Button backButton;
|
||||
@FXML private TextField userIdField;
|
||||
@FXML private TextField usernameField;
|
||||
@FXML private TextField profileNameField;
|
||||
@FXML private PasswordField passwordField;
|
||||
@FXML private PasswordField confirmPasswordField;
|
||||
|
||||
|
||||
|
||||
private ClientConnection connection;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
|
||||
try {
|
||||
connection = new ClientConnection("localhost", 8000);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Could not connect to server: " + e.getMessage());
|
||||
}
|
||||
|
||||
submitButton.setOnAction(e -> {
|
||||
String userID = userIdField.getText();
|
||||
String username = usernameField.getText();
|
||||
String profile_name = profileNameField.getText();
|
||||
String password = passwordField.getText();
|
||||
String confirmPass =confirmPasswordField.getText();
|
||||
JSONObject request = new JSONObject();
|
||||
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
|
||||
|
||||
userDatabase userDb = new userDatabase();
|
||||
if(password.equals(confirmPass) && password.matches(passwordRegex) && !userDb.existsByUserId(userID)&& !userDb.existsByUsername(username)){
|
||||
try {
|
||||
request.put("action", "register");
|
||||
request.put("user_id", userID);
|
||||
request.put("username", username);
|
||||
request.put("password", password);
|
||||
request.put("profile_name", profile_name);
|
||||
connection.send(request.toString());
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration is successful");
|
||||
alert.show();
|
||||
|
||||
} catch (Exception ex) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
|
||||
alert.show();
|
||||
}
|
||||
|
||||
}
|
||||
else if(!password.equals(confirmPass) && password.matches(passwordRegex)) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Password doesn't match");
|
||||
alert.show();
|
||||
}
|
||||
else if(userDb.existsByUserId(userID))
|
||||
{
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "User ID is already exist");
|
||||
alert.show();
|
||||
}
|
||||
else if(userDb.existsByUsername(username)){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Username is already exist");
|
||||
alert.show();
|
||||
}
|
||||
else {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Password isn't Strong enough");
|
||||
alert.show();
|
||||
}
|
||||
});
|
||||
|
||||
backButton.setOnAction(e -> {
|
||||
switchScene("login_view.fxml");
|
||||
});
|
||||
}
|
||||
|
||||
private void switchScene(String fxmlFile) {
|
||||
try {
|
||||
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
|
||||
Parent root = loader.load();
|
||||
|
||||
|
||||
Stage stage = (Stage) backButton.getScene().getWindow();
|
||||
stage.setScene(new Scene(root));
|
||||
stage.show();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Client.AvatarLocalResolver;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@@ -22,6 +25,9 @@ public class SidebarMenuController {
|
||||
|
||||
private static final String ICON_PATH = "/org/to/telegramfinalproject/Icons/";
|
||||
|
||||
|
||||
|
||||
|
||||
@FXML private VBox sidebarRoot;
|
||||
@FXML private ImageView profileImage;
|
||||
@FXML private Label usernameLabel;
|
||||
@@ -46,8 +52,10 @@ public class SidebarMenuController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
// Load default profile image
|
||||
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_profile.png");
|
||||
Image profile = loadImage("/org/to/telegramfinalproject/Avatars/default_user_profile.png");
|
||||
if (profile != null) profileImage.setImage(profile);
|
||||
AvatarFX.circleClip(profileImage, 56);
|
||||
|
||||
|
||||
setupButtonActions();
|
||||
setupToggleAction();
|
||||
@@ -210,4 +218,58 @@ public class SidebarMenuController {
|
||||
private void openSettings() { System.out.println("Opening Settings..."); }
|
||||
private void openTelegramFeatures() { System.out.println("Opening Telegram Features..."); }
|
||||
private void openTelegramQnA() { System.out.println("Opening Telegram Q&A..."); }
|
||||
|
||||
|
||||
|
||||
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/default_user_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;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// اگر مسیر نسبی سرور (مثل /avatars/...) است
|
||||
String fileUri = AvatarLocalResolver.resolve(src);
|
||||
if (fileUri != null) return new Image(fileUri, true);
|
||||
|
||||
// در غیر این صورت، فرض URL کامل
|
||||
return new Image(src, true);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,36 @@
|
||||
package org.to.telegramfinalproject.UI;
|
||||
package org.to.telegramfinalproject.UI;//package org.to.telegramfinalproject.UI;
|
||||
//
|
||||
//
|
||||
//import javafx.application.Application;
|
||||
//import javafx.fxml.FXMLLoader;
|
||||
//import javafx.scene.Scene;
|
||||
//import javafx.scene.image.Image;
|
||||
//import javafx.stage.Stage;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//
|
||||
//public class TelegramApplication extends Application {
|
||||
// @Override
|
||||
// public void start(Stage stage) throws IOException {
|
||||
// FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/main.fxml"));
|
||||
// Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
|
||||
//
|
||||
// scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm());
|
||||
//
|
||||
// stage.setTitle("Telegram");
|
||||
// stage.setScene(scene);
|
||||
//
|
||||
// // Add icon to the stage
|
||||
// Image icon = new Image(TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png"));
|
||||
// stage.getIcons().add(icon);
|
||||
//
|
||||
// stage.show();
|
||||
// }
|
||||
// public static void main(String[] args) {
|
||||
// launch();
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
|
||||
import javafx.application.Application;
|
||||
@@ -6,28 +38,31 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.to.telegramfinalproject.UI.AppRouter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class TelegramApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/main.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), 1480, 820);
|
||||
// First: intro.fxml
|
||||
FXMLLoader fx = new FXMLLoader(
|
||||
TelegramApplication.class.getResource("/org/to/telegramfinalproject/Fxml/intro.fxml"));
|
||||
Scene scene = new Scene(fx.load(), 1480, 820);
|
||||
|
||||
scene.getStylesheets().add(getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm());
|
||||
scene.getStylesheets().add(
|
||||
getClass().getResource("/org/to/telegramfinalproject/CSS/light_theme.css").toExternalForm()
|
||||
);
|
||||
|
||||
stage.setTitle("Telegram");
|
||||
stage.getIcons().add(new Image(
|
||||
TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png")
|
||||
));
|
||||
stage.setScene(scene);
|
||||
|
||||
// Add icon to the stage
|
||||
Image icon = new Image(TelegramApplication.class.getResourceAsStream("/org/to/telegramfinalproject/Images/telegram_icon.png"));
|
||||
stage.getIcons().add(icon);
|
||||
|
||||
stage.show();
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
launch();
|
||||
|
||||
AppRouter.init(stage, scene);
|
||||
}
|
||||
|
||||
}
|
||||
public static void main(String[] args) { launch(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user