FEGAD00000000000002lRbf2(K#
z2>-oe1t9$EiWGqGKbE}!g#Wp$1t9#}vK4^v`^i!O!tXD60SJGXqy-@S;gS`A@WUi2
zATWG!$3N!6PdSSkT|h+mrlp!ijh>2_&*#6%S@@!=s_!x+Op&6FfaP-8@ZtDCj`8zf
z$GjXx9Rd8$8*(mo7B%)I$nEOoLQN5(3gGrPho%7&
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/com/starlight/view/community.fxml b/src/main/resources/com/starlight/view/community.fxml
index 7733f9a..fcc1e11 100644
--- a/src/main/resources/com/starlight/view/community.fxml
+++ b/src/main/resources/com/starlight/view/community.fxml
@@ -14,7 +14,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -244,7 +244,7 @@
-
+
@@ -307,7 +307,7 @@
-
+
@@ -370,7 +370,7 @@
-
+
@@ -433,7 +433,7 @@
-
+
diff --git a/src/main/resources/com/starlight/view/editprofile.fxml b/src/main/resources/com/starlight/view/editProfile.fxml
similarity index 100%
rename from src/main/resources/com/starlight/view/editprofile.fxml
rename to src/main/resources/com/starlight/view/editProfile.fxml
diff --git a/src/main/resources/com/starlight/view/myRecipe.fxml b/src/main/resources/com/starlight/view/myRecipe.fxml
new file mode 100644
index 0000000..1daa896
--- /dev/null
+++ b/src/main/resources/com/starlight/view/myRecipe.fxml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/com/starlight/view/postItem.fxml b/src/main/resources/com/starlight/view/postItem.fxml
index b4fbd24..b8bd8c5 100644
--- a/src/main/resources/com/starlight/view/postItem.fxml
+++ b/src/main/resources/com/starlight/view/postItem.fxml
@@ -9,11 +9,11 @@
-
+
-
+
@@ -63,7 +63,7 @@
-
+
diff --git a/src/main/resources/com/starlight/view/profile.fxml b/src/main/resources/com/starlight/view/profile.fxml
new file mode 100644
index 0000000..5adec1b
--- /dev/null
+++ b/src/main/resources/com/starlight/view/profile.fxml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 848af4518f72290d533850f50bec05657757bd9a Mon Sep 17 00:00:00 2001
From: ZanDev32 <52073144+ZanDev32@users.noreply.github.com>
Date: Thu, 17 Jul 2025 18:32:21 +0700
Subject: [PATCH 14/45] Refactor and update community and profile views; remove
unused FXML files and update XML data structure
- Removed all posts from PostData.xml to reset data.
- Updated uploadtime format in PostDataDummy.xml for consistency.
- Modified community.fxml to add event handling for profile button and adjusted layout constraints.
- Deleted editProfile.fxml and myRecipe.fxml as they are no longer needed.
- Updated profile.fxml to fix layout issues and add event handling for profile image.
- Added RecipeItemController.java to manage recipe item interactions.
- Created editAccount.fxml for user account editing functionality.
- Introduced recipeItem.fxml for displaying individual recipe items with proper layout.
---
.../controller/CommunityController.java | 204 +++-----------
.../controller/EditProfileController.java | 93 ++++++-
.../starlight/controller/MainController.java | 6 +
.../controller/MyRecipeController.java | 33 ---
.../controller/NavbarController.java | 2 +-
.../controller/ProfileController.java | 256 +++++++++++++++++-
.../controller/RecipeItemController.java | 53 ++++
.../java/com/starlight/models/PostData.xml | 35 +--
.../com/starlight/models/PostDataDummy.xml | 6 +-
.../com/starlight/view/community.fxml | 9 +-
.../{editProfile.fxml => editAccount.fxml} | 4 +-
.../resources/com/starlight/view/profile.fxml | 87 +-----
.../view/{myRecipe.fxml => recipeItem.fxml} | 14 +-
13 files changed, 469 insertions(+), 333 deletions(-)
delete mode 100644 src/main/java/com/starlight/controller/MyRecipeController.java
create mode 100644 src/main/java/com/starlight/controller/RecipeItemController.java
rename src/main/resources/com/starlight/view/{editProfile.fxml => editAccount.fxml} (93%)
rename src/main/resources/com/starlight/view/{myRecipe.fxml => recipeItem.fxml} (84%)
diff --git a/src/main/java/com/starlight/controller/CommunityController.java b/src/main/java/com/starlight/controller/CommunityController.java
index 16c6455..f405ed5 100644
--- a/src/main/java/com/starlight/controller/CommunityController.java
+++ b/src/main/java/com/starlight/controller/CommunityController.java
@@ -15,21 +15,16 @@
import com.starlight.models.UserDataRepository;
import io.github.palexdev.materialfx.controls.MFXButton;
-import javafx.animation.ScaleTransition;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
-import javafx.stage.Modality;
-import javafx.stage.Stage;
-import javafx.util.Duration;
/**
* Controller backing the community page. It displays recent posts and allows
@@ -54,9 +49,6 @@ public class CommunityController implements Initializable {
@FXML
private GridPane manu;
- @FXML
- private MFXButton createpost;
-
@FXML
private ImageView dailyphoto1;
@@ -105,17 +97,27 @@ public class CommunityController implements Initializable {
@FXML
private Label dailylikecounter4;
-
@FXML
private VBox postlist;
+ private MainController main;
+
private final PostDataRepository repository = new PostDataRepository();
private final UserDataRepository userRepository = new UserDataRepository();
+ public void setMainController(MainController main) {
+ this.main = main;
+ }
+
+ @FXML
+ void toProfile(MouseEvent event) {
+ main.loadPage("profile");
+ }
+
/**
* Gets the profile picture path for a given username from UserData.xml
*/
- private String getProfilePictureForUser(String username) {
+ public String getProfilePictureForUser(String username) {
if (username == null) return null;
List users = userRepository.loadUsers();
@@ -130,7 +132,7 @@ private String getProfilePictureForUser(String username) {
/**
* Gets the display username from UserData.xml for a given username
*/
- private String getDisplayUsernameForUser(String username) {
+ public String getDisplayUsernameForUser(String username) {
if (username == null) return null;
List users = userRepository.loadUsers();
@@ -149,7 +151,7 @@ private String getDisplayUsernameForUser(String username) {
* while preserving aspect ratio and applying rounded corners.
* This method scales the image to fill the frame, cropping it if necessary.
*/
- private void scaleToFit(ImageView imageView, double frameWidth, double frameHeight, double arcRadius) {
+ public void scaleToFit(ImageView imageView, double frameWidth, double frameHeight, double arcRadius) {
if (imageView.getImage() == null) return;
Image image = imageView.getImage();
@@ -190,111 +192,6 @@ private void scaleToFit(ImageView imageView, double frameWidth, double frameHeig
imageView.setClip(clip);
}
- /**
- * Overloaded method that resizes and centers the given {@link ImageView} to fit inside the specified frame size
- * while preserving aspect ratio and applying rounded corners with individual corner control.
- * This method scales the image to fill the frame, cropping it if necessary.
- *
- * @param imageView the ImageView to resize and crop
- * @param frameWidth the target width of the frame
- * @param frameHeight the target height of the frame
- * @param topLeftRadius the arc radius for the top-left corner
- * @param topRightRadius the arc radius for the top-right corner
- * @param bottomLeftRadius the arc radius for the bottom-left corner
- * @param bottomRightRadius the arc radius for the bottom-right corner
- */
- private void scaleToFit(ImageView imageView, double frameWidth, double frameHeight,
- double topLeftRadius, double topRightRadius,
- double bottomLeftRadius, double bottomRightRadius) {
- if (imageView.getImage() == null) return;
- Image image = imageView.getImage();
-
- double imageWidth = image.getWidth();
- double imageHeight = image.getHeight();
-
- // Set the ImageView size to match the frame
- imageView.setFitWidth(frameWidth);
- imageView.setFitHeight(frameHeight);
-
- // Calculate aspect ratios
- double imageAspect = imageWidth / imageHeight;
- double frameAspect = frameWidth / frameHeight;
-
- double newWidth, newHeight;
- double xOffset = 0, yOffset = 0;
-
- // Determine the cropping area
- if (imageAspect > frameAspect) {
- // Image is wider than the frame, so we crop the sides
- newHeight = imageHeight;
- newWidth = newHeight * frameAspect;
- xOffset = (imageWidth - newWidth) / 2;
- } else {
- // Image is taller than the frame, so we crop the top and bottom
- newWidth = imageWidth;
- newHeight = newWidth / frameAspect;
- yOffset = (imageHeight - newHeight) / 2;
- }
-
- // Set the viewport to the centered, cropped portion of the image
- imageView.setViewport(new javafx.geometry.Rectangle2D(xOffset, yOffset, newWidth, newHeight));
-
- // Create a custom clip with individual corner radii using a rounded rectangle approach
- javafx.scene.shape.Path clipPath = new javafx.scene.shape.Path();
-
- // Move to starting point (top-left, accounting for radius)
- clipPath.getElements().add(new javafx.scene.shape.MoveTo(topLeftRadius, 0));
-
- // Top edge to top-right corner
- clipPath.getElements().add(new javafx.scene.shape.LineTo(frameWidth - topRightRadius, 0));
-
- // Top-right corner arc
- if (topRightRadius > 0) {
- clipPath.getElements().add(new javafx.scene.shape.QuadCurveTo(
- frameWidth, 0, frameWidth, topRightRadius));
- } else {
- clipPath.getElements().add(new javafx.scene.shape.LineTo(frameWidth, 0));
- }
-
- // Right edge to bottom-right corner
- clipPath.getElements().add(new javafx.scene.shape.LineTo(frameWidth, frameHeight - bottomRightRadius));
-
- // Bottom-right corner arc
- if (bottomRightRadius > 0) {
- clipPath.getElements().add(new javafx.scene.shape.QuadCurveTo(
- frameWidth, frameHeight, frameWidth - bottomRightRadius, frameHeight));
- } else {
- clipPath.getElements().add(new javafx.scene.shape.LineTo(frameWidth, frameHeight));
- }
-
- // Bottom edge to bottom-left corner
- clipPath.getElements().add(new javafx.scene.shape.LineTo(bottomLeftRadius, frameHeight));
-
- // Bottom-left corner arc
- if (bottomLeftRadius > 0) {
- clipPath.getElements().add(new javafx.scene.shape.QuadCurveTo(
- 0, frameHeight, 0, frameHeight - bottomLeftRadius));
- } else {
- clipPath.getElements().add(new javafx.scene.shape.LineTo(0, frameHeight));
- }
-
- // Left edge to top-left corner
- clipPath.getElements().add(new javafx.scene.shape.LineTo(0, topLeftRadius));
-
- // Top-left corner arc
- if (topLeftRadius > 0) {
- clipPath.getElements().add(new javafx.scene.shape.QuadCurveTo(
- 0, 0, topLeftRadius, 0));
- } else {
- clipPath.getElements().add(new javafx.scene.shape.LineTo(0, 0));
- }
-
- // Close the path
- clipPath.getElements().add(new javafx.scene.shape.ClosePath());
-
- imageView.setClip(clipPath);
- }
-
/**
* Loads posts from the repository and populates the UI. All posts are
* added to the postlist container.
@@ -378,14 +275,14 @@ private void loadDailyPosts() {
starrating3.setText(post.rating);
dailylikecounter3.setText(post.likecount);
loadImage(dailyphoto3, post.image, "/com/starlight/images/missing.png");
- scaleToFit(dailyphoto3, 280, 200, 30, 30, 0, 0);
+ scaleToFit(dailyphoto3, 280, 200, 30);
break;
case 3:
dailytitle4.setText(post.title);
starrating4.setText(post.rating);
dailylikecounter4.setText(post.likecount);
loadImage(dailyphoto4, post.image, "/com/starlight/images/missing.png");
- scaleToFit(dailyphoto4, 280, 200, 30, 30, 0, 0);
+ scaleToFit(dailyphoto4, 280, 200, 30);
break;
}
}
@@ -399,7 +296,7 @@ private void loadDailyPosts() {
* @param path the path to the image (can be classpath-relative or absolute)
* @param fallbackPath the classpath-relative path to a fallback image
*/
- private void loadImage(ImageView imageView, String path, String fallbackPath) {
+ public void loadImage(ImageView imageView, String path, String fallbackPath) {
Image imageToLoad = null;
if (path != null && !path.trim().isEmpty()) {
// Check if it's a classpath resource
@@ -438,49 +335,6 @@ public void initialize(URL url, ResourceBundle rb) {
loadDailyPosts();
}
- @FXML
- private void handleCreatePost() {
- showCreatePostPopup();
- }
-
- /**
- * Displays a modal dialog that allows the user to create a new post.
- */
- private void showCreatePostPopup() {
- try {
- FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/starlight/view/createPost.fxml"));
- Parent popupRoot = loader.load();
- CreatePostController controller = loader.getController();
-
- popupRoot.setScaleX(0.7);
- popupRoot.setScaleY(0.7);
-
- Scene popupScene = new Scene(popupRoot);
- Stage popupStage = new Stage();
- popupStage.initModality(Modality.APPLICATION_MODAL);
- popupStage.setScene(popupScene);
- popupStage.setTitle("Create Post");
- popupStage.setResizable(false);
-
- popupStage.setOnShown(e -> {
- ScaleTransition st = new ScaleTransition(Duration.millis(220), popupRoot);
- st.setFromX(0.7);
- st.setFromY(0.7);
- st.setToX(1.0);
- st.setToY(1.0);
- st.play();
- });
-
- popupStage.showAndWait();
- if (controller.isSuccess()) {
- loadPosts();
- loadDailyPosts();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
/**
* Formats a timestamp string into a relative time format like "2h ago", "1d ago"
*/
@@ -490,9 +344,25 @@ private String formatRelativeTime(String timeString) {
}
try {
- // Handle the format used in XML: "2025-07-11 17:12:55"
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- LocalDateTime postTime = LocalDateTime.parse(timeString.trim(), formatter);
+ LocalDateTime postTime;
+ String trimmedTime = timeString.trim();
+
+ // Try different formats to handle various timestamp formats
+ try {
+ // First try format with seconds: "2025-07-11 17:12:55"
+ DateTimeFormatter formatterWithSeconds = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ postTime = LocalDateTime.parse(trimmedTime, formatterWithSeconds);
+ } catch (Exception e1) {
+ try {
+ // Then try format without seconds: "2025-07-11 17:12"
+ DateTimeFormatter formatterWithoutSeconds = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+ postTime = LocalDateTime.parse(trimmedTime, formatterWithoutSeconds);
+ } catch (Exception e2) {
+ // If both fail, return original string
+ return timeString;
+ }
+ }
+
long hours = ChronoUnit.HOURS.between(postTime, LocalDateTime.now());
if (hours < 1) return "just now";
diff --git a/src/main/java/com/starlight/controller/EditProfileController.java b/src/main/java/com/starlight/controller/EditProfileController.java
index 160eae5..a4681e2 100644
--- a/src/main/java/com/starlight/controller/EditProfileController.java
+++ b/src/main/java/com/starlight/controller/EditProfileController.java
@@ -1,5 +1,6 @@
package com.starlight.controller;
+import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
@@ -16,6 +17,7 @@
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
+import javafx.stage.FileChooser;
import javafx.stage.Stage;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
@@ -25,10 +27,13 @@
/**
* Controller for the user settings dialog where profile information can be
* edited.
+ *
+ * Architecture: Uses CommunityController's public utility methods for image loading
+ * and scaling to maintain consistency and reduce code redundancy across the application.
*/
public class EditProfileController implements Initializable {
@FXML
- private ImageView profileimage;
+ private ImageView Image;
@FXML
private MFXButton imagepicker;
@@ -54,6 +59,9 @@ public class EditProfileController implements Initializable {
private User currentUser;
private Runnable onLogout;
private Stage previousStage;
+
+ // Use CommunityController for shared utility methods (image loading and scaling)
+ private final CommunityController communityController = new CommunityController();
public void setUser(User user) {
this.currentUser = user;
@@ -63,6 +71,12 @@ public void setUser(User user) {
if (birthDayPicker != null && user.birthDay != null && !user.birthDay.isEmpty()) {
birthDayPicker.setValue(java.time.LocalDate.parse(user.birthDay));
}
+
+ // Load and scale profile image using CommunityController methods
+ if (Image != null) {
+ communityController.loadImage(Image, user.profilepicture, "/com/starlight/images/missing.png");
+ communityController.scaleToFit(Image, 170, 170, 200);
+ }
}
public void setOnLogout(Runnable onLogout) {
@@ -77,6 +91,77 @@ public void setOnLogout(Runnable onLogout) {
public void setPreviousStage(Stage stage) {
this.previousStage = stage;
}
+
+ /**
+ * Loads the current user's profile image from UserData XML using CommunityController methods
+ */
+ private void loadCurrentUserProfileImage() {
+ User currentSessionUser = Session.getCurrentUser();
+ if (currentSessionUser != null && Image != null) {
+ // Load profile image using CommunityController for consistency
+ communityController.loadImage(Image, currentSessionUser.profilepicture, "/com/starlight/images/missing.png");
+ communityController.scaleToFit(Image, 170, 170, 85); // Circular profile image with rounded corners
+ }
+ }
+
+ /**
+ * Handles the image picker button click to allow user to select a new profile image
+ */
+ @FXML
+ private void onImagePickerClick(javafx.event.ActionEvent event) {
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle("Select Profile Image");
+
+ // Set extension filters for image files
+ FileChooser.ExtensionFilter imageFilter = new FileChooser.ExtensionFilter(
+ "Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp");
+ fileChooser.getExtensionFilters().add(imageFilter);
+
+ // Show the file chooser dialog
+ Stage stage = (Stage) imagepicker.getScene().getWindow();
+ File selectedFile = fileChooser.showOpenDialog(stage);
+
+ if (selectedFile != null) {
+ updateProfileImage(selectedFile.getAbsolutePath());
+ }
+ }
+
+ /**
+ * Updates the user's profile image in UserData XML and refreshes the display
+ */
+ private void updateProfileImage(String imagePath) {
+ try {
+ // Update the current user's profile picture path
+ User currentSessionUser = Session.getCurrentUser();
+ if (currentSessionUser != null) {
+ currentSessionUser.profilepicture = imagePath;
+
+ // Also update currentUser if it's set
+ if (currentUser != null) {
+ currentUser.profilepicture = imagePath;
+ }
+
+ // Update the UserData XML file
+ com.starlight.models.UserDataRepository repo = new com.starlight.models.UserDataRepository();
+ java.util.List users = repo.loadUsers();
+ for (com.starlight.models.User u : users) {
+ if (u.username.equals(currentSessionUser.username)) {
+ u.profilepicture = imagePath;
+ break;
+ }
+ }
+ repo.saveUsers(users);
+
+ // Refresh the profile image display
+ loadCurrentUserProfileImage();
+
+ System.out.println("Profile image updated successfully: " + imagePath);
+ }
+ } catch (Exception e) {
+ System.err.println("Failed to update profile image: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
/**
* Wires up the save and cancel buttons for the dialog.
@@ -121,6 +206,9 @@ public void initialize(URL url, ResourceBundle rb) {
if (currentSessionUser != null && welcomeLabel != null) {
welcomeLabel.setText("Hello, " + currentSessionUser.username);
}
+
+ // Load the current user's profile image
+ loadCurrentUserProfileImage();
}
@FXML
@@ -147,6 +235,9 @@ private void onUpdate(javafx.event.ActionEvent event) {
currentUser.email = email;
currentUser.password = password;
currentUser.birthDay = birthDay != null ? birthDay.toString() : null;
+
+ // Refresh the profile image in case it was updated
+ loadCurrentUserProfileImage();
}
@FXML
diff --git a/src/main/java/com/starlight/controller/MainController.java b/src/main/java/com/starlight/controller/MainController.java
index 0502fa9..a10f7dc 100644
--- a/src/main/java/com/starlight/controller/MainController.java
+++ b/src/main/java/com/starlight/controller/MainController.java
@@ -141,6 +141,12 @@ protected Parent call() throws Exception {
Session.getCurrentUser()
);
}
+ if (controller instanceof CommunityController) {
+ ((CommunityController) controller).setMainController(MainController.this);
+ }
+ if (controller instanceof ProfileController) {
+ ((ProfileController) controller).setMainController(MainController.this);
+ }
return root;
}
diff --git a/src/main/java/com/starlight/controller/MyRecipeController.java b/src/main/java/com/starlight/controller/MyRecipeController.java
deleted file mode 100644
index 9a05ec0..0000000
--- a/src/main/java/com/starlight/controller/MyRecipeController.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.starlight.controller;
-
-import io.github.palexdev.materialfx.controls.MFXButton;
-import javafx.fxml.FXML;
-import javafx.scene.control.Label;
-import javafx.scene.image.ImageView;
-
-public class MyRecipeController {
- @FXML
- private ImageView RecipePhoto;
-
- @FXML
- private MFXButton editRecipe;
-
- @FXML
- private MFXButton deletePost;
-
- @FXML
- private Label RecipeTitle;
-
- @FXML
- private Label starrating;
-
- @FXML
- private Label likecounter;
-
-
-
- @FXML
- private void initialize() {
- // Hint: initialize() will be called when the associated FXML has been completely loaded.
- }
-}
diff --git a/src/main/java/com/starlight/controller/NavbarController.java b/src/main/java/com/starlight/controller/NavbarController.java
index ee80b65..385c04b 100644
--- a/src/main/java/com/starlight/controller/NavbarController.java
+++ b/src/main/java/com/starlight/controller/NavbarController.java
@@ -36,7 +36,7 @@ public void setMainController(MainController main) {
@FXML
void profile(MouseEvent event) {
- main.loadPage("profile");
+ main.loadPage("editAccount");
}
@FXML
diff --git a/src/main/java/com/starlight/controller/ProfileController.java b/src/main/java/com/starlight/controller/ProfileController.java
index eae5fa4..2abed93 100644
--- a/src/main/java/com/starlight/controller/ProfileController.java
+++ b/src/main/java/com/starlight/controller/ProfileController.java
@@ -1,6 +1,260 @@
package com.starlight.controller;
-public class ProfileController {
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.ResourceBundle;
+import com.starlight.models.Post;
+import com.starlight.models.PostDataRepository;
+import com.starlight.models.User;
+import com.starlight.util.Session;
+import io.github.palexdev.materialfx.controls.MFXButton;
+import io.github.palexdev.materialfx.controls.MFXScrollPane;
+import javafx.animation.ScaleTransition;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+/**
+ * Controller backing the profile page. It displays user profile information and user's recipes/posts.
+ *
+ * Architecture: This controller uses dependency injection pattern by utilizing CommunityController's
+ * public utility methods to reduce code redundancy and maintain consistency across the application.
+ * Instead of duplicating image loading, scaling, and user data retrieval logic, we delegate these
+ * operations to the CommunityController which serves as a shared utility provider.
+ */
+public class ProfileController implements Initializable {
+ @FXML
+ private MFXButton editBio;
+
+ @FXML
+ private MFXButton addRecipe;
+
+ @FXML
+ private VBox badge;
+
+ @FXML
+ private ImageView badgeICon;
+
+ @FXML
+ private Label badgeTitle;
+
+ @FXML
+ private MFXButton swapToCommunity;
+
+ @FXML
+ private MFXScrollPane myrepiceList;
+
+ @FXML
+ private ImageView profile;
+
+ @FXML
+ private Label username;
+
+ @FXML
+ private Label bio;
+
+ @FXML
+ private Label recipes;
+
+ @FXML
+ private Label followers;
+
+ @FXML
+ private Label following;
+
+ @SuppressWarnings("unused")
+ private MainController main;
+
+ private final PostDataRepository repository = new PostDataRepository();
+
+ // Use CommunityController for shared utility methods to reduce code redundancy
+ private final CommunityController communityController = new CommunityController();
+
+ public void setMainController(MainController main) {
+ this.main = main;
+ // Also set the main controller for the community controller if needed
+ communityController.setMainController(main);
+ }
+
+ @FXML
+ void swapToCommunity(MouseEvent event) {
+ main.loadPage("community");
+ }
+
+ /**
+ * Loads the current user's profile information using CommunityController's methods
+ */
+ private void loadUserProfile() {
+ User currentUser = Session.getCurrentUser();
+ if (currentUser != null) {
+ // Set username using display name logic from CommunityController
+ if (username != null) {
+ String displayName = communityController.getDisplayUsernameForUser(currentUser.username);
+ username.setText(displayName != null ? displayName : currentUser.username);
+ }
+
+ // Load profile picture using CommunityController's image loading methods
+ if (profile != null) {
+ communityController.loadImage(profile, currentUser.profilepicture, "/com/starlight/images/missing.png");
+ communityController.scaleToFit(profile, 170, 170, 200); // Adjust size as needed
+ }
+
+ // Set followers and following counts (placeholder values since not in User model)
+ if (followers != null) {
+ followers.setText("125"); // Placeholder - could be calculated from user relationships
+ }
+
+ if (following != null) {
+ following.setText("89"); // Placeholder - could be calculated from user relationships
+ }
+
+ // Set bio if available (though not in current User model, prepare for future)
+ if (bio != null) {
+ bio.setText("Food enthusiast and recipe creator"); // Placeholder bio
+ }
+ }
+ }
+
+ /**
+ * Loads and displays the current user's recipes/posts using CommunityController's approach
+ */
+ private void loadUserRecipes() {
+ if (myrepiceList == null) return;
+
+ User currentUser = Session.getCurrentUser();
+ if (currentUser == null) return;
+
+ List allPosts = repository.loadPosts();
+ if (allPosts == null || allPosts.isEmpty()) {
+ // Clear the container if no posts
+ myrepiceList.setContent(new HBox());
+ if (recipes != null) {
+ recipes.setText("0");
+ }
+ return;
+ }
+
+ // Filter posts by current user
+ List userPosts = allPosts.stream()
+ .filter(post -> currentUser.username.equals(post.username))
+ .toList();
+
+ if (userPosts.isEmpty()) {
+ // Clear the container if no posts
+ myrepiceList.setContent(new HBox());
+ if (recipes != null) {
+ recipes.setText("0");
+ }
+ return;
+ }
+
+ // Create horizontal layout for recipes
+ HBox recipeContainer = new HBox();
+ recipeContainer.setSpacing(20);
+
+ for (Post post : userPosts) {
+ String title = post.title;
+ String image = post.image;
+ String likes = post.likecount;
+ String rating = post.rating;
+
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/starlight/view/recipeItem.fxml"));
+ GridPane recipeNode = loader.load();
+ RecipeItemController controller = loader.getController();
+
+ // Set the recipe data using the new setter method
+ controller.setRecipeData(title, rating != null ? rating : "0.0", likes != null ? likes : "0");
+
+ // Load recipe image using CommunityController's image loading methods
+ ImageView recipeImageView = controller.getImageView();
+ if (recipeImageView != null) {
+ communityController.loadImage(recipeImageView, image, "/com/starlight/images/missing.png");
+ communityController.scaleToFit(recipeImageView, 280, 200, 20); // Adjust size for recipe item
+ }
+
+ recipeContainer.getChildren().add(recipeNode);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ myrepiceList.setContent(recipeContainer);
+
+ // Update recipe count
+ if (recipes != null) {
+ recipes.setText(String.valueOf(userPosts.size()));
+ }
+ }
+
+ /**
+ * Initializes the controller by ensuring dummy data and loading profile and recipes.
+ */
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+ repository.ensureDummyData();
+ loadUserProfile();
+ loadUserRecipes();
+
+ // Connect addRecipe button to handleCreatePost method
+ if (addRecipe != null) {
+ addRecipe.setOnAction(e -> handleCreatePost());
+ }
+ }
+
+ @FXML
+ private void handleCreatePost() {
+ showCreatePostPopup();
+ }
+
+ /**
+ * Displays a modal dialog that allows the user to create a new post.
+ */
+ private void showCreatePostPopup() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/starlight/view/createPost.fxml"));
+ Parent popupRoot = loader.load();
+ CreatePostController controller = loader.getController();
+
+ popupRoot.setScaleX(0.7);
+ popupRoot.setScaleY(0.7);
+
+ Scene popupScene = new Scene(popupRoot);
+ Stage popupStage = new Stage();
+ popupStage.initModality(Modality.APPLICATION_MODAL);
+ popupStage.setScene(popupScene);
+ popupStage.setTitle("Create Post");
+ popupStage.setResizable(false);
+
+ popupStage.setOnShown(e -> {
+ ScaleTransition st = new ScaleTransition(Duration.millis(220), popupRoot);
+ st.setFromX(0.7);
+ st.setFromY(0.7);
+ st.setToX(1.0);
+ st.setToY(1.0);
+ st.play();
+ });
+
+ popupStage.showAndWait();
+ if (controller.isSuccess()) {
+ loadUserRecipes(); // Reload user recipes after creating new post
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/src/main/java/com/starlight/controller/RecipeItemController.java b/src/main/java/com/starlight/controller/RecipeItemController.java
new file mode 100644
index 0000000..e8eb146
--- /dev/null
+++ b/src/main/java/com/starlight/controller/RecipeItemController.java
@@ -0,0 +1,53 @@
+package com.starlight.controller;
+
+import io.github.palexdev.materialfx.controls.MFXButton;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.image.ImageView;
+
+public class RecipeItemController {
+ @FXML
+ private ImageView image;
+
+ @FXML
+ private Label title;
+
+ @FXML
+ private Label rating;
+
+ @FXML
+ private Label likecount;
+
+ @FXML
+ private MFXButton editRecipe;
+
+ @FXML
+ private MFXButton deletePost;
+
+ @FXML
+ private void initialize() {
+
+ }
+
+ /**
+ * Sets the recipe data for this recipe item
+ */
+ public void setRecipeData(String title, String rating, String likecount) {
+ if (this.title != null) {
+ this.title.setText(title);
+ }
+ if (this.rating != null) {
+ this.rating.setText(rating);
+ }
+ if (this.likecount != null) {
+ this.likecount.setText(likecount);
+ }
+ }
+
+ /**
+ * Gets the image view for external image loading
+ */
+ public ImageView getImageView() {
+ return image;
+ }
+}
diff --git a/src/main/java/com/starlight/models/PostData.xml b/src/main/java/com/starlight/models/PostData.xml
index 9e135d8..d8a9c28 100644
--- a/src/main/java/com/starlight/models/PostData.xml
+++ b/src/main/java/com/starlight/models/PostData.xml
@@ -1,36 +1,3 @@
-
- meko
- src/main/resources/com/starlight/images/dummy/2.png
- asdasd
- asdasd
- /home/zan/Pictures/hinanawi-tenshi-fumo.png
- 0.0
- 2025-07-11 17:12:55
- 0
-
-
- meko
- src/main/resources/com/starlight/images/dummy/2.png
- Doro goreng
- asdasda
- /home/zan/Pictures/Screenshots/Screenshot from 2025-07-04 14:40:47 WIB.png
- 0.0
- 2025-07-11 17:12:20
- 0
-
-
- Khairun Nissa
- Veggie Salad
- Fresh vegetable salad.
- - lettuce
-- tomato
-- cucumber
- Combine all veggies.
-Serve chilled.
- src/main/resources/com/starlight/images/dummy/image_2.jpg
- 4.8
- 2023-11-23 11:00
- 8
-
+
\ No newline at end of file
diff --git a/src/main/java/com/starlight/models/PostDataDummy.xml b/src/main/java/com/starlight/models/PostDataDummy.xml
index 9d60279..78ac8bd 100644
--- a/src/main/java/com/starlight/models/PostDataDummy.xml
+++ b/src/main/java/com/starlight/models/PostDataDummy.xml
@@ -10,7 +10,7 @@
Cook on skillet.
src/main/resources/com/starlight/images/dummy/recent_1.png
4.5
- 2023-11-22 10:00
+ 2023-11-22 10:00:00
12
@@ -24,7 +24,7 @@ Cook on skillet.
Serve chilled.
src/main/resources/com/starlight/images/dummy/image_2.jpg
4.8
- 2023-11-23 11:00
+ 2023-11-23 11:00:00
8
@@ -35,7 +35,7 @@ Serve chilled.
123344
/home/zan/Documents/main_folder/pfp/52073144.jpg
0
- 2025-06-22 10:09
+ 2025-06-22 10:09:00
0
\ No newline at end of file
diff --git a/src/main/resources/com/starlight/view/community.fxml b/src/main/resources/com/starlight/view/community.fxml
index fcc1e11..8d36653 100644
--- a/src/main/resources/com/starlight/view/community.fxml
+++ b/src/main/resources/com/starlight/view/community.fxml
@@ -14,7 +14,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -33,8 +33,8 @@
-
-
+
+
@@ -226,7 +226,6 @@
-
diff --git a/src/main/resources/com/starlight/view/editProfile.fxml b/src/main/resources/com/starlight/view/editAccount.fxml
similarity index 93%
rename from src/main/resources/com/starlight/view/editProfile.fxml
rename to src/main/resources/com/starlight/view/editAccount.fxml
index 90064d4..ccf7f90 100644
--- a/src/main/resources/com/starlight/view/editProfile.fxml
+++ b/src/main/resources/com/starlight/view/editAccount.fxml
@@ -29,12 +29,12 @@
-
+
-
+
diff --git a/src/main/resources/com/starlight/view/profile.fxml b/src/main/resources/com/starlight/view/profile.fxml
index 5adec1b..eb5ef7d 100644
--- a/src/main/resources/com/starlight/view/profile.fxml
+++ b/src/main/resources/com/starlight/view/profile.fxml
@@ -12,9 +12,8 @@
-
-
+
@@ -39,82 +38,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
@@ -125,7 +54,7 @@
-
+
-
+
@@ -159,7 +88,7 @@
-
+
@@ -228,7 +157,7 @@
-
+
diff --git a/src/main/resources/com/starlight/view/myRecipe.fxml b/src/main/resources/com/starlight/view/recipeItem.fxml
similarity index 84%
rename from src/main/resources/com/starlight/view/myRecipe.fxml
rename to src/main/resources/com/starlight/view/recipeItem.fxml
index 1daa896..644b733 100644
--- a/src/main/resources/com/starlight/view/myRecipe.fxml
+++ b/src/main/resources/com/starlight/view/recipeItem.fxml
@@ -13,16 +13,16 @@
-
+
-
+
-
+
-
+
@@ -66,7 +66,7 @@
-
-
+
@@ -100,7 +100,7 @@
-
+
From 6a37ff0ce9a4657ddaf916f77c78334c58d66534 Mon Sep 17 00:00:00 2001
From: ZanDev32 <52073144+ZanDev32@users.noreply.github.com>
Date: Thu, 17 Jul 2025 20:06:51 +0700
Subject: [PATCH 15/45] Update project structure and enhance file system
management
- Upgraded Maven dependencies: maven-compiler-plugin to 3.11.0 and maven-surefire-plugin to 3.0.0-M9.
- Introduced FileSystemManager for improved file handling, including directory initialization and file copying.
- Refactored image loading in CommunityController to utilize FileSystemManager for resolving image paths.
- Updated CreatePostController to copy images to user-specific directories using FileSystemManager.
- Enhanced EditProfileController to update user profile images with new file management logic.
- Removed obsolete XML files (PostData.xml, UserData.xml) and replaced with a new directory structure.
- Updated UserDataDummy.xml to reflect new user data structure.
- Added integration tests for data repositories and file system access to ensure functionality.
- Improved error handling and logging in file operations.
---
pom.xml | 4 +-
src/main/java/com/starlight/App.java | 3 +
.../controller/CommunityController.java | 45 ++-
.../controller/CreatePostController.java | 34 +-
.../controller/EditProfileController.java | 65 ++--
.../java/com/starlight/models/PostData.xml | 3 -
.../com/starlight/models/PostDataDummy.xml | 41 ---
.../starlight/models/PostDataRepository.java | 4 +-
.../java/com/starlight/models/UserData.xml | 42 ---
.../com/starlight/models/UserDataDummy.xml | 36 +-
.../starlight/models/UserDataRepository.java | 4 +-
.../com/starlight/util/FileSystemManager.java | 337 ++++++++++++++++++
.../com/starlight/view/editAccount.fxml | 2 +-
.../resources/com/starlight/view/profile.fxml | 2 +-
.../util/DataRepositoryIntegrationTest.java | 62 ++++
.../starlight/util/FileSystemAccessTest.java | 183 ++++++++++
16 files changed, 698 insertions(+), 169 deletions(-)
delete mode 100644 src/main/java/com/starlight/models/PostData.xml
delete mode 100644 src/main/java/com/starlight/models/PostDataDummy.xml
delete mode 100644 src/main/java/com/starlight/models/UserData.xml
create mode 100644 src/main/java/com/starlight/util/FileSystemManager.java
create mode 100644 src/test/java/com/starlight/util/DataRepositoryIntegrationTest.java
create mode 100644 src/test/java/com/starlight/util/FileSystemAccessTest.java
diff --git a/pom.xml b/pom.xml
index ff6cf87..f0ba88d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,7 +94,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.8.0
+ 3.11.0
17
@@ -157,7 +157,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 2.22.2
+ 3.0.0-M9
--add-exports=javafx.graphics/com.sun.javafx.application=ALL-UNNAMED
diff --git a/src/main/java/com/starlight/App.java b/src/main/java/com/starlight/App.java
index bf0608a..672df32 100644
--- a/src/main/java/com/starlight/App.java
+++ b/src/main/java/com/starlight/App.java
@@ -8,6 +8,7 @@
import javafx.animation.PauseTransition;
import javafx.util.Duration;
import com.starlight.util.FXMLVerificator;
+import com.starlight.util.FileSystemManager;
import com.starlight.api.UserApiServer;
import java.io.IOException;
@@ -174,6 +175,8 @@ public void stop() throws Exception {
* @param args command line arguments
*/
public static void main(String[] args) {
+ // Initialize application data directory on startup
+ FileSystemManager.initializeAppDataDirectory();
launch();
}
diff --git a/src/main/java/com/starlight/controller/CommunityController.java b/src/main/java/com/starlight/controller/CommunityController.java
index f405ed5..37731b8 100644
--- a/src/main/java/com/starlight/controller/CommunityController.java
+++ b/src/main/java/com/starlight/controller/CommunityController.java
@@ -3,6 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.net.URL;
+import java.nio.file.Path;
import java.util.List;
import java.util.ResourceBundle;
import java.time.LocalDateTime;
@@ -297,28 +298,54 @@ private void loadDailyPosts() {
* @param fallbackPath the classpath-relative path to a fallback image
*/
public void loadImage(ImageView imageView, String path, String fallbackPath) {
+ // Use FileSystemManager to resolve the image path
+ Path resolvedPath = com.starlight.util.FileSystemManager.resolveImagePath(path);
+
Image imageToLoad = null;
- if (path != null && !path.trim().isEmpty()) {
- // Check if it's a classpath resource
- if (path.startsWith("/")) {
- URL resource = getClass().getResource(path);
+ if (resolvedPath != null) {
+ // Check if it's a special resource marker
+ if (resolvedPath.toString().startsWith("resource:")) {
+ String resourcePath = resolvedPath.toString().substring("resource:".length());
+ URL resource = getClass().getResource(resourcePath);
if (resource != null) {
imageToLoad = new Image(resource.toExternalForm());
}
} else {
// Treat as a file path
- File file = new File(path);
+ File file = resolvedPath.toFile();
if (file.exists()) {
imageToLoad = new Image(file.toURI().toString());
}
}
}
- // If the image failed to load, use the fallback
+ // If the image failed to load, try the fallback
+ if (imageToLoad == null && fallbackPath != null) {
+ Path resolvedFallbackPath = com.starlight.util.FileSystemManager.resolveImagePath(fallbackPath);
+ if (resolvedFallbackPath != null) {
+ if (resolvedFallbackPath.toString().startsWith("resource:")) {
+ String resourcePath = resolvedFallbackPath.toString().substring("resource:".length());
+ URL fallbackResource = getClass().getResource(resourcePath);
+ if (fallbackResource != null) {
+ imageToLoad = new Image(fallbackResource.toExternalForm());
+ }
+ } else {
+ File fallbackFile = resolvedFallbackPath.toFile();
+ if (fallbackFile.exists()) {
+ imageToLoad = new Image(fallbackFile.toURI().toString());
+ }
+ }
+ }
+ }
+
+ // Final fallback - try to use FileSystemManager's fallback system
if (imageToLoad == null) {
- URL fallbackResource = getClass().getResource(fallbackPath);
- if (fallbackResource != null) {
- imageToLoad = new Image(fallbackResource.toExternalForm());
+ String defaultFallback = com.starlight.util.FileSystemManager.getFallbackImagePath("default");
+ if (defaultFallback != null) {
+ File defaultFile = new File(defaultFallback);
+ if (defaultFile.exists()) {
+ imageToLoad = new Image(defaultFile.toURI().toString());
+ }
}
}
diff --git a/src/main/java/com/starlight/controller/CreatePostController.java b/src/main/java/com/starlight/controller/CreatePostController.java
index 0356894..a9010eb 100644
--- a/src/main/java/com/starlight/controller/CreatePostController.java
+++ b/src/main/java/com/starlight/controller/CreatePostController.java
@@ -6,11 +6,6 @@
import java.time.format.DateTimeFormatter;
import java.util.ResourceBundle;
import java.util.List;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.UUID;
import com.starlight.models.Post;
import com.starlight.models.PostDataRepository;
@@ -63,25 +58,20 @@ public class CreatePostController implements Initializable {
private final PostDataRepository repository = new PostDataRepository();
/**
- * Copies the selected image to the user's home data directory.
+ * Copies the selected image to the user's data directory.
*
* @param image the image file selected by the user
- * @return the copied image file
+ * @return the copied image file path
*/
- private File copyImageToUserDir(File image) throws java.io.IOException {
- Path dir = Paths.get(System.getProperty("user.home"), ".tabemonpal", "images");
- Files.createDirectories(dir);
-
- String name = image.getName();
- String ext = "";
- int idx = name.lastIndexOf('.');
- if (idx > 0) {
- ext = name.substring(idx);
+ private String copyImageToUserDir(File image) {
+ try {
+ String username = Session.getCurrentUser() != null ? Session.getCurrentUser().username : "unknown";
+ return com.starlight.util.FileSystemManager.copyFileToUserDirectoryWithUniqueFilename(image, username);
+ } catch (Exception e) {
+ System.err.println("Failed to copy image to user directory: " + e.getMessage());
+ e.printStackTrace();
+ return null;
}
-
- Path target = dir.resolve(UUID.randomUUID().toString() + ext);
- Files.copy(image.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
- return target.toFile();
}
/**
@@ -135,8 +125,8 @@ public void initialize(URL url, ResourceBundle rb) {
newPost.title = postTitle;
newPost.description = postDescription;
try {
- File stored = copyImageToUserDir(selectedImage);
- newPost.image = stored.getAbsolutePath();
+ String storedPath = copyImageToUserDir(selectedImage);
+ newPost.image = storedPath != null ? storedPath : selectedImage.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
newPost.image = selectedImage.getAbsolutePath();
diff --git a/src/main/java/com/starlight/controller/EditProfileController.java b/src/main/java/com/starlight/controller/EditProfileController.java
index a4681e2..a271f02 100644
--- a/src/main/java/com/starlight/controller/EditProfileController.java
+++ b/src/main/java/com/starlight/controller/EditProfileController.java
@@ -122,41 +122,54 @@ private void onImagePickerClick(javafx.event.ActionEvent event) {
File selectedFile = fileChooser.showOpenDialog(stage);
if (selectedFile != null) {
- updateProfileImage(selectedFile.getAbsolutePath());
+ updateProfileImage(selectedFile);
}
}
/**
- * Updates the user's profile image in UserData XML and refreshes the display
+ * Updates the user's profile image in UserData XML and refreshes the display.
+ * Copies the selected file to the user's directory and updates the XML with the new path.
*/
- private void updateProfileImage(String imagePath) {
+ private void updateProfileImage(File selectedFile) {
try {
- // Update the current user's profile picture path
User currentSessionUser = Session.getCurrentUser();
- if (currentSessionUser != null) {
- currentSessionUser.profilepicture = imagePath;
-
- // Also update currentUser if it's set
- if (currentUser != null) {
- currentUser.profilepicture = imagePath;
- }
-
- // Update the UserData XML file
- com.starlight.models.UserDataRepository repo = new com.starlight.models.UserDataRepository();
- java.util.List users = repo.loadUsers();
- for (com.starlight.models.User u : users) {
- if (u.username.equals(currentSessionUser.username)) {
- u.profilepicture = imagePath;
- break;
- }
+ if (currentSessionUser == null) {
+ System.err.println("No current session user found");
+ return;
+ }
+
+ // Copy the selected file to the user's directory
+ String copiedFilePath = com.starlight.util.FileSystemManager.copyFileToUserDirectoryWithUniqueFilename(
+ selectedFile, currentSessionUser.username);
+
+ if (copiedFilePath == null) {
+ System.err.println("Failed to copy image file to user directory");
+ return;
+ }
+
+ // Update the current user's profile picture path
+ currentSessionUser.profilepicture = copiedFilePath;
+
+ // Also update currentUser if it's set
+ if (currentUser != null) {
+ currentUser.profilepicture = copiedFilePath;
+ }
+
+ // Update the UserData XML file
+ com.starlight.models.UserDataRepository repo = new com.starlight.models.UserDataRepository();
+ java.util.List users = repo.loadUsers();
+ for (com.starlight.models.User u : users) {
+ if (u.username.equals(currentSessionUser.username)) {
+ u.profilepicture = copiedFilePath;
+ break;
}
- repo.saveUsers(users);
-
- // Refresh the profile image display
- loadCurrentUserProfileImage();
-
- System.out.println("Profile image updated successfully: " + imagePath);
}
+ repo.saveUsers(users);
+
+ // Refresh the profile image display
+ loadCurrentUserProfileImage();
+
+ System.out.println("Profile image updated successfully: " + copiedFilePath);
} catch (Exception e) {
System.err.println("Failed to update profile image: " + e.getMessage());
e.printStackTrace();
diff --git a/src/main/java/com/starlight/models/PostData.xml b/src/main/java/com/starlight/models/PostData.xml
deleted file mode 100644
index d8a9c28..0000000
--- a/src/main/java/com/starlight/models/PostData.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/src/main/java/com/starlight/models/PostDataDummy.xml b/src/main/java/com/starlight/models/PostDataDummy.xml
deleted file mode 100644
index 78ac8bd..0000000
--- a/src/main/java/com/starlight/models/PostDataDummy.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
- M. Fakih Sahman
- Tasty Pancakes
- Fluffy pancakes with syrup.
- - 1 cup flour
-- 2 eggs
-- 1/2 cup milk
- Mix ingredients.
-Cook on skillet.
- src/main/resources/com/starlight/images/dummy/recent_1.png
- 4.5
- 2023-11-22 10:00:00
- 12
-
-
- Khairun Nissa
- Veggie Salad
- Fresh vegetable salad.
- - lettuce
-- tomato
-- cucumber
- Combine all veggies.
-Serve chilled.
- src/main/resources/com/starlight/images/dummy/image_2.jpg
- 4.8
- 2023-11-23 11:00:00
- 8
-
-
- gw
- qwert
- asdfg
- zxcvb
- 123344
- /home/zan/Documents/main_folder/pfp/52073144.jpg
- 0
- 2025-06-22 10:09:00
- 0
-
-
\ No newline at end of file
diff --git a/src/main/java/com/starlight/models/PostDataRepository.java b/src/main/java/com/starlight/models/PostDataRepository.java
index 2e3cca6..1ee0608 100644
--- a/src/main/java/com/starlight/models/PostDataRepository.java
+++ b/src/main/java/com/starlight/models/PostDataRepository.java
@@ -7,13 +7,13 @@
import java.util.List;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
+import com.starlight.util.FileSystemManager;
/**
* Repository for loading and saving {@link Post} objects to an XML file.
*/
public class PostDataRepository {
- private static final String DATA_DIR = System.getProperty("user.home") + File.separator + ".tabemonpal";
- private static final String DEFAULT_XML_PATH = DATA_DIR + File.separator + "PostData.xml";
+ private static final String DEFAULT_XML_PATH = FileSystemManager.getDatabaseDirectory() + File.separator + "PostData.xml";
private static final String DUMMY_XML_PATH = "src/main/java/com/starlight/models/PostDataDummy.xml";
private final String xmlPath;
diff --git a/src/main/java/com/starlight/models/UserData.xml b/src/main/java/com/starlight/models/UserData.xml
deleted file mode 100644
index 54c0621..0000000
--- a/src/main/java/com/starlight/models/UserData.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
- M. Fakih Sahman
- fakih@example.com
- pancake
- 1990-01-01
- src/main/resources/com/starlight/images/dummy/profileman.jpg
-
-
- Khairun Nissa
- nissa@example.com
- salad
- 1992-05-12
- src/main/resources/com/starlight/images/dummy/profilewoman.jpg
-
-
- gw
- gw@example.com
- qwerty
- 1998-07-30
- src/main/resources/com/starlight/images/dummy/profileman2.jpg
-
-
- fumo
- fumo@example.com
- fumo
- 1999-09-09
- src/main/resources/com/starlight/images/dummy/profiledefault.png
-
-
- meko
- meok@doro.com
- asdfghj
- src/main/resources/com/starlight/images/dummy/profiledefault.png
-
-
- zanzan
- zan@doro.com
- asdfghj
- src/main/resources/com/starlight/images/dummy/profiledefault.png
-
-
\ No newline at end of file
diff --git a/src/main/java/com/starlight/models/UserDataDummy.xml b/src/main/java/com/starlight/models/UserDataDummy.xml
index f1c4184..20cc7c4 100644
--- a/src/main/java/com/starlight/models/UserDataDummy.xml
+++ b/src/main/java/com/starlight/models/UserDataDummy.xml
@@ -1,30 +1,30 @@
+
+ gw
+ gw@example.com
+ GW
+ password123
+ src/main/resources/com/starlight/images/dummy/profileman.jpg
+
M. Fakih Sahman
- pancake
fakih@example.com
- 1990-01-01
- src/main/resources/com/starlight/images/dummy/profileman.jpg
+ M. Fakih Sahman
+ password123
+ src/main/resources/com/starlight/images/dummy/profileman2.jpg
Khairun Nissa
- salad
- nissa@example.com
- 1992-05-12
+ khairun@example.com
+ Khairun Nissa
+ password123
src/main/resources/com/starlight/images/dummy/profilewoman.jpg
- gw
- qwerty
- gw@example.com
- 1998-07-30
- src/main/resources/com/starlight/images/dummy/profileman2.jpg
-
-
- fumo
- fumo
- fumo@example.com
- 1999-09-09
- src/main/resources/com/starlight/images/dummy/profiledefault.png
+ John Doe
+ john@example.com
+ John Doe
+ password123
+ src/main/resources/com/starlight/images/dummy/profileman3.jpg
diff --git a/src/main/java/com/starlight/models/UserDataRepository.java b/src/main/java/com/starlight/models/UserDataRepository.java
index e5ee6db..bf7eea9 100644
--- a/src/main/java/com/starlight/models/UserDataRepository.java
+++ b/src/main/java/com/starlight/models/UserDataRepository.java
@@ -8,13 +8,13 @@
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
+import com.starlight.util.FileSystemManager;
/**
* Repository for persisting {@link User} objects to an XML file.
*/
public class UserDataRepository {
- private static final String DATA_DIR = System.getProperty("user.home") + File.separator + ".tabemonpal";
- private static final String DEFAULT_XML_PATH = DATA_DIR + File.separator + "UserData.xml";
+ private static final String DEFAULT_XML_PATH = FileSystemManager.getDatabaseDirectory() + File.separator + "UserData.xml";
private static final String DUMMY_XML_PATH = "src/main/java/com/starlight/models/UserDataDummy.xml";
private final String xmlPath;
diff --git a/src/main/java/com/starlight/util/FileSystemManager.java b/src/main/java/com/starlight/util/FileSystemManager.java
new file mode 100644
index 0000000..e7ed312
--- /dev/null
+++ b/src/main/java/com/starlight/util/FileSystemManager.java
@@ -0,0 +1,337 @@
+package com.starlight.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+/**
+ * Manages the application's file system operations including directory creation,
+ * file copying, and data migration. Provides cross-platform support for storing
+ * user data and application files in the user's home directory.
+ */
+public class FileSystemManager {
+
+ /** Application data directory path */
+ private static final String APP_DATA_DIR = System.getProperty("user.home") + File.separator + ".tabemonpal";
+
+ /** User data directory for storing user images */
+ private static final String USER_DATA_DIR = APP_DATA_DIR + File.separator + "UserData";
+
+ /** Database directory for storing XML files */
+ private static final String DATABASE_DIR = APP_DATA_DIR + File.separator + "Database";
+
+ /**
+ * Initializes the application data directory structure.
+ * Creates .tabemonpal directory with UserData and Database subdirectories.
+ * This method is cross-platform compatible and works when running as JAR.
+ */
+ public static void initializeAppDataDirectory() {
+ try {
+ // Create main app directory
+ Path appDir = Paths.get(APP_DATA_DIR);
+ Files.createDirectories(appDir);
+
+ // Create UserData directory for user images
+ Path userDataDir = Paths.get(USER_DATA_DIR);
+ Files.createDirectories(userDataDir);
+
+ // Create Database directory for XML files
+ Path databaseDir = Paths.get(DATABASE_DIR);
+ Files.createDirectories(databaseDir);
+
+ System.out.println("App data directory initialized at: " + APP_DATA_DIR);
+
+ } catch (IOException e) {
+ System.err.println("Failed to initialize app data directory: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Gets the path to the application data directory.
+ * @return absolute path to .tabemonpal directory
+ */
+ public static String getAppDataDirectory() {
+ return APP_DATA_DIR;
+ }
+
+ /**
+ * Gets the path to the user data directory for storing user images.
+ * @return absolute path to UserData directory
+ */
+ public static String getUserDataDirectory() {
+ return USER_DATA_DIR;
+ }
+
+ /**
+ * Gets the path to the database directory for storing XML files.
+ * @return absolute path to Database directory
+ */
+ public static String getDatabaseDirectory() {
+ return DATABASE_DIR;
+ }
+
+ /**
+ * Creates a user-specific directory for storing their images.
+ * @param username the username to create directory for
+ * @return the created user directory path
+ */
+ public static String createUserImageDirectory(String username) {
+ try {
+ Path userDir = Paths.get(USER_DATA_DIR, username);
+ Files.createDirectories(userDir);
+ return userDir.toString();
+ } catch (IOException e) {
+ System.err.println("Failed to create user directory for " + username + ": " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Copies a file from source to the user's image directory and returns the new path.
+ * @param sourceFile the source file to copy
+ * @param username the username whose directory to copy to
+ * @param newFileName optional new filename, or null to keep original
+ * @return the path to the copied file in the user directory
+ */
+ public static String copyFileToUserDirectory(File sourceFile, String username, String newFileName) {
+ try {
+ // Ensure user directory exists
+ String userDirPath = createUserImageDirectory(username);
+ if (userDirPath == null) {
+ return null;
+ }
+
+ // Determine target filename
+ String targetFileName = newFileName != null ? newFileName : sourceFile.getName();
+ Path targetPath = Paths.get(userDirPath, targetFileName);
+
+ // Copy file
+ Files.copy(sourceFile.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING);
+
+ System.out.println("File copied to: " + targetPath.toString());
+ return targetPath.toString();
+
+ } catch (IOException e) {
+ System.err.println("Failed to copy file to user directory: " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Copies a file from source to the user's image directory with a unique filename.
+ * @param sourceFile the source file to copy
+ * @param username the username whose directory to copy to
+ * @return the path to the copied file in the user directory
+ */
+ public static String copyFileToUserDirectoryWithUniqueFilename(File sourceFile, String username) {
+ try {
+ // Get file extension
+ String originalName = sourceFile.getName();
+ String extension = "";
+ int lastDot = originalName.lastIndexOf('.');
+ if (lastDot > 0) {
+ extension = originalName.substring(lastDot);
+ }
+
+ // Generate unique filename using timestamp and a counter to ensure uniqueness
+ String userDirPath = createUserImageDirectory(username);
+ if (userDirPath == null) {
+ return null;
+ }
+
+ String uniqueFileName;
+ Path targetPath;
+ int counter = 0;
+
+ do {
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ uniqueFileName = timestamp + (counter > 0 ? "_" + counter : "") + extension;
+ targetPath = Paths.get(userDirPath, uniqueFileName);
+ counter++;
+ } while (Files.exists(targetPath));
+
+ return copyFileToUserDirectory(sourceFile, username, uniqueFileName);
+
+ } catch (Exception e) {
+ System.err.println("Failed to copy file with unique filename: " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the application data directory is accessible and writable.
+ * @return true if the directory is accessible and writable
+ */
+ public static boolean isAppDataDirectoryAccessible() {
+ try {
+ Path appDir = Paths.get(APP_DATA_DIR);
+
+ // Check if directory exists and is writable
+ if (!Files.exists(appDir)) {
+ return false;
+ }
+
+ if (!Files.isWritable(appDir)) {
+ return false;
+ }
+
+ // Try creating a test file
+ Path testFile = appDir.resolve("test_access.tmp");
+ Files.write(testFile, "test".getBytes());
+ Files.deleteIfExists(testFile);
+
+ return true;
+
+ } catch (Exception e) {
+ System.err.println("App data directory is not accessible: " + e.getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Resolves an image path to an absolute path, handling various path formats
+ * @param imagePath The image path from XML (could be relative, absolute, or resource path)
+ * @return Resolved absolute path or null if not found
+ */
+ public static Path resolveImagePath(String imagePath) {
+ if (imagePath == null || imagePath.trim().isEmpty()) {
+ return null;
+ }
+
+ // Handle paths starting with "src/main/resources/" - these are project resource paths
+ if (imagePath.startsWith("src/main/resources/")) {
+ Path projectResourcesPath = Paths.get(imagePath);
+ if (Files.exists(projectResourcesPath)) {
+ return projectResourcesPath;
+ }
+
+ // Try to convert to classpath resource path and resolve
+ String resourcePath = imagePath.substring("src/main/resources".length());
+ URL resource = FileSystemManager.class.getResource(resourcePath);
+ if (resource != null) {
+ try {
+ return Paths.get(resource.toURI());
+ } catch (Exception e) {
+ // Resource exists but cannot convert to path (might be in JAR)
+ // Return a special marker indicating it's a valid resource
+ return Paths.get("resource:" + resourcePath);
+ }
+ }
+
+ System.out.println("Warning: Could not resolve image path: " + imagePath);
+ return null;
+ }
+
+ // Handle absolute paths (including user directory paths)
+ Path absolutePath = Paths.get(imagePath);
+ if (absolutePath.isAbsolute()) {
+ if (Files.exists(absolutePath)) {
+ return absolutePath;
+ } else {
+ System.out.println("Warning: Could not resolve absolute path: " + imagePath);
+ return null;
+ }
+ }
+
+ // Handle resource paths (paths starting with /)
+ if (imagePath.startsWith("/")) {
+ // Try to resolve as resource from classpath
+ URL resource = FileSystemManager.class.getResource(imagePath);
+ if (resource != null) {
+ try {
+ return Paths.get(resource.toURI());
+ } catch (Exception e) {
+ // Resource exists but cannot convert to path (might be in JAR)
+ // Return a special marker indicating it's a valid resource
+ return Paths.get("resource:" + imagePath);
+ }
+ }
+
+ // If not found as resource, try relative to project src/main/resources
+ String resourcePath = imagePath.substring(1);
+ Path projectResourcesPath = Paths.get("src/main/resources").resolve(resourcePath);
+ if (Files.exists(projectResourcesPath)) {
+ return projectResourcesPath;
+ }
+
+ System.out.println("Warning: Could not resolve resource path: " + imagePath);
+ return null;
+ }
+
+ // Handle relative paths - try different base directories
+
+ // Try relative to user data directory
+ Path userDataPath = Paths.get(getUserDataDirectory()).resolve(imagePath);
+ if (Files.exists(userDataPath)) {
+ return userDataPath;
+ }
+
+ // Try relative to user images directory (UserData subdirectory)
+ Path userImagesPath = Paths.get(getUserDataDirectory()).resolve(imagePath);
+ if (Files.exists(userImagesPath)) {
+ return userImagesPath;
+ }
+
+ // Try relative to project resources
+ Path projectResourcesPath = Paths.get("src/main/resources").resolve(imagePath);
+ if (Files.exists(projectResourcesPath)) {
+ return projectResourcesPath;
+ }
+
+ System.out.println("Warning: Could not resolve image path: " + imagePath);
+ return null;
+ }
+
+ /**
+ * Gets a fallback image path for when the original image cannot be found.
+ * @param imageType type of image (e.g., "profile", "post", "default")
+ * @return path to a fallback image, or null if no fallback available
+ */
+ public static String getFallbackImagePath(String imageType) {
+ String[] fallbackPaths = {
+ "src/main/resources/com/starlight/images/dummy/profileman.jpg",
+ "src/main/resources/com/starlight/images/dummy/2.png",
+ "src/main/resources/com/starlight/images/dummy/recent_1.png",
+ "src/main/resources/com/starlight/images/dummy/image_2.jpg"
+ };
+
+ for (String fallbackPath : fallbackPaths) {
+ Path resolvedPath = resolveImagePath(fallbackPath);
+ if (resolvedPath != null) {
+ return resolvedPath.toString();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Resolves an image path with automatic fallback to default images.
+ * @param imagePath the original image path
+ * @param imageType the type of image for fallback selection
+ * @return a valid image path or fallback path
+ */
+ public static String resolveImagePathWithFallback(String imagePath, String imageType) {
+ Path resolvedPath = resolveImagePath(imagePath);
+ if (resolvedPath != null) {
+ return resolvedPath.toString();
+ }
+
+ String fallbackPath = getFallbackImagePath(imageType);
+ if (fallbackPath != null) {
+ System.out.println("Using fallback image for: " + imagePath);
+ return fallbackPath;
+ }
+
+ System.err.println("No image found for path: " + imagePath);
+ return null;
+ }
+}
diff --git a/src/main/resources/com/starlight/view/editAccount.fxml b/src/main/resources/com/starlight/view/editAccount.fxml
index ccf7f90..05bebb8 100644
--- a/src/main/resources/com/starlight/view/editAccount.fxml
+++ b/src/main/resources/com/starlight/view/editAccount.fxml
@@ -14,7 +14,7 @@
-
+
diff --git a/src/main/resources/com/starlight/view/profile.fxml b/src/main/resources/com/starlight/view/profile.fxml
index eb5ef7d..ae88dea 100644
--- a/src/main/resources/com/starlight/view/profile.fxml
+++ b/src/main/resources/com/starlight/view/profile.fxml
@@ -157,7 +157,7 @@