-
-
\ No newline at end of file
From 7481f5b63703762d36f9266aa287a08e03966b57 Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Sun, 10 Nov 2024 17:37:49 -0500
Subject: [PATCH 006/138] Update entity and add login use cases
---
src/main/java/entity/UserFactory.java | 17 ++++++++
.../ChangePasswordInputBoundary.java | 14 ++++++
.../ChangePasswordInputData.java | 24 +++++++++++
.../ChangePasswordInteractor.java | 32 ++++++++++++++
.../ChangePasswordOutputBoundary.java | 18 ++++++++
.../ChangePasswordOutputData.java | 24 +++++++++++
...ChangePasswordUserDataAccessInterface.java | 15 +++++++
.../use_case/login/LoginInputBoundary.java | 13 ++++++
.../java/use_case/login/LoginInputData.java | 24 +++++++++++
.../java/use_case/login/LoginInteractor.java | 40 +++++++++++++++++
.../use_case/login/LoginOutputBoundary.java | 18 ++++++++
.../java/use_case/login/LoginOutputData.java | 20 +++++++++
.../login/LoginUserDataAccessInterface.java | 41 ++++++++++++++++++
.../use_case/logout/LogoutInputBoundary.java | 13 ++++++
.../java/use_case/logout/LogoutInputData.java | 12 ++++++
.../use_case/logout/LogoutInteractor.java | 25 +++++++++++
.../use_case/logout/LogoutOutputBoundary.java | 18 ++++++++
.../use_case/logout/LogoutOutputData.java | 23 ++++++++++
.../logout/LogoutUserDataAccessInterface.java | 19 ++++++++
.../use_case/signup/SignupInputBoundary.java | 18 ++++++++
.../java/use_case/signup/SignupInputData.java | 29 +++++++++++++
.../use_case/signup/SignupInteractor.java | 43 +++++++++++++++++++
.../use_case/signup/SignupOutputBoundary.java | 24 +++++++++++
.../use_case/signup/SignupOutputData.java | 24 +++++++++++
.../signup/SignupUserDataAccessInterface.java | 22 ++++++++++
25 files changed, 570 insertions(+)
create mode 100644 src/main/java/entity/UserFactory.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordInputBoundary.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordInputData.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordInteractor.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordOutputData.java
create mode 100644 src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java
create mode 100644 src/main/java/use_case/login/LoginInputBoundary.java
create mode 100644 src/main/java/use_case/login/LoginInputData.java
create mode 100644 src/main/java/use_case/login/LoginInteractor.java
create mode 100644 src/main/java/use_case/login/LoginOutputBoundary.java
create mode 100644 src/main/java/use_case/login/LoginOutputData.java
create mode 100644 src/main/java/use_case/login/LoginUserDataAccessInterface.java
create mode 100644 src/main/java/use_case/logout/LogoutInputBoundary.java
create mode 100644 src/main/java/use_case/logout/LogoutInputData.java
create mode 100644 src/main/java/use_case/logout/LogoutInteractor.java
create mode 100644 src/main/java/use_case/logout/LogoutOutputBoundary.java
create mode 100644 src/main/java/use_case/logout/LogoutOutputData.java
create mode 100644 src/main/java/use_case/logout/LogoutUserDataAccessInterface.java
create mode 100644 src/main/java/use_case/signup/SignupInputBoundary.java
create mode 100644 src/main/java/use_case/signup/SignupInputData.java
create mode 100644 src/main/java/use_case/signup/SignupInteractor.java
create mode 100644 src/main/java/use_case/signup/SignupOutputBoundary.java
create mode 100644 src/main/java/use_case/signup/SignupOutputData.java
create mode 100644 src/main/java/use_case/signup/SignupUserDataAccessInterface.java
diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java
new file mode 100644
index 000000000..e367faeb2
--- /dev/null
+++ b/src/main/java/entity/UserFactory.java
@@ -0,0 +1,17 @@
+package entity;
+
+/**
+ * Factory for creating CommonUser objects.
+ */
+public class UserFactory {
+
+ /**
+ * Creates a new User.
+ * @param name the name of the new user
+ * @param password the password of the new user
+ * @return the new user
+ */
+ public User create(String name, String password) {
+ return new User(name, password);
+ }
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java b/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java
new file mode 100644
index 000000000..06ae9448e
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java
@@ -0,0 +1,14 @@
+package use_case.change_password;
+
+/**
+ * The Change Password Use Case.
+ */
+public interface ChangePasswordInputBoundary {
+
+ /**
+ * Execute the Change Password Use Case.
+ * @param changePasswordInputData the input data for this use case
+ */
+ void execute(ChangePasswordInputData changePasswordInputData);
+
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordInputData.java b/src/main/java/use_case/change_password/ChangePasswordInputData.java
new file mode 100644
index 000000000..8e09d8d12
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordInputData.java
@@ -0,0 +1,24 @@
+package use_case.change_password;
+
+/**
+ * The input data for the Change Password Use Case.
+ */
+public class ChangePasswordInputData {
+
+ private final String password;
+ private final String username;
+
+ public ChangePasswordInputData(String password, String username) {
+ this.password = password;
+ this.username = username;
+ }
+
+ String getPassword() {
+ return password;
+ }
+
+ String getUsername() {
+ return username;
+ }
+
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordInteractor.java b/src/main/java/use_case/change_password/ChangePasswordInteractor.java
new file mode 100644
index 000000000..df91196c1
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordInteractor.java
@@ -0,0 +1,32 @@
+package use_case.change_password;
+
+import entity.User;
+import entity.UserFactory;
+
+/**
+ * The Change Password Interactor.
+ */
+public class ChangePasswordInteractor implements ChangePasswordInputBoundary {
+ private final ChangePasswordUserDataAccessInterface userDataAccessObject;
+ private final ChangePasswordOutputBoundary userPresenter;
+ private final UserFactory userFactory;
+
+ public ChangePasswordInteractor(ChangePasswordUserDataAccessInterface changePasswordDataAccessInterface,
+ ChangePasswordOutputBoundary changePasswordOutputBoundary,
+ UserFactory userFactory) {
+ this.userDataAccessObject = changePasswordDataAccessInterface;
+ this.userPresenter = changePasswordOutputBoundary;
+ this.userFactory = userFactory;
+ }
+
+ @Override
+ public void execute(ChangePasswordInputData changePasswordInputData) {
+ final User user = userFactory.create(changePasswordInputData.getUsername(),
+ changePasswordInputData.getPassword());
+ userDataAccessObject.changePassword(user);
+
+ final ChangePasswordOutputData changePasswordOutputData = new ChangePasswordOutputData(user.getName(),
+ false);
+ userPresenter.prepareSuccessView(changePasswordOutputData);
+ }
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java b/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java
new file mode 100644
index 000000000..fce28367b
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java
@@ -0,0 +1,18 @@
+package use_case.change_password;
+
+/**
+ * The output boundary for the Change Password Use Case.
+ */
+public interface ChangePasswordOutputBoundary {
+ /**
+ * Prepares the success view for the Change Password Use Case.
+ * @param outputData the output data
+ */
+ void prepareSuccessView(ChangePasswordOutputData outputData);
+
+ /**
+ * Prepares the failure view for the Change Password Use Case.
+ * @param errorMessage the explanation of the failure
+ */
+ void prepareFailView(String errorMessage);
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordOutputData.java b/src/main/java/use_case/change_password/ChangePasswordOutputData.java
new file mode 100644
index 000000000..b47b83fbc
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordOutputData.java
@@ -0,0 +1,24 @@
+package use_case.change_password;
+
+/**
+ * Output Data for the Change Password Use Case.
+ */
+public class ChangePasswordOutputData {
+
+ private final String username;
+
+ private final boolean useCaseFailed;
+
+ public ChangePasswordOutputData(String username, boolean useCaseFailed) {
+ this.username = username;
+ this.useCaseFailed = useCaseFailed;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public boolean isUseCaseFailed() {
+ return useCaseFailed;
+ }
+}
diff --git a/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java b/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java
new file mode 100644
index 000000000..6b73ab6b0
--- /dev/null
+++ b/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java
@@ -0,0 +1,15 @@
+package use_case.change_password;
+
+import entity.User;
+
+/**
+ * The interface of the DAO for the Change Password Use Case.
+ */
+public interface ChangePasswordUserDataAccessInterface {
+
+ /**
+ * Updates the system to record this user's password.
+ * @param user the user whose password is to be updated
+ */
+ void changePassword(User user);
+}
diff --git a/src/main/java/use_case/login/LoginInputBoundary.java b/src/main/java/use_case/login/LoginInputBoundary.java
new file mode 100644
index 000000000..faf72dc96
--- /dev/null
+++ b/src/main/java/use_case/login/LoginInputBoundary.java
@@ -0,0 +1,13 @@
+package use_case.login;
+
+/**
+ * Input Boundary for actions which are related to logging in.
+ */
+public interface LoginInputBoundary {
+
+ /**
+ * Executes the login use case.
+ * @param loginInputData the input data
+ */
+ void execute(LoginInputData loginInputData);
+}
diff --git a/src/main/java/use_case/login/LoginInputData.java b/src/main/java/use_case/login/LoginInputData.java
new file mode 100644
index 000000000..363316832
--- /dev/null
+++ b/src/main/java/use_case/login/LoginInputData.java
@@ -0,0 +1,24 @@
+package use_case.login;
+
+/**
+ * The Input Data for the Login Use Case.
+ */
+public class LoginInputData {
+
+ private final String username;
+ private final String password;
+
+ public LoginInputData(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ String getUsername() {
+ return username;
+ }
+
+ String getPassword() {
+ return password;
+ }
+
+}
diff --git a/src/main/java/use_case/login/LoginInteractor.java b/src/main/java/use_case/login/LoginInteractor.java
new file mode 100644
index 000000000..5b36ddcd8
--- /dev/null
+++ b/src/main/java/use_case/login/LoginInteractor.java
@@ -0,0 +1,40 @@
+package use_case.login;
+
+import entity.User;
+
+/**
+ * The Login Interactor.
+ */
+public class LoginInteractor implements LoginInputBoundary {
+ private final LoginUserDataAccessInterface userDataAccessObject;
+ private final LoginOutputBoundary loginPresenter;
+
+ public LoginInteractor(LoginUserDataAccessInterface userDataAccessInterface,
+ LoginOutputBoundary loginOutputBoundary) {
+ this.userDataAccessObject = userDataAccessInterface;
+ this.loginPresenter = loginOutputBoundary;
+ }
+
+ @Override
+ public void execute(LoginInputData loginInputData) {
+ final String username = loginInputData.getUsername();
+ final String password = loginInputData.getPassword();
+ if (!userDataAccessObject.existsByName(username)) {
+ loginPresenter.prepareFailView(username + ": Account does not exist.");
+ }
+ else {
+ final String pwd = userDataAccessObject.get(username).getPassword();
+ if (!password.equals(pwd)) {
+ loginPresenter.prepareFailView("Incorrect password for \"" + username + "\".");
+ }
+ else {
+
+ final User user = userDataAccessObject.get(loginInputData.getUsername());
+
+ userDataAccessObject.setCurrentUsername(user.getName());
+ final LoginOutputData loginOutputData = new LoginOutputData(user.getName(), false);
+ loginPresenter.prepareSuccessView(loginOutputData);
+ }
+ }
+ }
+}
diff --git a/src/main/java/use_case/login/LoginOutputBoundary.java b/src/main/java/use_case/login/LoginOutputBoundary.java
new file mode 100644
index 000000000..08bc4731f
--- /dev/null
+++ b/src/main/java/use_case/login/LoginOutputBoundary.java
@@ -0,0 +1,18 @@
+package use_case.login;
+
+/**
+ * The output boundary for the Login Use Case.
+ */
+public interface LoginOutputBoundary {
+ /**
+ * Prepares the success view for the Login Use Case.
+ * @param outputData the output data
+ */
+ void prepareSuccessView(LoginOutputData outputData);
+
+ /**
+ * Prepares the failure view for the Login Use Case.
+ * @param errorMessage the explanation of the failure
+ */
+ void prepareFailView(String errorMessage);
+}
diff --git a/src/main/java/use_case/login/LoginOutputData.java b/src/main/java/use_case/login/LoginOutputData.java
new file mode 100644
index 000000000..3ea119a8f
--- /dev/null
+++ b/src/main/java/use_case/login/LoginOutputData.java
@@ -0,0 +1,20 @@
+package use_case.login;
+
+/**
+ * Output Data for the Login Use Case.
+ */
+public class LoginOutputData {
+
+ private final String username;
+ private final boolean useCaseFailed;
+
+ public LoginOutputData(String username, boolean useCaseFailed) {
+ this.username = username;
+ this.useCaseFailed = useCaseFailed;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+}
diff --git a/src/main/java/use_case/login/LoginUserDataAccessInterface.java b/src/main/java/use_case/login/LoginUserDataAccessInterface.java
new file mode 100644
index 000000000..681e8a52e
--- /dev/null
+++ b/src/main/java/use_case/login/LoginUserDataAccessInterface.java
@@ -0,0 +1,41 @@
+package use_case.login;
+
+import entity.User;
+
+/**
+ * DAO for the Login Use Case.
+ */
+public interface LoginUserDataAccessInterface {
+
+ /**
+ * Checks if the given username exists.
+ * @param username the username to look for
+ * @return true if a user with the given username exists; false otherwise
+ */
+ boolean existsByName(String username);
+
+ /**
+ * Saves the user.
+ * @param user the user to save
+ */
+ void save(User user);
+
+ /**
+ * Returns the user with the given username.
+ * @param username the username to look up
+ * @return the user with the given username
+ */
+ User get(String username);
+
+ /**
+ * Returns the username of the curren user of the application.
+ * @return the username of the current user; null indicates that no one is logged into the application.
+ */
+ String getCurrentUsername();
+
+ /**
+ * Sets the username indicating who is the current user of the application.
+ * @param username the new current username; null to indicate that no one is currently logged into the application.
+ */
+ void setCurrentUsername(String username);
+}
diff --git a/src/main/java/use_case/logout/LogoutInputBoundary.java b/src/main/java/use_case/logout/LogoutInputBoundary.java
new file mode 100644
index 000000000..189f50168
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutInputBoundary.java
@@ -0,0 +1,13 @@
+package use_case.logout;
+
+/**
+ * Input Boundary for actions which are related to logging in.
+ */
+public interface LogoutInputBoundary {
+
+ /**
+ * Executes the Logout use case.
+ * @param LogoutInputData the input data
+ */
+ void execute(LogoutInputData LogoutInputData);
+}
diff --git a/src/main/java/use_case/logout/LogoutInputData.java b/src/main/java/use_case/logout/LogoutInputData.java
new file mode 100644
index 000000000..56a33b375
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutInputData.java
@@ -0,0 +1,12 @@
+package use_case.logout;
+
+/**
+ * The Input Data for the Logout Use Case.
+ */
+public class LogoutInputData {
+
+ public LogoutInputData(String username) {
+ // TODO: save the current username in an instance variable and add a getter.
+ }
+
+}
diff --git a/src/main/java/use_case/logout/LogoutInteractor.java b/src/main/java/use_case/logout/LogoutInteractor.java
new file mode 100644
index 000000000..1ca93b44e
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutInteractor.java
@@ -0,0 +1,25 @@
+package use_case.logout;
+
+/**
+ * The Logout Interactor.
+ */
+public class LogoutInteractor implements LogoutInputBoundary {
+ private LogoutUserDataAccessInterface userDataAccessObject;
+ private LogoutOutputBoundary logoutPresenter;
+
+ public LogoutInteractor(LogoutUserDataAccessInterface userDataAccessInterface,
+ LogoutOutputBoundary logoutOutputBoundary) {
+ // TODO: save the DAO and Presenter in the instance variables.
+ // Which parameter is the DAO and which is the presenter?
+ }
+
+ @Override
+ public void execute(LogoutInputData logoutInputData) {
+ // TODO: implement the logic of the Logout Use Case (depends on the LogoutInputData.java TODO)
+ // * get the username out of the input data,
+ // * set the username to null in the DAO
+ // * instantiate the `LogoutOutputData`, which needs to contain the username.
+ // * tell the presenter to prepare a success view.
+ }
+}
+
diff --git a/src/main/java/use_case/logout/LogoutOutputBoundary.java b/src/main/java/use_case/logout/LogoutOutputBoundary.java
new file mode 100644
index 000000000..935a06bdc
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutOutputBoundary.java
@@ -0,0 +1,18 @@
+package use_case.logout;
+
+/**
+ * The output boundary for the Login Use Case.
+ */
+public interface LogoutOutputBoundary {
+ /**
+ * Prepares the success view for the Login Use Case.
+ * @param outputData the output data
+ */
+ void prepareSuccessView(LogoutOutputData outputData);
+
+ /**
+ * Prepares the failure view for the Login Use Case.
+ * @param errorMessage the explanation of the failure
+ */
+ void prepareFailView(String errorMessage);
+}
diff --git a/src/main/java/use_case/logout/LogoutOutputData.java b/src/main/java/use_case/logout/LogoutOutputData.java
new file mode 100644
index 000000000..2bbd76532
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutOutputData.java
@@ -0,0 +1,23 @@
+package use_case.logout;
+
+/**
+ * Output Data for the Logout Use Case.
+ */
+public class LogoutOutputData {
+
+ private final String username;
+ private final boolean useCaseFailed;
+
+ public LogoutOutputData(String username, boolean useCaseFailed) {
+ this.username = username;
+ this.useCaseFailed = useCaseFailed;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public boolean isUseCaseFailed() {
+ return useCaseFailed;
+ }
+}
diff --git a/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java
new file mode 100644
index 000000000..8263700e2
--- /dev/null
+++ b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java
@@ -0,0 +1,19 @@
+package use_case.logout;
+
+/**
+ * DAO for the Logout Use Case.
+ */
+public interface LogoutUserDataAccessInterface {
+
+ /**
+ * Returns the username of the curren user of the application.
+ * @return the username of the current user
+ */
+ String getCurrentUsername();
+
+ /**
+ * Sets the username indicating who is the current user of the application.
+ * @param username the new current username
+ */
+ void setCurrentUsername(String username);
+}
diff --git a/src/main/java/use_case/signup/SignupInputBoundary.java b/src/main/java/use_case/signup/SignupInputBoundary.java
new file mode 100644
index 000000000..1cb69e02e
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupInputBoundary.java
@@ -0,0 +1,18 @@
+package use_case.signup;
+
+/**
+ * Input Boundary for actions which are related to signing up.
+ */
+public interface SignupInputBoundary {
+
+ /**
+ * Executes the signup use case.
+ * @param signupInputData the input data
+ */
+ void execute(SignupInputData signupInputData);
+
+ /**
+ * Executes the switch to login view use case.
+ */
+ void switchToLoginView();
+}
diff --git a/src/main/java/use_case/signup/SignupInputData.java b/src/main/java/use_case/signup/SignupInputData.java
new file mode 100644
index 000000000..86c5e8abc
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupInputData.java
@@ -0,0 +1,29 @@
+package use_case.signup;
+
+/**
+ * The Input Data for the Signup Use Case.
+ */
+public class SignupInputData {
+
+ private final String username;
+ private final String password;
+ private final String repeatPassword;
+
+ public SignupInputData(String username, String password, String repeatPassword) {
+ this.username = username;
+ this.password = password;
+ this.repeatPassword = repeatPassword;
+ }
+
+ String getUsername() {
+ return username;
+ }
+
+ String getPassword() {
+ return password;
+ }
+
+ public String getRepeatPassword() {
+ return repeatPassword;
+ }
+}
diff --git a/src/main/java/use_case/signup/SignupInteractor.java b/src/main/java/use_case/signup/SignupInteractor.java
new file mode 100644
index 000000000..3fd6560c7
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupInteractor.java
@@ -0,0 +1,43 @@
+package use_case.signup;
+
+import entity.User;
+import entity.UserFactory;
+
+/**
+ * The Signup Interactor.
+ */
+public class SignupInteractor implements SignupInputBoundary {
+ private final SignupUserDataAccessInterface userDataAccessObject;
+ private final SignupOutputBoundary userPresenter;
+ private final UserFactory userFactory;
+
+ public SignupInteractor(SignupUserDataAccessInterface signupDataAccessInterface,
+ SignupOutputBoundary signupOutputBoundary,
+ UserFactory userFactory) {
+ this.userDataAccessObject = signupDataAccessInterface;
+ this.userPresenter = signupOutputBoundary;
+ this.userFactory = userFactory;
+ }
+
+ @Override
+ public void execute(SignupInputData signupInputData) {
+ if (userDataAccessObject.existsByName(signupInputData.getUsername())) {
+ userPresenter.prepareFailView("User already exists.");
+ }
+ else if (!signupInputData.getPassword().equals(signupInputData.getRepeatPassword())) {
+ userPresenter.prepareFailView("Passwords don't match.");
+ }
+ else {
+ final User user = userFactory.create(signupInputData.getUsername(), signupInputData.getPassword());
+ userDataAccessObject.save(user);
+
+ final SignupOutputData signupOutputData = new SignupOutputData(user.getName(), false);
+ userPresenter.prepareSuccessView(signupOutputData);
+ }
+ }
+
+ @Override
+ public void switchToLoginView() {
+ userPresenter.switchToLoginView();
+ }
+}
diff --git a/src/main/java/use_case/signup/SignupOutputBoundary.java b/src/main/java/use_case/signup/SignupOutputBoundary.java
new file mode 100644
index 000000000..314376b93
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupOutputBoundary.java
@@ -0,0 +1,24 @@
+package use_case.signup;
+
+/**
+ * The output boundary for the Signup Use Case.
+ */
+public interface SignupOutputBoundary {
+
+ /**
+ * Prepares the success view for the Signup Use Case.
+ * @param outputData the output data
+ */
+ void prepareSuccessView(SignupOutputData outputData);
+
+ /**
+ * Prepares the failure view for the Signup Use Case.
+ * @param errorMessage the explanation of the failure
+ */
+ void prepareFailView(String errorMessage);
+
+ /**
+ * Switches to the Login View.
+ */
+ void switchToLoginView();
+}
diff --git a/src/main/java/use_case/signup/SignupOutputData.java b/src/main/java/use_case/signup/SignupOutputData.java
new file mode 100644
index 000000000..6dc74d2fb
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupOutputData.java
@@ -0,0 +1,24 @@
+package use_case.signup;
+
+/**
+ * Output Data for the Signup Use Case.
+ */
+public class SignupOutputData {
+
+ private final String username;
+
+ private final boolean useCaseFailed;
+
+ public SignupOutputData(String username, boolean useCaseFailed) {
+ this.username = username;
+ this.useCaseFailed = useCaseFailed;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public boolean isUseCaseFailed() {
+ return useCaseFailed;
+ }
+}
diff --git a/src/main/java/use_case/signup/SignupUserDataAccessInterface.java b/src/main/java/use_case/signup/SignupUserDataAccessInterface.java
new file mode 100644
index 000000000..b9d60f585
--- /dev/null
+++ b/src/main/java/use_case/signup/SignupUserDataAccessInterface.java
@@ -0,0 +1,22 @@
+package use_case.signup;
+
+import entity.User;
+
+/**
+ * DAO for the Signup Use Case.
+ */
+public interface SignupUserDataAccessInterface {
+
+ /**
+ * Checks if the given username exists.
+ * @param username the username to look for
+ * @return true if a user with the given username exists; false otherwise
+ */
+ boolean existsByName(String username);
+
+ /**
+ * Saves the user.
+ * @param user the user to save
+ */
+ void save(User user);
+}
From bf0d4879bde7cc9e0cf70be0825ec1444ba1ce7c Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Sun, 10 Nov 2024 19:20:30 -0500
Subject: [PATCH 007/138] Fix TODOs in use case
---
src/main/java/use_case/logout/LogoutInputData.java | 7 ++++++-
.../java/use_case/logout/LogoutInteractor.java | 14 ++++++--------
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/main/java/use_case/logout/LogoutInputData.java b/src/main/java/use_case/logout/LogoutInputData.java
index 56a33b375..d0df964fe 100644
--- a/src/main/java/use_case/logout/LogoutInputData.java
+++ b/src/main/java/use_case/logout/LogoutInputData.java
@@ -4,9 +4,14 @@
* The Input Data for the Logout Use Case.
*/
public class LogoutInputData {
+ private final String username;
public LogoutInputData(String username) {
- // TODO: save the current username in an instance variable and add a getter.
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return username;
}
}
diff --git a/src/main/java/use_case/logout/LogoutInteractor.java b/src/main/java/use_case/logout/LogoutInteractor.java
index 1ca93b44e..d0bd6a7b1 100644
--- a/src/main/java/use_case/logout/LogoutInteractor.java
+++ b/src/main/java/use_case/logout/LogoutInteractor.java
@@ -9,17 +9,15 @@ public class LogoutInteractor implements LogoutInputBoundary {
public LogoutInteractor(LogoutUserDataAccessInterface userDataAccessInterface,
LogoutOutputBoundary logoutOutputBoundary) {
- // TODO: save the DAO and Presenter in the instance variables.
- // Which parameter is the DAO and which is the presenter?
+ this.userDataAccessObject = userDataAccessInterface;
+ this.logoutPresenter = logoutOutputBoundary;
}
@Override
public void execute(LogoutInputData logoutInputData) {
- // TODO: implement the logic of the Logout Use Case (depends on the LogoutInputData.java TODO)
- // * get the username out of the input data,
- // * set the username to null in the DAO
- // * instantiate the `LogoutOutputData`, which needs to contain the username.
- // * tell the presenter to prepare a success view.
+ final String username = logoutInputData.getUsername();
+ userDataAccessObject.setCurrentUsername(null);
+ final LogoutOutputData logoutOutputData = new LogoutOutputData(username, false);
+ logoutPresenter.prepareSuccessView(logoutOutputData);
}
}
-
From 38cb95e50b3a1f649b7912c27294ac1037167ec0 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sun, 10 Nov 2024 19:27:54 -0500
Subject: [PATCH 008/138] demo pull request
---
src/main/java/app/MainNoteApplication.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/app/MainNoteApplication.java b/src/main/java/app/MainNoteApplication.java
index c37860156..078d25304 100644
--- a/src/main/java/app/MainNoteApplication.java
+++ b/src/main/java/app/MainNoteApplication.java
@@ -17,7 +17,7 @@
*
* The ViewManager code has also been removed, since this minimal program only requires a single
* view. Your team may wish to bring back the ViewManager or make your own implementation of supporting
- * switching between views depending on your project.
+ * switching views depending on your project.
*/
public class MainNoteApplication {
From 60753d9e3f593c99fa1934e2ce68b9c95d4cba68 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sun, 10 Nov 2024 19:53:12 -0500
Subject: [PATCH 009/138] demo
---
src/main/java/app/NoteAppBuilder.java | 16 ++++++++--------
.../{NoteView.java => RecipeSearchView.java} | 0
2 files changed, 8 insertions(+), 8 deletions(-)
rename src/main/java/view/{NoteView.java => RecipeSearchView.java} (100%)
diff --git a/src/main/java/app/NoteAppBuilder.java b/src/main/java/app/NoteAppBuilder.java
index a68cb9ad6..e78c0d1b4 100644
--- a/src/main/java/app/NoteAppBuilder.java
+++ b/src/main/java/app/NoteAppBuilder.java
@@ -9,7 +9,7 @@
import use_case.note.NoteDataAccessInterface;
import use_case.note.NoteInteractor;
import use_case.note.NoteOutputBoundary;
-import view.NoteView;
+import view.RecipeSearchView;
/**
* Builder for the Note Application.
@@ -19,7 +19,7 @@ public class NoteAppBuilder {
public static final int WIDTH = 400;
private NoteDataAccessInterface noteDAO;
private NoteViewModel noteViewModel = new NoteViewModel();
- private NoteView noteView;
+ private RecipeSearchView recipeSearchView;
private NoteInteractor noteInteractor;
/**
@@ -33,7 +33,7 @@ public NoteAppBuilder addNoteDAO(NoteDataAccessInterface noteDataAccess) {
}
/**
- * Creates the objects for the Note Use Case and connects the NoteView to its
+ * Creates the objects for the Note Use Case and connects the RecipeSearchView to its
* controller.
*
This method must be called after addNoteView!
* @return this builder
@@ -45,20 +45,20 @@ public NoteAppBuilder addNoteUseCase() {
noteDAO, noteOutputBoundary);
final NoteController controller = new NoteController(noteInteractor);
- if (noteView == null) {
+ if (recipeSearchView == null) {
throw new RuntimeException("addNoteView must be called before addNoteUseCase");
}
- noteView.setNoteController(controller);
+ recipeSearchView.setNoteController(controller);
return this;
}
/**
- * Creates the NoteView and underlying NoteViewModel.
+ * Creates the RecipeSearchView and underlying NoteViewModel.
* @return this builder
*/
public NoteAppBuilder addNoteView() {
noteViewModel = new NoteViewModel();
- noteView = new NoteView(noteViewModel);
+ recipeSearchView = new RecipeSearchView(noteViewModel);
return this;
}
@@ -72,7 +72,7 @@ public JFrame build() {
frame.setTitle("Note Application");
frame.setSize(WIDTH, HEIGHT);
- frame.add(noteView);
+ frame.add(recipeSearchView);
// refresh so that the note will be visible when we start the program
noteInteractor.executeRefresh();
diff --git a/src/main/java/view/NoteView.java b/src/main/java/view/RecipeSearchView.java
similarity index 100%
rename from src/main/java/view/NoteView.java
rename to src/main/java/view/RecipeSearchView.java
From db0fb3eaef760f5190c52062f9fae9399db296b6 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sun, 10 Nov 2024 20:01:14 -0500
Subject: [PATCH 010/138] modifying the View to handle ingredient search
---
.../interface_adapter/note/NoteViewModel.java | 2 +-
src/main/java/view/RecipeSearchView.java | 202 +++++++++++++-----
2 files changed, 146 insertions(+), 58 deletions(-)
diff --git a/src/main/java/interface_adapter/note/NoteViewModel.java b/src/main/java/interface_adapter/note/NoteViewModel.java
index 6e185d0fa..777689f25 100644
--- a/src/main/java/interface_adapter/note/NoteViewModel.java
+++ b/src/main/java/interface_adapter/note/NoteViewModel.java
@@ -3,7 +3,7 @@
import interface_adapter.ViewModel;
/**
- * The ViewModel for the NoteView.
+ * The ViewModel for the RecipeSearchView.
*/
public class NoteViewModel extends ViewModel {
public NoteViewModel() {
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index 331d76493..07ee377a7 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -1,95 +1,183 @@
package view;
+import java.awt.BorderLayout;
import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.Box;
import javax.swing.BoxLayout;
+import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
-import javax.swing.JOptionPane;
+import javax.swing.JList;
import javax.swing.JPanel;
-import javax.swing.JTextArea;
-
-import interface_adapter.note.NoteController;
-import interface_adapter.note.NoteState;
-import interface_adapter.note.NoteViewModel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
/**
- * The View for when the user is viewing a note in the program.
+ * A view for searching recipes by ingredients.
*/
-public class NoteView extends JPanel implements ActionListener, PropertyChangeListener {
-
- private final NoteViewModel noteViewModel;
-
- private final JLabel noteName = new JLabel("note for jonathan_calver2");
- private final JTextArea noteInputField = new JTextArea();
+public class RecipeSearchView extends JPanel implements ActionListener, PropertyChangeListener {
+
+ private static final int TEXTFIELD_WIDTH = 20;
+ private static final int VERTICAL_SPACING = 10;
+ private static final int INGREDIENT_LIST_HEIGHT = 100;
+ private static final int INGREDIENT_LIST_WIDTH = 300;
+ private static final int RESULTS_LIST_HEIGHT = 200;
+ private static final int RESULTS_LIST_WIDTH = 300;
+ private static final int TITLE_FONT_SIZE = 16;
+
+ private final JLabel title;
+ private final JPanel inputPanel;
+ private final JTextField ingredientField;
+ private final JButton addIngredientButton;
+ private final DefaultListModel ingredientListModel;
+ private final JList ingredientList;
+ private final JButton removeIngredientButton;
+ private final JButton searchButton;
+ private final JPanel resultsPanel;
+ private final DefaultListModel recipeListModel;
+ private final JList recipeResults;
+ private final List ingredients;
- private final JButton saveButton = new JButton("Save");
- private final JButton refreshButton = new JButton("Refresh");
- private NoteController noteController;
+ /**
+ * Constructs a new RecipeSearchView.
+ */
+ public RecipeSearchView() {
+ // Initialize components
+ title = new JLabel("Recipe Search by Ingredients");
+ inputPanel = new JPanel();
+ ingredientField = new JTextField(TEXTFIELD_WIDTH);
+ addIngredientButton = new JButton("Add Ingredient");
+ ingredientListModel = new DefaultListModel<>();
+ ingredientList = new JList<>(ingredientListModel);
+ removeIngredientButton = new JButton("Remove Selected");
+ searchButton = new JButton("Search Recipes");
+ resultsPanel = new JPanel();
+ recipeListModel = new DefaultListModel<>();
+ recipeResults = new JList<>(recipeListModel);
+ ingredients = new ArrayList<>();
+
+ // Set up the main layout
+ this.setLayout(new BorderLayout());
+
+ // Configure the title
+ title.setFont(new Font(title.getFont().getName(), Font.BOLD, TITLE_FONT_SIZE));
+ title.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+ // Set up panels
+ setupInputPanel();
+ setupResultsPanel();
+
+ // Add action listeners
+ addIngredientButton.addActionListener(this);
+ removeIngredientButton.addActionListener(this);
+ searchButton.addActionListener(this);
+
+ // Main panel assembly
+ final JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ mainPanel.add(title);
+ mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ mainPanel.add(inputPanel);
+ mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ mainPanel.add(searchButton);
+ mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ mainPanel.add(resultsPanel);
+
+ this.add(mainPanel, BorderLayout.CENTER);
+ }
- public NoteView(NoteViewModel noteViewModel) {
+ private void setupInputPanel() {
+ inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
- noteName.setAlignmentX(Component.CENTER_ALIGNMENT);
- this.noteViewModel = noteViewModel;
- this.noteViewModel.addPropertyChangeListener(this);
+ final JPanel addIngredientPanel = new JPanel();
+ addIngredientPanel.add(new JLabel("Enter Ingredient:"));
+ addIngredientPanel.add(ingredientField);
+ addIngredientPanel.add(addIngredientButton);
- final JPanel buttons = new JPanel();
- buttons.add(saveButton);
- buttons.add(refreshButton);
+ final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
+ ingredientScrollPane.setPreferredSize(
+ new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
- saveButton.addActionListener(
- evt -> {
- if (evt.getSource().equals(saveButton)) {
- noteController.execute(noteInputField.getText());
+ final JPanel removeButtonPanel = new JPanel();
+ removeButtonPanel.add(removeIngredientButton);
- }
- }
- );
+ inputPanel.add(addIngredientPanel);
+ inputPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ inputPanel.add(ingredientScrollPane);
+ inputPanel.add(removeButtonPanel);
+ }
- refreshButton.addActionListener(
- evt -> {
- if (evt.getSource().equals(refreshButton)) {
- noteController.execute(null);
+ private void setupResultsPanel() {
+ resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.Y_AXIS));
- }
- }
- );
+ final JLabel resultsLabel = new JLabel("Recipe Results:");
+ resultsLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
- this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ final JScrollPane resultsScrollPane = new JScrollPane(recipeResults);
+ resultsScrollPane.setPreferredSize(
+ new Dimension(RESULTS_LIST_WIDTH, RESULTS_LIST_HEIGHT));
- this.add(noteName);
- this.add(noteInputField);
- this.add(buttons);
+ resultsPanel.add(resultsLabel);
+ resultsPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ resultsPanel.add(resultsScrollPane);
}
- /**
- * React to a button click that results in evt.
- * @param evt the ActionEvent to react to
- */
+ @Override
public void actionPerformed(ActionEvent evt) {
- System.out.println("Click " + evt.getActionCommand());
+ if (evt.getSource().equals(addIngredientButton)) {
+ final String ingredient = ingredientField.getText().trim();
+ if (!ingredient.isEmpty()) {
+ ingredients.add(ingredient);
+ ingredientListModel.addElement(ingredient);
+ ingredientField.setText("");
+ }
+ }
+ else if (evt.getSource().equals(removeIngredientButton)) {
+ final int selectedIndex = ingredientList.getSelectedIndex();
+ if (selectedIndex != -1) {
+ ingredients.remove(selectedIndex);
+ ingredientListModel.remove(selectedIndex);
+ }
+ }
+ else if (evt.getSource().equals(searchButton)) {
+ // TODO: Implement search functionality
+ // This will be connected to the controller
+ System.out.println("Searching for recipes with ingredients: " + ingredients);
+ }
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
- final NoteState state = (NoteState) evt.getNewValue();
- setFields(state);
- if (state.getError() != null) {
- JOptionPane.showMessageDialog(this, state.getError(),
- "Error", JOptionPane.ERROR_MESSAGE);
- }
+ // TODO: Handle property changes from the view model
+ // This will update the recipe results when the search is complete
}
- private void setFields(NoteState state) {
- noteInputField.setText(state.getNote());
+ /**
+ * Returns a copy of the current list of ingredients.
+ * @return List of ingredients
+ */
+ public List getIngredients() {
+ return new ArrayList<>(ingredients);
}
- public void setNoteController(NoteController controller) {
- this.noteController = controller;
+ /**
+ * Updates the recipe results display.
+ * @param recipes List of recipe names to display
+ */
+ public void updateRecipeResults(List recipes) {
+ recipeListModel.clear();
+ for (String recipe : recipes) {
+ recipeListModel.addElement(recipe);
+ }
}
}
-
From 3b78b204d9f8069e86a4b3dc3c44a2afbab5950a Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sun, 10 Nov 2024 20:26:56 -0500
Subject: [PATCH 011/138] Added Interface adapter Recipe Search State and
Updated View, Recipe Search View
---
.../recipe_search/RecipeSearchState.java | 54 +++++++++
src/main/java/view/RecipeSearchView.java | 114 ++++++++++--------
2 files changed, 119 insertions(+), 49 deletions(-)
create mode 100644 src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
new file mode 100644
index 000000000..7a6dcf1d4
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
@@ -0,0 +1,54 @@
+package interface_adapter.recipe_search;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the state for recipe search functionality.
+ */
+public class RecipeSearchState {
+ private List ingredients = new ArrayList<>();
+ private List recipeResults = new ArrayList<>();
+ private String error;
+
+ /**
+ * Copy constructor for RecipeSearchState.
+ * @param copy The state to copy from
+ */
+ public RecipeSearchState(RecipeSearchState copy) {
+ ingredients = new ArrayList<>(copy.ingredients);
+ recipeResults = new ArrayList<>(copy.recipeResults);
+ error = copy.error;
+ }
+
+ /**
+ * Default constructor for RecipeSearchState.
+ */
+ public RecipeSearchState() {
+
+ }
+
+ public List getIngredients() {
+ return new ArrayList<>(ingredients);
+ }
+
+ public void setIngredients(List ingredients) {
+ this.ingredients = new ArrayList<>(ingredients);
+ }
+
+ public List getRecipeResults() {
+ return new ArrayList<>(recipeResults);
+ }
+
+ public void setRecipeResults(List recipeResults) {
+ this.recipeResults = new ArrayList<>(recipeResults);
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+}
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index 07ee377a7..b1edbec28 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -1,5 +1,9 @@
package view;
+import interface_adapter.recipe_search.RecipeSearchController;
+import interface_adapter.recipe_search.RecipeSearchState;
+import interface_adapter.recipe_search.RecipeSearchViewModel;
+
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
@@ -17,6 +21,7 @@
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
@@ -47,19 +52,26 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private final JList recipeResults;
private final List ingredients;
+ private final RecipeSearchViewModel recipeSearchViewModel;
+ private RecipeSearchController recipeSearchController;
+
/**
* Constructs a new RecipeSearchView.
+ * @param viewModel The view model for recipe search
*/
- public RecipeSearchView() {
+ public RecipeSearchView(RecipeSearchViewModel viewModel) {
+ this.recipeSearchViewModel = viewModel;
+ this.recipeSearchViewModel.addPropertyChangeListener(this);
+
// Initialize components
- title = new JLabel("Recipe Search by Ingredients");
+ title = new JLabel(RecipeSearchViewModel.TITLE_LABEL);
inputPanel = new JPanel();
ingredientField = new JTextField(TEXTFIELD_WIDTH);
- addIngredientButton = new JButton("Add Ingredient");
+ addIngredientButton = new JButton(RecipeSearchViewModel.ADD_INGREDIENT_BUTTON_LABEL);
ingredientListModel = new DefaultListModel<>();
ingredientList = new JList<>(ingredientListModel);
- removeIngredientButton = new JButton("Remove Selected");
- searchButton = new JButton("Search Recipes");
+ removeIngredientButton = new JButton(RecipeSearchViewModel.REMOVE_INGREDIENT_BUTTON_LABEL);
+ searchButton = new JButton(RecipeSearchViewModel.SEARCH_BUTTON_LABEL);
resultsPanel = new JPanel();
recipeListModel = new DefaultListModel<>();
recipeResults = new JList<>(recipeListModel);
@@ -96,6 +108,52 @@ public RecipeSearchView() {
this.add(mainPanel, BorderLayout.CENTER);
}
+ /**
+ * Sets the controller for this view.
+ * @param controller The controller to set
+ */
+ public void setRecipeSearchController(RecipeSearchController controller) {
+ this.recipeSearchController = controller;
+ }
+
+ // ... (keep existing panel setup methods the same) ...
+
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ if (evt.getSource().equals(addIngredientButton)) {
+ final String ingredient = ingredientField.getText().trim();
+ if (!ingredient.isEmpty()) {
+ ingredients.add(ingredient);
+ ingredientListModel.addElement(ingredient);
+ ingredientField.setText("");
+ }
+ }
+ else if (evt.getSource().equals(removeIngredientButton)) {
+ final int selectedIndex = ingredientList.getSelectedIndex();
+ if (selectedIndex != -1) {
+ ingredients.remove(selectedIndex);
+ ingredientListModel.remove(selectedIndex);
+ }
+ }
+ else if (evt.getSource().equals(searchButton)) {
+ if (recipeSearchController != null) {
+ recipeSearchController.executeSearch(new ArrayList<>(ingredients));
+ }
+ }
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ RecipeSearchState state = (RecipeSearchState) evt.getNewValue();
+ if (state != null) {
+ if (state.getError() != null) {
+ JOptionPane.showMessageDialog(this, state.getError());
+ } else {
+ updateRecipeResults(state.getRecipeResults());
+ }
+ }
+ }
+
private void setupInputPanel() {
inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
@@ -132,52 +190,10 @@ private void setupResultsPanel() {
resultsPanel.add(resultsScrollPane);
}
- @Override
- public void actionPerformed(ActionEvent evt) {
- if (evt.getSource().equals(addIngredientButton)) {
- final String ingredient = ingredientField.getText().trim();
- if (!ingredient.isEmpty()) {
- ingredients.add(ingredient);
- ingredientListModel.addElement(ingredient);
- ingredientField.setText("");
- }
- }
- else if (evt.getSource().equals(removeIngredientButton)) {
- final int selectedIndex = ingredientList.getSelectedIndex();
- if (selectedIndex != -1) {
- ingredients.remove(selectedIndex);
- ingredientListModel.remove(selectedIndex);
- }
- }
- else if (evt.getSource().equals(searchButton)) {
- // TODO: Implement search functionality
- // This will be connected to the controller
- System.out.println("Searching for recipes with ingredients: " + ingredients);
- }
- }
-
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- // TODO: Handle property changes from the view model
- // This will update the recipe results when the search is complete
- }
-
- /**
- * Returns a copy of the current list of ingredients.
- * @return List of ingredients
- */
- public List getIngredients() {
- return new ArrayList<>(ingredients);
- }
-
- /**
- * Updates the recipe results display.
- * @param recipes List of recipe names to display
- */
- public void updateRecipeResults(List recipes) {
+ private void updateRecipeResults(List recipes) {
recipeListModel.clear();
for (String recipe : recipes) {
recipeListModel.addElement(recipe);
}
}
-}
+}
\ No newline at end of file
From afdc6fffd32fe94d4a80b4c2603110754902b12d Mon Sep 17 00:00:00 2001
From: kaibaek
Date: Sun, 10 Nov 2024 20:49:51 -0500
Subject: [PATCH 012/138] Implementing RecipeSearchEdamam Class for data_access
and RecipeForSearch class is edited version of original Recipe entity so that
it can fit on our edamam api.
---
.../java/data_access/RecipeSearchEdamam.java | 62 +++++++++++++++++++
src/main/java/entity/RecipeForSearch.java | 53 ++++++++++++++++
2 files changed, 115 insertions(+)
create mode 100644 src/main/java/data_access/RecipeSearchEdamam.java
create mode 100644 src/main/java/entity/RecipeForSearch.java
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
new file mode 100644
index 000000000..2894dd972
--- /dev/null
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -0,0 +1,62 @@
+package data_access;
+
+import entity.RecipeForSearch;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class RecipeSearchEdamam {
+ private static final String APP_ID = "35f28703";
+ private static final String APP_KEY = "acb2a3e8e5cd69c1e0bcacefd85ea880";
+ private static final String BASE_URL = "https://api.edamam.com/search";
+ private final OkHttpClient httpClient = new OkHttpClient();
+
+ public List searchRecipesByFoodName(String foodName) {
+ List recipes = new ArrayList<>();
+
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
+ urlBuilder.addQueryParameter("q", foodName);
+ urlBuilder.addQueryParameter("app_id", APP_ID);
+ urlBuilder.addQueryParameter("app_key", APP_KEY);
+
+ Request request = new Request.Builder()
+ .url(urlBuilder.build().toString()).build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (response.body() != null) {
+ String jsonData = response.body().string();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ JSONArray hitsArray = jsonObject.getJSONArray("hits");
+
+ for (int i = 0; i < hitsArray.length(); i++) {
+ JSONObject recipeJson = hitsArray.getJSONObject(i).getJSONObject("recipe");
+
+ // Extract recipe details
+ String name = recipeJson.getString("label");
+ String description = recipeJson.optString("source", "No description available");
+
+ // Extract ingredients
+ List ingredients = new ArrayList<>();
+ JSONArray ingredientsArray = recipeJson.getJSONArray("ingredientLines");
+ for (int j = 0; j < ingredientsArray.length(); j++) {
+ ingredients.add(ingredientsArray.getString(j));
+ }
+
+ // Create and add Recipe entity
+ RecipeForSearch recipeForSearch = new RecipeForSearch(name, description, ingredients);
+ recipes.add(recipeForSearch);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return recipes;
+ }
+}
diff --git a/src/main/java/entity/RecipeForSearch.java b/src/main/java/entity/RecipeForSearch.java
new file mode 100644
index 000000000..077ebfa2f
--- /dev/null
+++ b/src/main/java/entity/RecipeForSearch.java
@@ -0,0 +1,53 @@
+package entity;
+
+import java.util.List;
+
+/**
+ * The Recipe entity which contains ingredients and other details.
+ */
+public class RecipeForSearch {
+
+ private String title;
+ private String description;
+ private List ingredients;
+ private String instructions;
+
+ public RecipeForSearch(String title, String description, List ingredients) {
+ this.title = title;
+ this.description = description;
+ this.ingredients = ingredients;
+ this.instructions = "Instructions not available";
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List getIngredients() {
+ return ingredients;
+ }
+
+ public void setIngredients(List ingredients) {
+ this.ingredients = ingredients;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+
+ public void setInstructions(String instructions) {
+ this.instructions = instructions;
+ }
+}
From 0d93cd6a2b2a4b06d2d8a725e71cf8c8331e62be Mon Sep 17 00:00:00 2001
From: kaibaek
Date: Sun, 10 Nov 2024 21:13:44 -0500
Subject: [PATCH 013/138] Changes on RecipeSearchEdamam for correct queries.
---
src/main/java/data_access/RecipeSearchEdamam.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index 2894dd972..b9e2098b9 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -15,13 +15,14 @@
public class RecipeSearchEdamam {
private static final String APP_ID = "35f28703";
private static final String APP_KEY = "acb2a3e8e5cd69c1e0bcacefd85ea880";
- private static final String BASE_URL = "https://api.edamam.com/search";
+ private static final String BASE_URL = "https://api.edamam.com/api/recipes/v2";
private final OkHttpClient httpClient = new OkHttpClient();
public List searchRecipesByFoodName(String foodName) {
List recipes = new ArrayList<>();
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
+ urlBuilder.addQueryParameter("type", "public");
urlBuilder.addQueryParameter("q", foodName);
urlBuilder.addQueryParameter("app_id", APP_ID);
urlBuilder.addQueryParameter("app_key", APP_KEY);
From f88891e7aaa0dac0b1422401046fbd7aa9762dfb Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Sun, 10 Nov 2024 23:49:53 -0500
Subject: [PATCH 014/138] Version 1 for recipe search use case
---
.../use_case/recipe_search/RecipeSearch.java | 25 ++++++++++
.../recipe_search/RecipeSearchImpl.java | 47 +++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 src/main/java/use_case/recipe_search/RecipeSearch.java
create mode 100644 src/main/java/use_case/recipe_search/RecipeSearchImpl.java
diff --git a/src/main/java/use_case/recipe_search/RecipeSearch.java b/src/main/java/use_case/recipe_search/RecipeSearch.java
new file mode 100644
index 000000000..4cd702760
--- /dev/null
+++ b/src/main/java/use_case/recipe_search/RecipeSearch.java
@@ -0,0 +1,25 @@
+package use_case.recipe_search;
+
+import java.util.List;
+
+import entity.Recipe;
+
+/**
+ * Interface for the use case that handles searching for recipes based on
+ * various filters such as ingredients, cuisines, and nutritional information.
+ * This use case allows the application to fetch recipes by making calls to
+ * external APIs or services.
+ */
+public interface RecipeSearch {
+
+ /**
+ * Searches for recipes based on the provided query and filters.
+ *
+ * @param query The search query (e.g., recipe name, dish type).
+ * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
+ * @param number The number of results to return.
+ * @return A list of recipes that match the search criteria.
+ * @throws RecipeSearchException if an error occurs while fetching the recipes.
+ */
+ List searchRecipes(String query, int maxFat, int number) throws RecipeSearchException;
+}
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
new file mode 100644
index 000000000..24e774598
--- /dev/null
+++ b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
@@ -0,0 +1,47 @@
+package use_case.recipe_search;
+
+import java.util.List;
+import entity.Recipe;
+import repository.RecipeRepository;
+import exception.RecipeSearchException;
+
+/**
+ * Implementation of the RecipeSearchUseCase interface. This class is responsible
+ * for searching recipes using external APIs or data sources. It delegates the actual
+ * search operation to the RecipeApiGateway.
+ */
+public class RecipeSearchImpl implements RecipeSearch {
+
+ private final RecipeRepository recipeRepository;
+
+ /**
+ * Constructor for the RecipeSearchUseCaseImpl.
+ * Initializes the RecipeApiGateway which is used to fetch recipe data.
+ *
+ * @param recipeRepository The gateway for accessing the external API or service.
+ */
+ public RecipeSearchImpl(RecipeRepository recipeRepository) {
+ this.recipeRepository = recipeRepository;
+ }
+
+ /**
+ * Searches for recipes based on the provided search parameters.
+ * This method calls the RecipeApiGateway to fetch recipe data and returns
+ * the result as a list of Recipe objects.
+ *
+ * @param query The search query (e.g., recipe name, dish type).
+ * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
+ * @param number The number of results to return.
+ * @return A list of recipes that match the search criteria.
+ * @throws RecipeSearchException if an error occurs while searching for recipes.
+ */
+ public List searchRecipes(String query, int maxFat, int number) throws RecipeSearchException {
+ try {
+ // Use the recipeApiGateway to fetch recipes based on the parameters
+ return recipeRepository.fetchRecipes(query, maxFat, number);
+ } catch (Exception e) {
+ // Wrap any errors into a custom exception
+ throw new RecipeSearchException("Failed to search recipes", e);
+ }
+ }
+}
From 3e9bc989b81725467a6878447560b00524c0931f Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Mon, 11 Nov 2024 00:18:43 -0500
Subject: [PATCH 015/138] The implementation of RecipeSearchController and
RecipeSearchViewModel
---
.../interface_adapter/note/NoteViewModel.java | 2 +-
.../recipe_search/RecipeSearchController.java | 26 +++++++++++++++++++
.../recipe_search/RecipeSearchViewModel.java | 12 +++++++++
3 files changed, 39 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
create mode 100644 src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
diff --git a/src/main/java/interface_adapter/note/NoteViewModel.java b/src/main/java/interface_adapter/note/NoteViewModel.java
index 777689f25..6e185d0fa 100644
--- a/src/main/java/interface_adapter/note/NoteViewModel.java
+++ b/src/main/java/interface_adapter/note/NoteViewModel.java
@@ -3,7 +3,7 @@
import interface_adapter.ViewModel;
/**
- * The ViewModel for the RecipeSearchView.
+ * The ViewModel for the NoteView.
*/
public class NoteViewModel extends ViewModel {
public NoteViewModel() {
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
new file mode 100644
index 000000000..a712f62dd
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -0,0 +1,26 @@
+package interface_adapter.recipe_search;
+
+import use_case.recipe_search.RecipeSearch;
+
+/**
+ * Controller for the RecipeSearch system.
+ */
+public class RecipeSearchController {
+ private final RecipeSearch RecipeSearchInteractor;
+
+ public RecipeSearchController(RecipeSearch RecipeSearchInteractor) {
+ this.RecipeSearchInteractor = RecipeSearchInteractor;
+ }
+
+ /**
+ * Executes the RecipeSearch Use Case.
+ * @param query The search query (e.g., recipe name, dish type).
+ * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
+ * @param number The number of results to return.
+ */
+ public void execute(String query, int maxFat, int number) {
+
+ RecipeSearchInteractor.searchRecipes(query, maxFat, number);
+ }
+}
+
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
new file mode 100644
index 000000000..2aa649f03
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
@@ -0,0 +1,12 @@
+package interface_adapter.recipe_search;
+
+import interface_adapter.ViewModel;
+
+/**
+ * The ViewModel for the RecipeSearchView.
+ */
+public class RecipeSearchViewModel extends ViewModel {
+ public RecipeSearchViewModel() {
+ super("Recipe Search");
+ }
+}
From e277b9f985692c45df2c59e3e02fd0584924fb3e Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Mon, 11 Nov 2024 00:46:52 -0500
Subject: [PATCH 016/138] The implementation of RecipeSearchController and
RecipeSearchViewModel
---
.../recipe_search/RecipeSearchController.java | 1 +
.../recipe_search/RecipeSearchViewModel.java | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index a712f62dd..f95f74bfe 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -21,6 +21,7 @@ public RecipeSearchController(RecipeSearch RecipeSearchInteractor) {
public void execute(String query, int maxFat, int number) {
RecipeSearchInteractor.searchRecipes(query, maxFat, number);
+ // The method executed and parameters taken are based on the use case
}
}
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
index 2aa649f03..0561a939b 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
@@ -6,7 +6,16 @@
* The ViewModel for the RecipeSearchView.
*/
public class RecipeSearchViewModel extends ViewModel {
+ // Attributes are created base on the components in RecipeSearchView class
+ public static final String TITLE_LABEL = "Recipe Search";
+ public static final String ADD_INGREDIENT_BUTTON_LABEL = "Add Ingredient";
+ public static final String REMOVE_INGREDIENT_BUTTON_LABEL = "Remove Ingredient";
+ public static final String SEARCH_BUTTON_LABEL = "Search";
+
public RecipeSearchViewModel() {
+ // RecipeSearchViewModel is currently a subclass of ViewModel base on the implementation
+ // of viewModel it must have a view name.
+ // However, this view name is not used in RecipeSearchView class.
super("Recipe Search");
}
}
From 10dc14f50f767f1f0b7dcbd7e832abeef76d2574 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 15:30:27 -0500
Subject: [PATCH 017/138] Fixed bugs in Interface Interactor
---
src/main/java/app/Main.java | 35 ++++++++++++
src/main/java/app/MainNoteApplication.java | 56 -------------------
.../recipe_search/RecipeSearchController.java | 26 +++++----
.../recipe_search/RecipeSearchPresenter.java | 46 +++++++++++++++
src/main/java/view/RecipeSearchView.java | 3 +-
5 files changed, 97 insertions(+), 69 deletions(-)
create mode 100644 src/main/java/app/Main.java
delete mode 100644 src/main/java/app/MainNoteApplication.java
create mode 100644 src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java
new file mode 100644
index 000000000..453e1cff3
--- /dev/null
+++ b/src/main/java/app/Main.java
@@ -0,0 +1,35 @@
+package app;
+
+import javax.swing.*;
+import data_access.RecipeSearchEdamam;
+import interface_adapter.recipe_search.*;
+import use_case.recipe_search.*;
+import view.RecipeSearchView;
+
+/**
+ * Main application class for the recipe search functionality.
+ */
+public class MainRecipeApplication {
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(() -> {
+ // Create components
+ RecipeSearchViewModel viewModel = new RecipeSearchViewModel();
+ RecipeSearchView view = new RecipeSearchView(viewModel);
+ RecipeSearchPresenter presenter = new RecipeSearchPresenter(viewModel);
+ RecipeSearchEdamam edamamAPI = new RecipeSearchEdamam();
+ RecipeSearch recipeSearchUseCase = new RecipeSearchImpl(edamamAPI, presenter);
+ RecipeSearchController controller = new RecipeSearchController(recipeSearchUseCase);
+
+ // Connect view to controller
+ view.setRecipeSearchController(controller);
+
+ // Create and show the frame
+ JFrame frame = new JFrame("Recipe Search");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.getContentPane().add(view);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+ frame.setVisible(true);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/app/MainNoteApplication.java b/src/main/java/app/MainNoteApplication.java
deleted file mode 100644
index 078d25304..000000000
--- a/src/main/java/app/MainNoteApplication.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package app;
-
-import data_access.DBNoteDataAccessObject;
-import use_case.note.NoteDataAccessInterface;
-
-/**
- * An application where we can view and add to a note stored by a user.
- *
- * This is a minimal example of using the password-protected user API from lab 5,
- * but demonstrating the endpoint allowing you to store an arbitrary JSON object.
- * This functionality could be used in any project where your team wants to persist
- * data which is then accessible across devices.
- *
The code is intentionally somewhat incomplete to leave work to be done if your
- * team were to choose to work on a project which would require similar functionality.
- * For example, we have intentionally not created a full "Note" entity here, but
- * rather just represented a note as a string.
- *
- * The ViewManager code has also been removed, since this minimal program only requires a single
- * view. Your team may wish to bring back the ViewManager or make your own implementation of supporting
- * switching views depending on your project.
- */
-public class MainNoteApplication {
-
- /**
- * The main entry point of the application.
- *
- * The program will show you the note currently saved in the system.
- * You are able to edit it and then save it to the system. You can refresh
- * to update the note to reflect what was saved most recently. This
- * uses the API from lab, so there is one database storing the note,
- * which means that if anyone updates the note, that is what you will
- * see when you refresh.
- *
- * You can generalize the code to allow you to
- * specify which "user" to save the note for, which will allow your team
- * to store information specific to your team which is password-protected.
- * The username and password used in this application are currently for
- * user jonathan_calver2, but you can change that. As you did in lab 3,
- * you will likely want to store password information locally rather than
- * in your repo. Or you can require the user to enter their credentials
- * in your application; it just depends on what your program's main
- * functionality.
- *
- * @param args commandline arguments are ignored
- */
- public static void main(String[] args) {
-
- // create the data access and inject it into our builder!
- final NoteDataAccessInterface noteDataAccess = new DBNoteDataAccessObject();
-
- final NoteAppBuilder builder = new NoteAppBuilder();
- builder.addNoteDAO(noteDataAccess)
- .addNoteView()
- .addNoteUseCase().build().setVisible(true);
- }
-}
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index f95f74bfe..a8e2a1930 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -1,27 +1,29 @@
package interface_adapter.recipe_search;
import use_case.recipe_search.RecipeSearch;
+import java.util.List;
/**
- * Controller for the RecipeSearch system.
+ * Controller for the recipe search functionality.
*/
public class RecipeSearchController {
- private final RecipeSearch RecipeSearchInteractor;
+ private final RecipeSearch recipeSearchUseCase;
- public RecipeSearchController(RecipeSearch RecipeSearchInteractor) {
- this.RecipeSearchInteractor = RecipeSearchInteractor;
+ public RecipeSearchController(RecipeSearch recipeSearchUseCase) {
+ this.recipeSearchUseCase = recipeSearchUseCase;
}
/**
- * Executes the RecipeSearch Use Case.
- * @param query The search query (e.g., recipe name, dish type).
- * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
- * @param number The number of results to return.
+ * Execute a recipe search with the given ingredients.
+ * @param ingredients List of ingredients to search for
*/
- public void execute(String query, int maxFat, int number) {
-
- RecipeSearchInteractor.searchRecipes(query, maxFat, number);
- // The method executed and parameters taken are based on the use case
+ public void executeSearch(List ingredients) {
+ try {
+ recipeSearchUseCase.searchRecipes(ingredients);
+ }
+ catch (Exception e) {
+ // Error will be handled by the presenter through output boundary
+ }
}
}
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
new file mode 100644
index 000000000..c46ec77b6
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
@@ -0,0 +1,46 @@
+package interface_adapter.recipe_search;
+
+import entity.Recipe;
+import entity.Ingredient;
+import use_case.recipe_search.RecipeSearchOutputBoundary;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Presenter for the recipe search functionality.
+ */
+public class RecipeSearchPresenter implements RecipeSearchOutputBoundary {
+ private final RecipeSearchViewModel viewModel;
+
+ public RecipeSearchPresenter(RecipeSearchViewModel viewModel) {
+ this.viewModel = viewModel;
+ }
+
+ @Override
+ public void presentRecipes(List recipes) {
+ RecipeSearchState state = new RecipeSearchState();
+ List recipeResults = new ArrayList<>();
+
+ for (Recipe recipe : recipes) {
+ StringBuilder description = new StringBuilder();
+ description.append(recipe.getTitle()).append("\n");
+ description.append("Ingredients:\n");
+ for (Ingredient ingredient : recipe.getIngredients()) {
+ description.append("- ").append(ingredient.getName()).append("\n");
+ }
+ recipeResults.add(description.toString());
+ }
+
+ state.setRecipeResults(recipeResults);
+ viewModel.setState(state);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentError(String error) {
+ RecipeSearchState state = new RecipeSearchState();
+ state.setError(error);
+ viewModel.setState(state);
+ viewModel.firePropertyChanged();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index b1edbec28..a6872876a 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -148,7 +148,8 @@ public void propertyChange(PropertyChangeEvent evt) {
if (state != null) {
if (state.getError() != null) {
JOptionPane.showMessageDialog(this, state.getError());
- } else {
+ }
+ else {
updateRecipeResults(state.getRecipeResults());
}
}
From 9c4838be3955738f761251428d8c9621ba55ca6d Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 15:34:44 -0500
Subject: [PATCH 018/138] New code for recipe search exception
---
.../recipe_search/RecipeSearchException.java | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 src/main/java/use_case/recipe_search/RecipeSearchException.java
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchException.java b/src/main/java/use_case/recipe_search/RecipeSearchException.java
new file mode 100644
index 000000000..692fc4044
--- /dev/null
+++ b/src/main/java/use_case/recipe_search/RecipeSearchException.java
@@ -0,0 +1,14 @@
+package use_case.recipe_search;
+
+/**
+ * Exception class for recipe search errors.
+ */
+public class RecipeSearchException extends RuntimeException {
+ public RecipeSearchException(String message) {
+ super(message);
+ }
+
+ public RecipeSearchException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
From 1b4ecf80e304a06194d277689745e286d429e484 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 15:42:13 -0500
Subject: [PATCH 019/138] Usecase for output boundary
---
.../{Main.java => MainRecipeApplication.java} | 0
.../RecipeSearchOutputBoundary.java | 21 +++++++++++++++++++
2 files changed, 21 insertions(+)
rename src/main/java/app/{Main.java => MainRecipeApplication.java} (100%)
create mode 100644 src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
diff --git a/src/main/java/app/Main.java b/src/main/java/app/MainRecipeApplication.java
similarity index 100%
rename from src/main/java/app/Main.java
rename to src/main/java/app/MainRecipeApplication.java
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java b/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
new file mode 100644
index 000000000..d96d9367d
--- /dev/null
+++ b/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
@@ -0,0 +1,21 @@
+package use_case.recipe_search;
+
+import entity.Recipe;
+import java.util.List;
+
+/**
+ * Output boundary for the recipe search use case.
+ */
+public interface RecipeSearchOutputBoundary {
+ /**
+ * Present the recipes to the user.
+ * @param recipes List of recipes to present
+ */
+ void presentRecipes(List recipes);
+
+ /**
+ * Present an error message to the user.
+ * @param error Error message to present
+ */
+ void presentError(String error);
+}
From 03a5dd1258917882cc3ca27a1bdad63342abc497 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 15:50:43 -0500
Subject: [PATCH 020/138] Implemented Main and Recipe App builder
---
src/main/java/app/MainRecipeApplication.java | 51 ++++++------
src/main/java/app/NoteAppBuilder.java | 83 -------------------
src/main/java/app/RecipeAppBuilder.java | 77 +++++++++++++++++
.../java/data_access/RecipeSearchEdamam.java | 3 +-
4 files changed, 106 insertions(+), 108 deletions(-)
delete mode 100644 src/main/java/app/NoteAppBuilder.java
create mode 100644 src/main/java/app/RecipeAppBuilder.java
diff --git a/src/main/java/app/MainRecipeApplication.java b/src/main/java/app/MainRecipeApplication.java
index 453e1cff3..2b9614692 100644
--- a/src/main/java/app/MainRecipeApplication.java
+++ b/src/main/java/app/MainRecipeApplication.java
@@ -1,35 +1,38 @@
package app;
-import javax.swing.*;
import data_access.RecipeSearchEdamam;
-import interface_adapter.recipe_search.*;
-import use_case.recipe_search.*;
-import view.RecipeSearchView;
/**
- * Main application class for the recipe search functionality.
+ * An application where users can search for recipes based on ingredients.
+ *
+ * This application allows users to input ingredients and search for matching recipes
+ * using the Edamam Recipe Search API. Users can view recipe details including
+ * ingredients and instructions.
+ *
*/
public class MainRecipeApplication {
- public static void main(String[] args) {
- SwingUtilities.invokeLater(() -> {
- // Create components
- RecipeSearchViewModel viewModel = new RecipeSearchViewModel();
- RecipeSearchView view = new RecipeSearchView(viewModel);
- RecipeSearchPresenter presenter = new RecipeSearchPresenter(viewModel);
- RecipeSearchEdamam edamamAPI = new RecipeSearchEdamam();
- RecipeSearch recipeSearchUseCase = new RecipeSearchImpl(edamamAPI, presenter);
- RecipeSearchController controller = new RecipeSearchController(recipeSearchUseCase);
- // Connect view to controller
- view.setRecipeSearchController(controller);
+ /**
+ * The main entry point of the application.
+ *
+ * The program will show a search interface where users can:
+ * - Add ingredients to their search
+ * - Remove ingredients from their search
+ * - Search for recipes matching their ingredients
+ * - View recipe results including ingredients and basic instructions
+ *
+ * @param args commandline arguments are ignored
+ */
+ public static void main(String[] args) {
+ // Create the data access object for recipe search
+ final RecipeSearchEdamam recipeSearchEdamam = new RecipeSearchEdamam();
- // Create and show the frame
- JFrame frame = new JFrame("Recipe Search");
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.getContentPane().add(view);
- frame.pack();
- frame.setLocationRelativeTo(null);
- frame.setVisible(true);
- });
+ // Create and configure the application using the builder
+ final RecipeAppBuilder builder = new RecipeAppBuilder();
+ builder.addRecipeSearchAPI(recipeSearchEdamam)
+ .addRecipeSearchView()
+ .addRecipeSearchUseCase()
+ .build()
+ .setVisible(true);
}
}
\ No newline at end of file
diff --git a/src/main/java/app/NoteAppBuilder.java b/src/main/java/app/NoteAppBuilder.java
deleted file mode 100644
index e78c0d1b4..000000000
--- a/src/main/java/app/NoteAppBuilder.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package app;
-
-import javax.swing.JFrame;
-import javax.swing.WindowConstants;
-
-import interface_adapter.note.NoteController;
-import interface_adapter.note.NotePresenter;
-import interface_adapter.note.NoteViewModel;
-import use_case.note.NoteDataAccessInterface;
-import use_case.note.NoteInteractor;
-import use_case.note.NoteOutputBoundary;
-import view.RecipeSearchView;
-
-/**
- * Builder for the Note Application.
- */
-public class NoteAppBuilder {
- public static final int HEIGHT = 300;
- public static final int WIDTH = 400;
- private NoteDataAccessInterface noteDAO;
- private NoteViewModel noteViewModel = new NoteViewModel();
- private RecipeSearchView recipeSearchView;
- private NoteInteractor noteInteractor;
-
- /**
- * Sets the NoteDAO to be used in this application.
- * @param noteDataAccess the DAO to use
- * @return this builder
- */
- public NoteAppBuilder addNoteDAO(NoteDataAccessInterface noteDataAccess) {
- noteDAO = noteDataAccess;
- return this;
- }
-
- /**
- * Creates the objects for the Note Use Case and connects the RecipeSearchView to its
- * controller.
- *
This method must be called after addNoteView!
- * @return this builder
- * @throws RuntimeException if this method is called before addNoteView
- */
- public NoteAppBuilder addNoteUseCase() {
- final NoteOutputBoundary noteOutputBoundary = new NotePresenter(noteViewModel);
- noteInteractor = new NoteInteractor(
- noteDAO, noteOutputBoundary);
-
- final NoteController controller = new NoteController(noteInteractor);
- if (recipeSearchView == null) {
- throw new RuntimeException("addNoteView must be called before addNoteUseCase");
- }
- recipeSearchView.setNoteController(controller);
- return this;
- }
-
- /**
- * Creates the RecipeSearchView and underlying NoteViewModel.
- * @return this builder
- */
- public NoteAppBuilder addNoteView() {
- noteViewModel = new NoteViewModel();
- recipeSearchView = new RecipeSearchView(noteViewModel);
- return this;
- }
-
- /**
- * Builds the application.
- * @return the JFrame for the application
- */
- public JFrame build() {
- final JFrame frame = new JFrame();
- frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- frame.setTitle("Note Application");
- frame.setSize(WIDTH, HEIGHT);
-
- frame.add(recipeSearchView);
-
- // refresh so that the note will be visible when we start the program
- noteInteractor.executeRefresh();
-
- return frame;
-
- }
-}
diff --git a/src/main/java/app/RecipeAppBuilder.java b/src/main/java/app/RecipeAppBuilder.java
new file mode 100644
index 000000000..834461a17
--- /dev/null
+++ b/src/main/java/app/RecipeAppBuilder.java
@@ -0,0 +1,77 @@
+package app;
+
+import javax.swing.JFrame;
+import javax.swing.WindowConstants;
+
+import data_access.RecipeSearchEdamam;
+import interface_adapter.recipe_search.*;
+import use_case.recipe_search.*;
+import view.RecipeSearchView;
+
+/**
+ * Builder for the Recipe Search Application.
+ */
+public class RecipeAppBuilder {
+ public static final int HEIGHT = 600; // Increased for better recipe display
+ public static final int WIDTH = 800; // Increased for better recipe display
+
+ private RecipeSearchEdamam recipeSearchEdamam;
+ private RecipeSearchViewModel recipeSearchViewModel;
+ private RecipeSearchView recipeSearchView;
+ private RecipeSearch recipeSearchUseCase;
+
+ /**
+ * Sets the Recipe Search API to be used in this application.
+ * @param recipeSearchEdamam the API client to use
+ * @return this builder
+ */
+ public RecipeAppBuilder addRecipeSearchAPI(RecipeSearchEdamam recipeSearchEdamam) {
+ this.recipeSearchEdamam = recipeSearchEdamam;
+ return this;
+ }
+
+ /**
+ * Creates the objects for the Recipe Search Use Case and connects the RecipeSearchView to its
+ * controller.
+ * @return this builder
+ * @throws RuntimeException if this method is called before addRecipeSearchView
+ */
+ public RecipeAppBuilder addRecipeSearchUseCase() {
+ if (recipeSearchView == null) {
+ throw new RuntimeException("addRecipeSearchView must be called before addRecipeSearchUseCase");
+ }
+
+ RecipeSearchPresenter presenter = new RecipeSearchPresenter(recipeSearchViewModel);
+ recipeSearchUseCase = new RecipeSearchImpl(recipeSearchEdamam, presenter);
+ RecipeSearchController controller = new RecipeSearchController(recipeSearchUseCase);
+ recipeSearchView.setRecipeSearchController(controller);
+
+ return this;
+ }
+
+ /**
+ * Creates the RecipeSearchView and underlying ViewModel.
+ * @return this builder
+ */
+ public RecipeAppBuilder addRecipeSearchView() {
+ recipeSearchViewModel = new RecipeSearchViewModel();
+ recipeSearchView = new RecipeSearchView(recipeSearchViewModel);
+ return this;
+ }
+
+ /**
+ * Builds the application.
+ * @return the JFrame for the application
+ */
+ public JFrame build() {
+ final JFrame frame = new JFrame();
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setTitle("Recipe Search Application");
+ frame.setSize(WIDTH, HEIGHT);
+
+ frame.add(recipeSearchView);
+ frame.setLocationRelativeTo(null); // Center on screen
+
+ return frame;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index b9e2098b9..b1703076d 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -55,7 +55,8 @@ public List searchRecipesByFoodName(String foodName) {
recipes.add(recipeForSearch);
}
}
- } catch (IOException e) {
+ }
+ catch (IOException e) {
throw new RuntimeException(e);
}
return recipes;
From d4d1cead1f289d25162937846f41b8c83a57b541 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 16:02:39 -0500
Subject: [PATCH 021/138] Bug fix in recipe search use case
---
.../use_case/recipe_search/RecipeSearch.java | 23 ++++++-------------
1 file changed, 7 insertions(+), 16 deletions(-)
diff --git a/src/main/java/use_case/recipe_search/RecipeSearch.java b/src/main/java/use_case/recipe_search/RecipeSearch.java
index 4cd702760..64423cfe3 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearch.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearch.java
@@ -1,25 +1,16 @@
package use_case.recipe_search;
-import java.util.List;
-
import entity.Recipe;
+import java.util.List;
/**
- * Interface for the use case that handles searching for recipes based on
- * various filters such as ingredients, cuisines, and nutritional information.
- * This use case allows the application to fetch recipes by making calls to
- * external APIs or services.
+ * Interface for the recipe search use case.
*/
public interface RecipeSearch {
-
/**
- * Searches for recipes based on the provided query and filters.
- *
- * @param query The search query (e.g., recipe name, dish type).
- * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
- * @param number The number of results to return.
- * @return A list of recipes that match the search criteria.
- * @throws RecipeSearchException if an error occurs while fetching the recipes.
+ * Searches for recipes based on a list of ingredients.
+ * @param ingredients List of ingredients to search with
+ * @throws RecipeSearchException if search fails
*/
- List searchRecipes(String query, int maxFat, int number) throws RecipeSearchException;
-}
+ void searchRecipes(List ingredients) throws RecipeSearchException;
+}
\ No newline at end of file
From 9d24234003379b907f7c502f4c4436286ae0b635 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Mon, 11 Nov 2024 16:21:45 -0500
Subject: [PATCH 022/138] new version of recipe search impl
---
.../recipe_search/RecipeSearchImpl.java | 99 ++++++++++++-------
1 file changed, 65 insertions(+), 34 deletions(-)
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
index 24e774598..ef107135c 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
@@ -1,47 +1,78 @@
package use_case.recipe_search;
-import java.util.List;
import entity.Recipe;
-import repository.RecipeRepository;
-import exception.RecipeSearchException;
+import entity.RecipeForSearch;
+import entity.Ingredient;
+import entity.Nutrition;
+import data_access.RecipeSearchEdamam;
+import java.util.List;
+import java.util.ArrayList;
/**
- * Implementation of the RecipeSearchUseCase interface. This class is responsible
- * for searching recipes using external APIs or data sources. It delegates the actual
- * search operation to the RecipeApiGateway.
+ * Implementation of the recipe search use case.
*/
public class RecipeSearchImpl implements RecipeSearch {
+ private final RecipeSearchEdamam recipeSearchEdamam;
+ private final RecipeSearchOutputBoundary outputBoundary;
- private final RecipeRepository recipeRepository;
-
- /**
- * Constructor for the RecipeSearchUseCaseImpl.
- * Initializes the RecipeApiGateway which is used to fetch recipe data.
- *
- * @param recipeRepository The gateway for accessing the external API or service.
- */
- public RecipeSearchImpl(RecipeRepository recipeRepository) {
- this.recipeRepository = recipeRepository;
+ public RecipeSearchImpl(RecipeSearchEdamam recipeSearchEdamam,
+ RecipeSearchOutputBoundary outputBoundary) {
+ this.recipeSearchEdamam = recipeSearchEdamam;
+ this.outputBoundary = outputBoundary;
}
- /**
- * Searches for recipes based on the provided search parameters.
- * This method calls the RecipeApiGateway to fetch recipe data and returns
- * the result as a list of Recipe objects.
- *
- * @param query The search query (e.g., recipe name, dish type).
- * @param maxFat The maximum amount of fat in grams the recipe can have per serving.
- * @param number The number of results to return.
- * @return A list of recipes that match the search criteria.
- * @throws RecipeSearchException if an error occurs while searching for recipes.
- */
- public List searchRecipes(String query, int maxFat, int number) throws RecipeSearchException {
+ @Override
+ public void searchRecipes(List ingredients) throws RecipeSearchException {
try {
- // Use the recipeApiGateway to fetch recipes based on the parameters
- return recipeRepository.fetchRecipes(query, maxFat, number);
- } catch (Exception e) {
- // Wrap any errors into a custom exception
- throw new RecipeSearchException("Failed to search recipes", e);
+ // Join ingredients with commas for the API search
+ String searchQuery = String.join(",", ingredients);
+
+ // Get recipes from Edamam API
+ List searchResults = recipeSearchEdamam.searchRecipesByFoodName(searchQuery);
+
+ // Convert API results to Recipe entities
+ List recipes = convertToRecipes(searchResults);
+
+ // Present success
+ outputBoundary.presentRecipes(recipes);
+ }
+ catch (Exception e) {
+ // Present error
+ outputBoundary.presentError("Failed to search recipes: " + e.getMessage());
+ throw new RecipeSearchException("Recipe search failed", e);
}
}
-}
+
+ private List convertToRecipes(List searchResults) {
+ List recipes = new ArrayList<>();
+ int recipeId = 1;
+
+ for (RecipeForSearch result : searchResults) {
+ // Convert ingredients to Ingredient entities
+ List ingredients = new ArrayList<>();
+ int ingredientId = 1;
+ for (String ingredientStr : result.getIngredients()) {
+ ingredients.add(new Ingredient(
+ ingredientId++,
+ ingredientStr,
+ 0.0, // Default quantity
+ "" // Default unit
+ ));
+ }
+
+ // Create Recipe entity
+ Recipe recipe = new Recipe(
+ recipeId++,
+ result.getTitle(),
+ ingredients,
+ result.getInstructions(),
+ new Nutrition(0, 0, 0, 0, 0, 0), // Default nutrition
+ new ArrayList<>() // Empty food list
+ );
+
+ recipes.add(recipe);
+ }
+
+ return recipes;
+ }
+}
\ No newline at end of file
From 4efe2e9f301446214797a5f7a8aa1295e57ac0da Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Wed, 13 Nov 2024 19:11:40 -0500
Subject: [PATCH 023/138] Add Starting page view
---
src/main/java/view/StartPageView.java | 53 +++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 src/main/java/view/StartPageView.java
diff --git a/src/main/java/view/StartPageView.java b/src/main/java/view/StartPageView.java
new file mode 100644
index 000000000..15cb16fbc
--- /dev/null
+++ b/src/main/java/view/StartPageView.java
@@ -0,0 +1,53 @@
+package view;
+
+import java.awt.*;
+import javax.swing.*;
+
+/**
+ * A view for the starting page.
+ */
+public class StartPageView extends JFrame {
+
+ private static final int WINDOW_WIDTH = 400;
+ private static final int WINDOW_HEIGHT = 300;
+ private static final int TITLE_FONT_SIZE = 20;
+ private static final int TITLE_PADDING_TOP = 80;
+ private static final int TITLE_BUTTON_SPACING = 50;
+ private static final int BUTTON_HORIZONTAL_SPACING = 90;
+ private static final int BUTTON_VERTICAL_SPACING = 20;
+
+ public StartPageView() {
+ super("Login to Start");
+
+ setLayout(new BorderLayout());
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
+ setLocationRelativeTo(null);
+
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+ panel.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel("Welcome to Recipe & Meal Planner!", JLabel.CENTER);
+ titleLabel.setFont(new Font("Arial", Font.BOLD, TITLE_FONT_SIZE));
+ titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+ panel.add(Box.createVerticalStrut(TITLE_PADDING_TOP));
+
+ panel.add(titleLabel);
+
+ panel.add(Box.createVerticalStrut(TITLE_BUTTON_SPACING));
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER, BUTTON_HORIZONTAL_SPACING, BUTTON_VERTICAL_SPACING));
+ JButton signUpButton = new JButton("Sign Up");
+ JButton logInButton = new JButton("Log In");
+ buttonPanel.add(signUpButton);
+ buttonPanel.add(logInButton);
+
+ panel.add(buttonPanel);
+
+ add(panel, BorderLayout.CENTER);
+
+ setVisible(true);
+ }
+}
From 855d5e92509356f10568efafd0f51a07a777d7ac Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:43:04 -0500
Subject: [PATCH 024/138] Add a function in data_access
---
.../java/data_access/RecipeSearchEdamam.java | 60 +++++++++++++++++++
src/main/java/view/FilterFrameView.java | 4 ++
2 files changed, 64 insertions(+)
create mode 100644 src/main/java/view/FilterFrameView.java
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index b1703076d..899d1155e 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -18,6 +18,9 @@ public class RecipeSearchEdamam {
private static final String BASE_URL = "https://api.edamam.com/api/recipes/v2";
private final OkHttpClient httpClient = new OkHttpClient();
+ /**
+ * Get recipes by filling only food name.
+ */
public List searchRecipesByFoodName(String foodName) {
List recipes = new ArrayList<>();
@@ -61,4 +64,61 @@ public List searchRecipesByFoodName(String foodName) {
}
return recipes;
}
+
+ /**
+ * Get recipes by selecting restrictions.
+ */
+ public List searchRecipesByRestriction(String foodName, String diet, String health, String cuisineType) {
+ List recipes = new ArrayList<>();
+
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
+ urlBuilder.addQueryParameter("type", "public");
+ urlBuilder.addQueryParameter("q", foodName);
+ urlBuilder.addQueryParameter("app_id", APP_ID);
+ urlBuilder.addQueryParameter("app_key", APP_KEY);
+
+ // Add optional filters if provided
+ if (diet != null && !diet.isEmpty()) {
+ urlBuilder.addQueryParameter("diet", diet);
+ }
+ if (health != null && !health.isEmpty()) {
+ urlBuilder.addQueryParameter("health", health);
+ }
+ if (cuisineType != null && !cuisineType.isEmpty()) {
+ urlBuilder.addQueryParameter("cuisineType", cuisineType);
+ }
+
+ Request request = new Request.Builder()
+ .url(urlBuilder.build().toString()).build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (response.body() != null) {
+ String jsonData = response.body().string();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ JSONArray hitsArray = jsonObject.getJSONArray("hits");
+
+ for (int i = 0; i < hitsArray.length(); i++) {
+ JSONObject recipeJson = hitsArray.getJSONObject(i).getJSONObject("recipe");
+
+ // Extract recipe details
+ String name = recipeJson.getString("label");
+ String description = recipeJson.optString("source", "No description available");
+
+ // Extract ingredients
+ List ingredients = new ArrayList<>();
+ JSONArray ingredientsArray = recipeJson.getJSONArray("ingredientLines");
+ for (int j = 0; j < ingredientsArray.length(); j++) {
+ ingredients.add(ingredientsArray.getString(j));
+ }
+
+ // Create and add Recipe entity
+ RecipeForSearch recipeForSearch = new RecipeForSearch(name, description, ingredients);
+ recipes.add(recipeForSearch);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return recipes;
+ }
}
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
new file mode 100644
index 000000000..7e1093a8a
--- /dev/null
+++ b/src/main/java/view/FilterFrameView.java
@@ -0,0 +1,4 @@
+package view;
+
+public class AddRestrictionView {
+}
From a5d6b0924266ebb2ed44495b361ef27e84005390 Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:45:50 -0500
Subject: [PATCH 025/138] Add function in RecipeSearchImpl
---
.../recipe_search/RecipeSearchImpl.java | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
index ef107135c..939d9c94a 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
@@ -7,6 +7,7 @@
import data_access.RecipeSearchEdamam;
import java.util.List;
import java.util.ArrayList;
+import java.util.Map;
/**
* Implementation of the recipe search use case.
@@ -43,6 +44,31 @@ public void searchRecipes(List ingredients) throws RecipeSearchException
}
}
+ @Override
+ public void searchRestrictionRecipes(Map> restrictions) throws RecipeSearchException {
+ try {
+ // Join ingredients with commas for the API search
+ String searchFoodQuery = String.join(",", restrictions.get("Food Name"));
+ String searchDietQuery = String.join(",", restrictions.get("Diet Label"));
+ String searchHealthQuery = String.join(",", restrictions.get("Health Label"));
+ String searchCuisineQuery = String.join(",", restrictions.get("Cuisine Type"));
+
+ // Get recipes from Edamam API
+ List searchResults = recipeSearchEdamam.searchRecipesByRestriction(searchFoodQuery, searchDietQuery, searchHealthQuery, searchCuisineQuery);
+
+ // Convert API results to Recipe entities
+ List recipes = convertToRecipes(searchResults);
+
+ // Present success
+ outputBoundary.presentRecipes(recipes);
+ }
+ catch (Exception e) {
+ // Present error
+ outputBoundary.presentError("Failed to search recipes: " + e.getMessage());
+ throw new RecipeSearchException("Recipe search failed", e);
+ }
+ }
+
private List convertToRecipes(List searchResults) {
List recipes = new ArrayList<>();
int recipeId = 1;
From 503d4c033eff50e343cd557a46318f176b938c7e Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:47:03 -0500
Subject: [PATCH 026/138] Add function in adapter
---
src/main/java/use_case/recipe_search/RecipeSearch.java | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/main/java/use_case/recipe_search/RecipeSearch.java b/src/main/java/use_case/recipe_search/RecipeSearch.java
index 64423cfe3..5f4633a1a 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearch.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearch.java
@@ -2,6 +2,7 @@
import entity.Recipe;
import java.util.List;
+import java.util.Map;
/**
* Interface for the recipe search use case.
@@ -13,4 +14,11 @@ public interface RecipeSearch {
* @throws RecipeSearchException if search fails
*/
void searchRecipes(List ingredients) throws RecipeSearchException;
+
+ /**
+ * Searches for recipes based on a map of restrictions.
+ * @param restrictions Map of restrictions to search with
+ * @throws RecipeSearchException if search fails
+ */
+ void searchRestrictionRecipes(Map> restrictions) throws RecipeSearchException;
}
\ No newline at end of file
From 68883692f68d69ddd868bf19cdf76e3957c4bee2 Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:48:34 -0500
Subject: [PATCH 027/138] Adjust contoller
---
.../recipe_search/RecipeSearchController.java | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index a8e2a1930..a65be3d50 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -2,6 +2,7 @@
import use_case.recipe_search.RecipeSearch;
import java.util.List;
+import java.util.Map;
/**
* Controller for the recipe search functionality.
@@ -25,5 +26,18 @@ public void executeSearch(List ingredients) {
// Error will be handled by the presenter through output boundary
}
}
+
+ /**
+ * Execute a recipe search with the given restrictions.
+ * @param ingredients Map of ingredients to search for
+ */
+ public void executeRestrictionSearch(Map> ingredients) {
+ try {
+ recipeSearchUseCase.searchRestrictionRecipes(ingredients);
+ }
+ catch (Exception e) {
+ // Error will be handled by the presenter through output boundary
+ }
+ }
}
From e8f7858d422f4515de3ad2e8e93119231d85795c Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:50:29 -0500
Subject: [PATCH 028/138] Add view according to the user story-4
---
src/main/java/view/FilterFrameView.java | 215 ++++++++++++++++++++++-
src/main/java/view/RecipeSearchView.java | 75 ++++++--
2 files changed, 279 insertions(+), 11 deletions(-)
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
index 7e1093a8a..78639f788 100644
--- a/src/main/java/view/FilterFrameView.java
+++ b/src/main/java/view/FilterFrameView.java
@@ -1,4 +1,217 @@
package view;
-public class AddRestrictionView {
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class FilterFrameView extends JFrame {
+ private static final int WINDOW_WIDTH = 500;
+ private static final int WINDOW_HEIGHT = 500;
+
+ private final RecipeSearchView mainFrame; // 主窗口引用,用于回传用户选择
+ private final JTextArea resultArea; // 用于展示筛选结果
+
+ // 添加筛选选项面板的实例变量
+ private final JPanel dietPanel;
+ private final JPanel healthPanel;
+ private final JPanel cuisinePanel;
+
+ // 筛选选项
+ private static final String[] DIET_TYPES = {"balanced", "high-fiber", "high-protein", "low-carb", "low-fat", "low-sodium"};
+ private static final String[] DIET_RESTRICTIONS = {"alcohol-cocktail", "alcohol-free", "celery-free", "crustacean-free",
+ "dairy-free", "DASH", "egg-free", "fish-free", "fodmap-free", "gluten-free", "immuno-supportive", "keto-friendly",
+ "kidney-friendly", "kosher", "low-fat-abs", "low-potassium", "low-sugar", "lupine-free", "Mediterranean", "mollusk-free",
+ "mustard-free", "no-oil-added", "paleo", "peanut-free", "pescatarian", "pork-free", "red-meat-free", "sesame-free",
+ "shellfish-free", "soy-free", "sugar-conscious", "sulfite-free", "tree-nut-free", "vegan", "vegetarian", "wheat-free"};
+ private static final String[] CUISINE_TYPES = {"American", "Asian", "British", "Caribbean", "Central Europe", "Chinese",
+ "Eastern Europe", "French", "Indian", "Italian", "Japanese", "Kosher", "Mediterranean", "Mexican", "Middle Eastern",
+ "Nordic", "South American", "South East Asian"};
+
+ public FilterFrameView(RecipeSearchView mainFrame) {
+ this.mainFrame = mainFrame;
+
+ setTitle("Filter Page");
+ setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ setLocationRelativeTo(null);
+
+ // 创建面板
+ JPanel panel = new JPanel();
+ panel.setLayout(new BorderLayout(10, 10));
+
+ // 创建标签
+ JLabel label = new JLabel("Select Filters:", JLabel.CENTER);
+ label.setFont(new Font("Arial", Font.PLAIN, 16));
+ panel.add(label, BorderLayout.NORTH);
+
+ // 创建中间面板,放置滚动复选框
+ JPanel checkBoxPanel = new JPanel();
+ checkBoxPanel.setLayout(new GridLayout(3, 1, 10, 10)); // 3行布局
+
+ // 使用类实例变量初始化筛选项面板
+ dietPanel = createScrollableCheckBoxPanel("Diet Label", DIET_TYPES);
+ healthPanel = createScrollableCheckBoxPanel("Health Label", DIET_RESTRICTIONS);
+ cuisinePanel = createScrollableCheckBoxPanel("Cuisine Type", CUISINE_TYPES);
+
+ checkBoxPanel.add(dietPanel);
+ checkBoxPanel.add(healthPanel);
+ checkBoxPanel.add(cuisinePanel);
+
+ panel.add(checkBoxPanel, BorderLayout.CENTER);
+
+ // 用于展示筛选结果的区域
+ resultArea = new JTextArea(5, 30);
+ resultArea.setEditable(false); // 不可编辑
+ resultArea.setFont(new Font("Arial", Font.PLAIN, 14));
+ panel.add(new JScrollPane(resultArea), BorderLayout.SOUTH);
+
+ // 添加 Cancel 和 Confirm 按钮
+ JPanel buttonPanel = new JPanel();
+ JButton cancelButton = new JButton("Cancel");
+ cancelButton.addActionListener(e -> cancelAction());
+ JButton confirmButton = new JButton("Confirm");
+ confirmButton.addActionListener(e -> confirmAction());
+
+ buttonPanel.add(cancelButton);
+ buttonPanel.add(confirmButton);
+
+ panel.add(buttonPanel, BorderLayout.SOUTH);
+
+ // 将面板添加到窗口
+ add(panel);
+ setVisible(true);
+ }
+
+ // 创建带滚动条的复选框面板
+ private JPanel createScrollableCheckBoxPanel(String label, String[] items) {
+ JPanel panel = new JPanel();
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); // 垂直排列复选框
+ panel.setBorder(BorderFactory.createTitledBorder(label));
+
+ // 为每个选项添加一个 JCheckBox
+ for (String item : items) {
+ panel.add(new JCheckBox(item));
+ }
+
+ // 创建一个滚动面板,包装 JPanel
+ JScrollPane scrollPane = new JScrollPane(panel);
+ scrollPane.setPreferredSize(new Dimension(400, 120)); // 设置滚动面板的大小
+
+ JPanel wrappedPanel = new JPanel(new BorderLayout());
+ wrappedPanel.add(scrollPane, BorderLayout.CENTER);
+
+ return wrappedPanel;
+ }
+
+ // 获取面板中所有的 JCheckBox
+ private JCheckBox[] getCheckBoxes(JPanel panel) {
+ for (Component component : panel.getComponents()) {
+ if (component instanceof JScrollPane) {
+ JPanel innerPanel = (JPanel) ((JScrollPane) component).getViewport().getView();
+ Component[] components = innerPanel.getComponents();
+ JCheckBox[] checkBoxes = new JCheckBox[components.length];
+ for (int i = 0; i < components.length; i++) {
+ checkBoxes[i] = (JCheckBox) components[i];
+ }
+ return checkBoxes;
+ }
+ }
+ return new JCheckBox[0];
+ }
+
+ // 获取已选择的筛选内容
+ public String getSelectedFilters() {
+// StringBuilder resultText = new StringBuilder("Selected Filters:\n");
+ StringBuilder resultText = new StringBuilder();
+
+ // Diet Label 选择的项
+// resultText.append("Diet Types: ");
+ for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
+ if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
+ }
+
+ // 去掉最后的逗号和空格
+ if (resultText.toString().endsWith(", ")) {
+ resultText.setLength(resultText.length() - 2);
+ }
+
+ resultText.append("\n");
+
+ // Health Label 选择的项
+// resultText.append("Health Labels: ");
+ for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
+ if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
+ }
+
+ // 去掉最后的逗号和空格
+ if (resultText.toString().endsWith(", ")) {
+ resultText.setLength(resultText.length() - 2);
+ }
+
+ resultText.append("\n");
+
+ // Cuisine Type 选择的项
+// resultText.append("Cuisine Types: ");
+ for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
+ if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
+ }
+
+ // 去掉最后的逗号和空格
+ if (resultText.toString().endsWith(", ")) {
+ resultText.setLength(resultText.length() - 2);
+ }
+
+ return resultText.toString();
+ }
+
+ public Map> getSelectedFiltersMap() {
+ final Map> filtersMap = new HashMap<>();
+
+ // Diet Label 选择的项
+ for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
+ if (checkBox.isSelected()) {
+ addValueToKey(filtersMap, "Diet Types", checkBox.getText());
+ }
+ }
+
+ // Health Label 选择的项
+ for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
+ if (checkBox.isSelected()) {
+ addValueToKey(filtersMap, "Health Labels", checkBox.getText());
+ }
+ }
+
+ // Cuisine Type 选择的项
+ for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
+ if (checkBox.isSelected()) {
+ addValueToKey(filtersMap, "Cuisine Types", checkBox.getText());
+ }
+ }
+
+ return filtersMap;
+ }
+
+ // 添加值到指定的 key 中,如果 key 不存在则新建一个 List
+ private static void addValueToKey(Map> map, String key, String value) {
+ // 如果 map 中已经存在这个 key,直接添加值到 List
+ map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
+ }
+
+ // 点击 Cancel 按钮
+ private void cancelAction() {
+ // 直接回到主页面
+ mainFrame.setVisible(true);
+ this.setVisible(false);
+ }
+
+ // 点击 Confirm 按钮
+ private void confirmAction() {
+ String selectedFilters = getSelectedFilters();
+ mainFrame.updateSelection(selectedFilters);
+ this.setVisible(false);
+ }
}
+
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index a6872876a..46f0deb41 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -13,7 +13,9 @@
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -51,12 +53,19 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private final DefaultListModel recipeListModel;
private final JList recipeResults;
private final List ingredients;
+ private final JPanel restrictionPanel;
+ private final JButton addRestrictionButton;
+ private final DefaultListModel restrictionListModel;
+ private final JList restrictionList;
+ private final List restrictions;
+ private Map> restrictionMap;
private final RecipeSearchViewModel recipeSearchViewModel;
private RecipeSearchController recipeSearchController;
/**
* Constructs a new RecipeSearchView.
+ *
* @param viewModel The view model for recipe search
*/
public RecipeSearchView(RecipeSearchViewModel viewModel) {
@@ -64,18 +73,35 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
this.recipeSearchViewModel.addPropertyChangeListener(this);
// Initialize components
+ // Labels
title = new JLabel(RecipeSearchViewModel.TITLE_LABEL);
+
+ // Panels
inputPanel = new JPanel();
+ restrictionPanel = new JPanel();
+ resultsPanel = new JPanel();
+
+ // Text Field
ingredientField = new JTextField(TEXTFIELD_WIDTH);
+
+ // Buttons
addIngredientButton = new JButton(RecipeSearchViewModel.ADD_INGREDIENT_BUTTON_LABEL);
- ingredientListModel = new DefaultListModel<>();
- ingredientList = new JList<>(ingredientListModel);
removeIngredientButton = new JButton(RecipeSearchViewModel.REMOVE_INGREDIENT_BUTTON_LABEL);
searchButton = new JButton(RecipeSearchViewModel.SEARCH_BUTTON_LABEL);
- resultsPanel = new JPanel();
+ addRestrictionButton = new JButton(RecipeSearchViewModel.ADD_RESTRICTION_LABEL);
+
+ // Lists and Models
+ ingredientListModel = new DefaultListModel<>();
+ ingredientList = new JList<>(ingredientListModel);
+ restrictionListModel = new DefaultListModel<>();
+ restrictionList = new JList<>(restrictionListModel);
recipeListModel = new DefaultListModel<>();
recipeResults = new JList<>(recipeListModel);
+
+ // Data Structures
ingredients = new ArrayList<>();
+ restrictions = new ArrayList<>();
+ restrictionMap = new HashMap<>();
// Set up the main layout
this.setLayout(new BorderLayout());
@@ -92,6 +118,7 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
addIngredientButton.addActionListener(this);
removeIngredientButton.addActionListener(this);
searchButton.addActionListener(this);
+ addRestrictionButton.addActionListener(this);
// Main panel assembly
final JPanel mainPanel = new JPanel();
@@ -101,6 +128,8 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
mainPanel.add(inputPanel);
mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
+ mainPanel.add(restrictionPanel);
+ mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
mainPanel.add(searchButton);
mainPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
mainPanel.add(resultsPanel);
@@ -110,6 +139,7 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
/**
* Sets the controller for this view.
+ *
* @param controller The controller to set
*/
public void setRecipeSearchController(RecipeSearchController controller) {
@@ -124,20 +154,32 @@ public void actionPerformed(ActionEvent evt) {
final String ingredient = ingredientField.getText().trim();
if (!ingredient.isEmpty()) {
ingredients.add(ingredient);
+ addValueToKey(restrictionMap, "Food Name", ingredient);
ingredientListModel.addElement(ingredient);
ingredientField.setText("");
}
- }
- else if (evt.getSource().equals(removeIngredientButton)) {
+ } else if (evt.getSource().equals(addRestrictionButton)) {
+ FilterFrameView filterFrame = new FilterFrameView(this);
+ final String restriction = filterFrame.getSelectedFilters().trim();
+ restrictionMap = filterFrame.getSelectedFiltersMap();
+
+ if (!restrictions.isEmpty()) {
+ restrictions.add(restriction);
+ restrictionListModel.addElement(restriction);
+ }
+ } else if (evt.getSource().equals(removeIngredientButton)) {
final int selectedIndex = ingredientList.getSelectedIndex();
if (selectedIndex != -1) {
ingredients.remove(selectedIndex);
ingredientListModel.remove(selectedIndex);
}
- }
- else if (evt.getSource().equals(searchButton)) {
+ } else if (evt.getSource().equals(searchButton)) {
if (recipeSearchController != null) {
- recipeSearchController.executeSearch(new ArrayList<>(ingredients));
+ if (restrictions.isEmpty()) {
+ recipeSearchController.executeSearch(new ArrayList<>(ingredients));
+ } else {
+ recipeSearchController.executeRestrictionSearch(restrictionMap);
+ }
}
}
}
@@ -148,13 +190,16 @@ public void propertyChange(PropertyChangeEvent evt) {
if (state != null) {
if (state.getError() != null) {
JOptionPane.showMessageDialog(this, state.getError());
- }
- else {
+ } else {
updateRecipeResults(state.getRecipeResults());
}
}
}
+ public void updateSelection(String selectionText) {
+ restrictionListModel.addElement(selectionText);
+ }
+
private void setupInputPanel() {
inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
@@ -162,17 +207,23 @@ private void setupInputPanel() {
addIngredientPanel.add(new JLabel("Enter Ingredient:"));
addIngredientPanel.add(ingredientField);
addIngredientPanel.add(addIngredientButton);
+ addIngredientPanel.add(addRestrictionButton);
final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
ingredientScrollPane.setPreferredSize(
new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
+ final JScrollPane restrictionScrollPane = new JScrollPane(restrictionList);
+ ingredientScrollPane.setPreferredSize(
+ new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
+
final JPanel removeButtonPanel = new JPanel();
removeButtonPanel.add(removeIngredientButton);
inputPanel.add(addIngredientPanel);
inputPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
inputPanel.add(ingredientScrollPane);
+ inputPanel.add(restrictionScrollPane);
inputPanel.add(removeButtonPanel);
}
@@ -197,4 +248,8 @@ private void updateRecipeResults(List recipes) {
recipeListModel.addElement(recipe);
}
}
+
+ private static void addValueToKey(Map> map, String key, String value) {
+ map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
+ }
}
\ No newline at end of file
From 5d0f6cbacc9a20cebc78d788bd91dbdd5b7f594a Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:54:01 -0500
Subject: [PATCH 029/138] Fix bug in FilterFrameView
---
src/main/java/view/FilterFrameView.java | 48 ++++++-------------------
1 file changed, 11 insertions(+), 37 deletions(-)
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
index 78639f788..172b39057 100644
--- a/src/main/java/view/FilterFrameView.java
+++ b/src/main/java/view/FilterFrameView.java
@@ -11,15 +11,13 @@ public class FilterFrameView extends JFrame {
private static final int WINDOW_WIDTH = 500;
private static final int WINDOW_HEIGHT = 500;
- private final RecipeSearchView mainFrame; // 主窗口引用,用于回传用户选择
- private final JTextArea resultArea; // 用于展示筛选结果
+ private final RecipeSearchView mainFrame;
+ private final JTextArea resultArea;
- // 添加筛选选项面板的实例变量
private final JPanel dietPanel;
private final JPanel healthPanel;
private final JPanel cuisinePanel;
- // 筛选选项
private static final String[] DIET_TYPES = {"balanced", "high-fiber", "high-protein", "low-carb", "low-fat", "low-sodium"};
private static final String[] DIET_RESTRICTIONS = {"alcohol-cocktail", "alcohol-free", "celery-free", "crustacean-free",
"dairy-free", "DASH", "egg-free", "fish-free", "fodmap-free", "gluten-free", "immuno-supportive", "keto-friendly",
@@ -38,20 +36,16 @@ public FilterFrameView(RecipeSearchView mainFrame) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
- // 创建面板
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout(10, 10));
- // 创建标签
JLabel label = new JLabel("Select Filters:", JLabel.CENTER);
label.setFont(new Font("Arial", Font.PLAIN, 16));
panel.add(label, BorderLayout.NORTH);
- // 创建中间面板,放置滚动复选框
JPanel checkBoxPanel = new JPanel();
checkBoxPanel.setLayout(new GridLayout(3, 1, 10, 10)); // 3行布局
- // 使用类实例变量初始化筛选项面板
dietPanel = createScrollableCheckBoxPanel("Diet Label", DIET_TYPES);
healthPanel = createScrollableCheckBoxPanel("Health Label", DIET_RESTRICTIONS);
cuisinePanel = createScrollableCheckBoxPanel("Cuisine Type", CUISINE_TYPES);
@@ -62,13 +56,11 @@ public FilterFrameView(RecipeSearchView mainFrame) {
panel.add(checkBoxPanel, BorderLayout.CENTER);
- // 用于展示筛选结果的区域
resultArea = new JTextArea(5, 30);
- resultArea.setEditable(false); // 不可编辑
+ resultArea.setEditable(false);
resultArea.setFont(new Font("Arial", Font.PLAIN, 14));
panel.add(new JScrollPane(resultArea), BorderLayout.SOUTH);
- // 添加 Cancel 和 Confirm 按钮
JPanel buttonPanel = new JPanel();
JButton cancelButton = new JButton("Cancel");
cancelButton.addActionListener(e -> cancelAction());
@@ -80,25 +72,21 @@ public FilterFrameView(RecipeSearchView mainFrame) {
panel.add(buttonPanel, BorderLayout.SOUTH);
- // 将面板添加到窗口
add(panel);
setVisible(true);
}
- // 创建带滚动条的复选框面板
private JPanel createScrollableCheckBoxPanel(String label, String[] items) {
JPanel panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); // 垂直排列复选框
+ panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(label));
- // 为每个选项添加一个 JCheckBox
for (String item : items) {
panel.add(new JCheckBox(item));
}
- // 创建一个滚动面板,包装 JPanel
JScrollPane scrollPane = new JScrollPane(panel);
- scrollPane.setPreferredSize(new Dimension(400, 120)); // 设置滚动面板的大小
+ scrollPane.setPreferredSize(new Dimension(400, 120));
JPanel wrappedPanel = new JPanel(new BorderLayout());
wrappedPanel.add(scrollPane, BorderLayout.CENTER);
@@ -106,7 +94,6 @@ private JPanel createScrollableCheckBoxPanel(String label, String[] items) {
return wrappedPanel;
}
- // 获取面板中所有的 JCheckBox
private JCheckBox[] getCheckBoxes(JPanel panel) {
for (Component component : panel.getComponents()) {
if (component instanceof JScrollPane) {
@@ -122,44 +109,36 @@ private JCheckBox[] getCheckBoxes(JPanel panel) {
return new JCheckBox[0];
}
- // 获取已选择的筛选内容
public String getSelectedFilters() {
-// StringBuilder resultText = new StringBuilder("Selected Filters:\n");
StringBuilder resultText = new StringBuilder();
- // Diet Label 选择的项
-// resultText.append("Diet Types: ");
+ // Diet Label
for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
}
- // 去掉最后的逗号和空格
if (resultText.toString().endsWith(", ")) {
resultText.setLength(resultText.length() - 2);
}
resultText.append("\n");
- // Health Label 选择的项
-// resultText.append("Health Labels: ");
+ // Health Label
for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
}
- // 去掉最后的逗号和空格
if (resultText.toString().endsWith(", ")) {
resultText.setLength(resultText.length() - 2);
}
resultText.append("\n");
- // Cuisine Type 选择的项
-// resultText.append("Cuisine Types: ");
+ // Cuisine Type
for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
if (checkBox.isSelected()) resultText.append(checkBox.getText()).append(", ");
}
- // 去掉最后的逗号和空格
if (resultText.toString().endsWith(", ")) {
resultText.setLength(resultText.length() - 2);
}
@@ -170,21 +149,21 @@ public String getSelectedFilters() {
public Map> getSelectedFiltersMap() {
final Map> filtersMap = new HashMap<>();
- // Diet Label 选择的项
+ // Diet Label
for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
if (checkBox.isSelected()) {
addValueToKey(filtersMap, "Diet Types", checkBox.getText());
}
}
- // Health Label 选择的项
+ // Health Label
for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
if (checkBox.isSelected()) {
addValueToKey(filtersMap, "Health Labels", checkBox.getText());
}
}
- // Cuisine Type 选择的项
+ // Cuisine Type
for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
if (checkBox.isSelected()) {
addValueToKey(filtersMap, "Cuisine Types", checkBox.getText());
@@ -194,20 +173,15 @@ public Map> getSelectedFiltersMap() {
return filtersMap;
}
- // 添加值到指定的 key 中,如果 key 不存在则新建一个 List
private static void addValueToKey(Map> map, String key, String value) {
- // 如果 map 中已经存在这个 key,直接添加值到 List
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
}
- // 点击 Cancel 按钮
private void cancelAction() {
- // 直接回到主页面
mainFrame.setVisible(true);
this.setVisible(false);
}
- // 点击 Confirm 按钮
private void confirmAction() {
String selectedFilters = getSelectedFilters();
mainFrame.updateSelection(selectedFilters);
From 7cfe9ea4325ae78992ce0c06f09805592a90559c Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:54:56 -0500
Subject: [PATCH 030/138] Add constant in ViewModel
---
.../interface_adapter/recipe_search/RecipeSearchViewModel.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
index 0561a939b..58e77cbc1 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
@@ -11,6 +11,7 @@ public class RecipeSearchViewModel extends ViewModel {
public static final String ADD_INGREDIENT_BUTTON_LABEL = "Add Ingredient";
public static final String REMOVE_INGREDIENT_BUTTON_LABEL = "Remove Ingredient";
public static final String SEARCH_BUTTON_LABEL = "Search";
+ public static final String ADD_RESTRICTION_LABEL = "Add Restriction";
public RecipeSearchViewModel() {
// RecipeSearchViewModel is currently a subclass of ViewModel base on the implementation
From 1424bbf249030874d38f3f5d1202c08db5ffb06d Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Thu, 14 Nov 2024 21:56:56 -0500
Subject: [PATCH 031/138] Fix tinny bugs
---
src/main/java/view/FilterFrameView.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
index 172b39057..4696ee116 100644
--- a/src/main/java/view/FilterFrameView.java
+++ b/src/main/java/view/FilterFrameView.java
@@ -44,7 +44,7 @@ public FilterFrameView(RecipeSearchView mainFrame) {
panel.add(label, BorderLayout.NORTH);
JPanel checkBoxPanel = new JPanel();
- checkBoxPanel.setLayout(new GridLayout(3, 1, 10, 10)); // 3行布局
+ checkBoxPanel.setLayout(new GridLayout(3, 1, 10, 10));
dietPanel = createScrollableCheckBoxPanel("Diet Label", DIET_TYPES);
healthPanel = createScrollableCheckBoxPanel("Health Label", DIET_RESTRICTIONS);
From d3ad18bfde40e8d69f3055e3349b103e28b37235 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sat, 16 Nov 2024 04:00:44 -0500
Subject: [PATCH 032/138] Entities for User Story 2
---
src/main/java/entity/MealPlanEntry.java | 61 +++++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 src/main/java/entity/MealPlanEntry.java
diff --git a/src/main/java/entity/MealPlanEntry.java b/src/main/java/entity/MealPlanEntry.java
new file mode 100644
index 000000000..98b7620a7
--- /dev/null
+++ b/src/main/java/entity/MealPlanEntry.java
@@ -0,0 +1,61 @@
+package entity;
+
+import java.time.LocalDate;
+
+public class MealPlanEntry {
+ private int entryId;
+ private Recipe recipe;
+ private LocalDate date;
+ private int userId;
+ // e.g., "Breakfast", "Lunch", "Dinner"
+ private String mealType;
+
+ public MealPlanEntry(int entryId, Recipe recipe, LocalDate date, int userId, String mealType) {
+ this.entryId = entryId;
+ this.recipe = recipe;
+ this.date = date;
+ this.userId = userId;
+ this.mealType = mealType;
+ }
+
+ // Getters and setters
+ public int getEntryId() {
+ return entryId;
+ }
+
+ public void setEntryId(int entryId) {
+ this.entryId = entryId;
+ }
+
+ public Recipe getRecipe() {
+ return recipe;
+ }
+
+ public void setRecipe(Recipe recipe) {
+ this.recipe = recipe;
+ }
+
+ public LocalDate getDate() {
+ return date;
+ }
+
+ public void setDate(LocalDate date) {
+ this.date = date;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ public String getMealType() {
+ return mealType;
+ }
+
+ public void setMealType(String mealType) {
+ this.mealType = mealType;
+ }
+}
From 37aa49967d01c0bd05e9c54bf07ca1befeac6692 Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sat, 16 Nov 2024 20:32:58 -0500
Subject: [PATCH 033/138] Implementation of NutritionAnalysis use case,
Including NutritionAnalysis.java, NutritionAnalysisException.java and
NutritionAnalysisoutputBoundary.java.
---
.../nutrition_analysis/NutritionAnalysis.java | 15 +++++++++++++++
.../NutritionAnalysisException.java | 7 +++++++
.../NutritionAnalysisoutputBoundary.java | 19 +++++++++++++++++++
3 files changed, 41 insertions(+)
create mode 100644 src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
create mode 100644 src/main/java/use_case/nutrition_analysis/NutritionAnalysisException.java
create mode 100644 src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
new file mode 100644
index 000000000..50443e2ca
--- /dev/null
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
@@ -0,0 +1,15 @@
+package use_case.nutrition_analysis;
+
+/**
+ * Interface for the Nutrition Analysis use case.
+ */
+public interface NutritionAnalysis {
+
+ /**
+ * Searches for recipes based on a list of ingredients.
+ *
+ * @param RecipeName the title of recipe to analyze.
+ * @throws NutritionAnalysisException if search fails
+ */
+ void analyzeNutrition(String RecipeName) throws NutritionAnalysisException;
+}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisException.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisException.java
new file mode 100644
index 000000000..76cffdfe6
--- /dev/null
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisException.java
@@ -0,0 +1,7 @@
+package use_case.nutrition_analysis;
+
+public class NutritionAnalysisException extends RuntimeException {
+ public NutritionAnalysisException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
new file mode 100644
index 000000000..da27a07c4
--- /dev/null
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
@@ -0,0 +1,19 @@
+package use_case.nutrition_analysis;
+
+import entity.Nutrient;
+
+import java.util.List;
+
+public interface NutritionAnalysisoutputBoundary {
+ /**
+ * Present the Nutrition information to the user.
+ * @param NutritionInfo List of recipes to present
+ */
+ void presentNutritionInfo(List NutritionInfo);
+
+ /**
+ * Present an error message to the user.
+ * @param error Error message to present
+ */
+ void presentError(String error);
+}
\ No newline at end of file
From dee55540d300b918f6b40d3d3a841f57f97a2c31 Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sat, 16 Nov 2024 21:05:58 -0500
Subject: [PATCH 034/138] Implementation of NutritionAnalysis use case. The
NutritionAnalysisImpl.java.
---
.../NutritionAnalysisImpl.java | 35 +++++++++++++++++++
.../NutritionAnalysisoutputBoundary.java | 2 +-
2 files changed, 36 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
new file mode 100644
index 000000000..167ac2712
--- /dev/null
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
@@ -0,0 +1,35 @@
+package use_case.nutrition_analysis;
+
+import data_access.RecipeSearchEdamam;
+import use_case.recipe_search.RecipeSearchException;
+
+/**
+ * Implementation of the Nutrition Analysis use case.
+ */
+public class NutritionAnalysisImpl implements NutritionAnalysis {
+
+ private final RecipeSearchEdamam recipeSearchEdamam;
+ private final NutritionAnalysisoutputBoundary outputBoundary;
+
+ public NutritionAnalysisImpl(RecipeSearchEdamam recipeSearchEdamam,
+ NutritionAnalysisoutputBoundary outputBoundary) {
+ this.recipeSearchEdamam = recipeSearchEdamam;
+ this.outputBoundary = outputBoundary;
+ }
+
+ @Override
+ public void analyzeNutrition(String RecipeName) throws NutritionAnalysisException {
+ try {
+ // Get NutritionInfo from Edamam API by the RecipeName.
+ Nutrient NutritionInfo = recipeSearchEdamam.AnalyzeNutritionByRecipeName(RecipeName);
+
+ // Present success
+ outputBoundary.presentNutritionInfo(NutritionInfo);
+ }
+ catch (Exception e) {
+ // Present error
+ outputBoundary.presentError("Failed to analyze the recipe: " + e.getMessage());
+ throw new RecipeSearchException("Analysis failed", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
index da27a07c4..5ce585e4a 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
@@ -9,7 +9,7 @@ public interface NutritionAnalysisoutputBoundary {
* Present the Nutrition information to the user.
* @param NutritionInfo List of recipes to present
*/
- void presentNutritionInfo(List NutritionInfo);
+ void presentNutritionInfo(Nutrients NutritionInfo);
/**
* Present an error message to the user.
From 45ef0c619b6960073dbbcae2f79396d24b46c0a9 Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sat, 16 Nov 2024 21:14:10 -0500
Subject: [PATCH 035/138] Implementation of Nutrient.java entity and some
related changes.
---
src/main/java/entity/Nutrient.java | 22 +++++++++++++++++++
.../NutritionAnalysisImpl.java | 1 +
2 files changed, 23 insertions(+)
create mode 100644 src/main/java/entity/Nutrient.java
diff --git a/src/main/java/entity/Nutrient.java b/src/main/java/entity/Nutrient.java
new file mode 100644
index 000000000..aff54d746
--- /dev/null
+++ b/src/main/java/entity/Nutrient.java
@@ -0,0 +1,22 @@
+package entity;
+
+import java.util.List;
+
+/**
+ * The class represents the NutritionInfo for food, in form of the details of each Nutrient.
+ */
+public class Nutrient {
+ private List nutrients;
+
+ public Nutrient(List nutrients) {
+ this.nutrients = nutrients;
+ }
+
+ public List getNutrients() {
+ return nutrients;
+ }
+
+ public void setNutrients(List nutrients) {
+ this.nutrients = nutrients;
+ }
+}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
index 167ac2712..a1c9a231c 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
@@ -2,6 +2,7 @@
import data_access.RecipeSearchEdamam;
import use_case.recipe_search.RecipeSearchException;
+import entity.Nutrient;
/**
* Implementation of the Nutrition Analysis use case.
From 5c14b28d8149ae3d188429c5959296c78f835e16 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sat, 16 Nov 2024 21:44:12 -0500
Subject: [PATCH 036/138] files made for interface adapter of Meal Planning
---
.../meal_planning/MealPlanningController.java | 7 ++
.../meal_planning/MealPlanningPresenter.java | 8 ++
.../meal_planning/MealPlanningState.java | 4 +
.../meal_planning/MealPlanningViewModel.java | 15 ++++
src/main/java/view/MealPlanningView.java | 74 +++++++++++++++++++
5 files changed, 108 insertions(+)
create mode 100644 src/main/java/interface_adapter/meal_planning/MealPlanningController.java
create mode 100644 src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
create mode 100644 src/main/java/interface_adapter/meal_planning/MealPlanningState.java
create mode 100644 src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
create mode 100644 src/main/java/view/MealPlanningView.java
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningController.java b/src/main/java/interface_adapter/meal_planning/MealPlanningController.java
new file mode 100644
index 000000000..5654af374
--- /dev/null
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningController.java
@@ -0,0 +1,7 @@
+package interface_adapter.meal_planning;
+
+import use_case.meal_planning.MealPlanning;
+import java.time.LocalDate;
+
+public class MealPlanningController {
+}
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java b/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
new file mode 100644
index 000000000..a54d4e92e
--- /dev/null
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
@@ -0,0 +1,8 @@
+package interface_adapter.meal_planning;
+
+import entity.MealPlanEntry;
+import use_case.meal_planning.MealPlanningOutputBoundary;
+import java.util.List;
+
+public class MealPlanningPresenter implements MealPlanningOutputBoundary {
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningState.java b/src/main/java/interface_adapter/meal_planning/MealPlanningState.java
new file mode 100644
index 000000000..fc6e924d6
--- /dev/null
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningState.java
@@ -0,0 +1,4 @@
+package interface_adapter.meal_planning;
+
+public class MealPlanningState {
+}
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java b/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
new file mode 100644
index 000000000..93dfe92dd
--- /dev/null
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
@@ -0,0 +1,15 @@
+package interface_adapter.meal_planning;
+
+import interface_adapter.ViewModel;
+
+public class MealPlanningViewModel extends ViewModel {
+ public static final String TITLE_LABEL = "Meal Planning Calendar";
+ public static final String ADD_BUTTON_LABEL = "Add to Calendar";
+ public static final String REMOVE_BUTTON_LABEL = "Remove from Calendar";
+ public static final String NEXT_WEEK_BUTTON_LABEL = "Next Week";
+ public static final String PREV_WEEK_BUTTON_LABEL = "Previous Week";
+
+ public MealPlanningViewModel() {
+ super("Meal Planning Calendar");
+ }
+}
diff --git a/src/main/java/view/MealPlanningView.java b/src/main/java/view/MealPlanningView.java
new file mode 100644
index 000000000..4d95235e6
--- /dev/null
+++ b/src/main/java/view/MealPlanningView.java
@@ -0,0 +1,74 @@
+package view;
+
+import interface_adapter.meal_planning.*;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class MealPlanningView extends JPanel implements ActionListener, PropertyChangeListener {
+ private static final int CALENDAR_COLUMNS = 7;
+ private static final int VERTICAL_GAP = 10;
+ private static final int HORIZONTAL_GAP = 10;
+
+ private final MealPlanningViewModel viewModel;
+ private MealPlanningController controller;
+
+ private final JPanel calendarPanel;
+ private final JButton prevWeekButton;
+ private final JButton nextWeekButton;
+ private final JLabel weekLabel;
+
+ public MealPlanningView(MealPlanningViewModel viewModel) {
+ this.viewModel = viewModel;
+ this.viewModel.addPropertyChangeListener(this);
+
+ setLayout(new BorderLayout(HORIZONTAL_GAP, VERTICAL_GAP));
+
+ // Title panel
+ JLabel titleLabel = new JLabel(viewModel.TITLE_LABEL);
+ titleLabel.setFont(new Font(titleLabel.getFont().getName(), Font.BOLD, 16));
+ titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
+
+ // Navigation panel
+ JPanel navigationPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
+ prevWeekButton = new JButton(MealPlanningViewModel.PREV_WEEK_BUTTON_LABEL);
+ nextWeekButton = new JButton(MealPlanningViewModel.NEXT_WEEK_BUTTON_LABEL);
+ weekLabel = new JLabel();
+
+ navigationPanel.add(prevWeekButton);
+ navigationPanel.add(weekLabel);
+ navigationPanel.add(nextWeekButton);
+
+ // Calendar panel
+ calendarPanel = new JPanel(new GridLayout(0, CALENDAR_COLUMNS, 5, 5));
+
+ // Assembly
+ add(titleLabel, BorderLayout.NORTH);
+ add(navigationPanel, BorderLayout.CENTER);
+ add(new JScrollPane(calendarPanel), BorderLayout.SOUTH);
+
+ // Add listeners
+ prevWeekButton.addActionListener(this);
+ nextWeekButton.addActionListener(this);
+
+ // Initial update
+ updateWeekLabel();
+ refreshCalendar();
+ }
+
+ private void updateWeekLabel() {
+ MealPlanningState state = viewModel.getState();
+ if (state != null) {
+ LocalDate weekStart = state.getCurrentWeekStart();
+ LocalDate weekEnd = weekStart.plusDays(6);
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d, yyyy");
+ weekLabel.setText(String.format("%s - %s",
+ weekStart.format(formatter),
+ weekEnd.format(formatter)));
+ }
+ }
+}
From 7d10f2d43b65f98b0be84c8cafe0504935389e57 Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sat, 16 Nov 2024 22:30:20 -0500
Subject: [PATCH 037/138] Implementation of NutritionAnalysis interface,
including NutritionAnalysisController.java, NutritionAnalysisState.java and
NutritionAnalysisViewModel.java.
---
.../NutritionAnalysisController.java | 27 +++++++++++
.../NutritionAnalysisState.java | 47 +++++++++++++++++++
.../NutritionAnalysisViewModel.java | 16 +++++++
3 files changed, 90 insertions(+)
create mode 100644 src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisController.java
create mode 100644 src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
create mode 100644 src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisViewModel.java
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisController.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisController.java
new file mode 100644
index 000000000..22f73cde6
--- /dev/null
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisController.java
@@ -0,0 +1,27 @@
+package interface_adapter.nutrition_analysis;
+
+import use_case.nutrition_analysis.NutritionAnalysis;
+
+/**
+ * Controller for the Nutrition Analysis functionality.
+ */
+public class NutritionAnalysisController {
+ private final NutritionAnalysis nutritionAnalysisUseCase;
+
+ public NutritionAnalysisController(NutritionAnalysis nutritionAnalysisUsecase) {
+ this.nutritionAnalysisUseCase = nutritionAnalysisUsecase;
+ }
+
+ /**
+ * Execute a nutrition analysis with the given recipe name.
+ * @param RecipeName the name of the recipe the user wants to analyze.
+ */
+ public void executeAnalysis(String RecipeName) {
+ try {
+ nutritionAnalysisUseCase.analyzeNutrition(RecipeName);
+ }
+ catch (Exception e) {
+ // Error will be handled by the presenter through output boundary
+ }
+ }
+}
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
new file mode 100644
index 000000000..2228923bf
--- /dev/null
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
@@ -0,0 +1,47 @@
+package interface_adapter.nutrition_analysis;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the state for Nutrition Analysis functionality.
+ */
+public class NutritionAnalysisState {
+ private String recipeName;
+ private List nutritionResults = new ArrayList<>();
+ private String error;
+
+ /**
+ * Copy constructor for NutritionAnalysisState.
+ * @param copy The state to copy from
+ */
+ public NutritionAnalysisState(NutritionAnalysisState copy) {
+ this.recipeName = copy.getRecipeName();
+ this.nutritionResults = copy.getNutritionResults();
+ this.error = copy.getError();
+ }
+
+ public String getRecipeName() {
+ return recipeName;
+ }
+
+ public List getNutritionResults() {
+ return nutritionResults;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setNutritionResults(List nutritionResults) {
+ this.nutritionResults = nutritionResults;
+ }
+
+ public void setRecipeName(String recipeName) {
+ this.recipeName = recipeName;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+}
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisViewModel.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisViewModel.java
new file mode 100644
index 000000000..692626f55
--- /dev/null
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisViewModel.java
@@ -0,0 +1,16 @@
+package interface_adapter.nutrition_analysis;
+
+import interface_adapter.ViewModel;
+
+/**
+ * The ViewModel for the NutritionAnalysis View.
+ */
+public class NutritionAnalysisViewModel extends ViewModel {
+ // Attributes are created base on the components in RecipeSearchView class
+ private static final String TITLE_LABEL = "Nutrition Analysis";
+ private static final String REMOVE_INGREDIENT_BUTTON_LABEL = "Remove All";
+
+ public NutritionAnalysisViewModel(String viewName) {
+ super(viewName);
+ }
+}
From 0748bbc76860dc77cd321b06be8da1635f213f15 Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sun, 17 Nov 2024 16:58:53 -0500
Subject: [PATCH 038/138] Implementation of NutritionAnalysis interface. The
NutritionAnalysisPresenter.java, and some minor changes to other files
---
src/main/java/entity/Nutrient.java | 1 +
.../NutritionAnalysisPresenter.java | 33 +++++++++++++++++++
.../NutritionAnalysisState.java | 7 ++++
.../NutritionAnalysisImpl.java | 4 +--
...a => NutritionAnalysisOutputBoundary.java} | 9 ++---
5 files changed, 48 insertions(+), 6 deletions(-)
create mode 100644 src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
rename src/main/java/use_case/nutrition_analysis/{NutritionAnalysisoutputBoundary.java => NutritionAnalysisOutputBoundary.java} (66%)
diff --git a/src/main/java/entity/Nutrient.java b/src/main/java/entity/Nutrient.java
index aff54d746..8e09a742b 100644
--- a/src/main/java/entity/Nutrient.java
+++ b/src/main/java/entity/Nutrient.java
@@ -6,6 +6,7 @@
* The class represents the NutritionInfo for food, in form of the details of each Nutrient.
*/
public class Nutrient {
+
private List nutrients;
public Nutrient(List nutrients) {
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
new file mode 100644
index 000000000..a9fa01ab5
--- /dev/null
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
@@ -0,0 +1,33 @@
+package interface_adapter.nutrition_analysis;
+
+import entity.Nutrient;
+import use_case.nutrition_analysis.NutritionAnalysisOutputBoundary;
+
+/**
+ * Presenter for the Nutrition Analysis functionality.
+ */
+public class NutritionAnalysisPresenter implements NutritionAnalysisOutputBoundary {
+ private final NutritionAnalysisViewModel viewModel;
+
+ public NutritionAnalysisPresenter(NutritionAnalysisViewModel viewModel) {
+ this.viewModel = viewModel;
+ }
+
+ @Override
+ public void presentNutritionInfo(Nutrient NutritionInfo) {
+ NutritionAnalysisState state = new NutritionAnalysisState();
+
+ state.setNutritionResults(NutritionInfo.getNutrients());
+ viewModel.setState(state);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentError(String error) {
+ final NutritionAnalysisState state = new NutritionAnalysisState();
+ state.setError(error);
+ viewModel.setState(state);
+ viewModel.firePropertyChanged();
+
+ }
+}
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
index 2228923bf..60acef7f5 100644
--- a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisState.java
@@ -21,6 +21,13 @@ public NutritionAnalysisState(NutritionAnalysisState copy) {
this.error = copy.getError();
}
+ /**
+ * Default constructor for NutritionAnalysisState.
+ */
+ public NutritionAnalysisState() {
+
+ }
+
public String getRecipeName() {
return recipeName;
}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
index a1c9a231c..d47e2689d 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
@@ -10,10 +10,10 @@
public class NutritionAnalysisImpl implements NutritionAnalysis {
private final RecipeSearchEdamam recipeSearchEdamam;
- private final NutritionAnalysisoutputBoundary outputBoundary;
+ private final NutritionAnalysisOutputBoundary outputBoundary;
public NutritionAnalysisImpl(RecipeSearchEdamam recipeSearchEdamam,
- NutritionAnalysisoutputBoundary outputBoundary) {
+ NutritionAnalysisOutputBoundary outputBoundary) {
this.recipeSearchEdamam = recipeSearchEdamam;
this.outputBoundary = outputBoundary;
}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
similarity index 66%
rename from src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
rename to src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
index 5ce585e4a..ca2562c8c 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisoutputBoundary.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
@@ -2,14 +2,15 @@
import entity.Nutrient;
-import java.util.List;
-
-public interface NutritionAnalysisoutputBoundary {
+/**
+ * Output boundary for the Nutrition Analysis use case.
+ */
+public interface NutritionAnalysisOutputBoundary {
/**
* Present the Nutrition information to the user.
* @param NutritionInfo List of recipes to present
*/
- void presentNutritionInfo(Nutrients NutritionInfo);
+ void presentNutritionInfo(Nutrient NutritionInfo);
/**
* Present an error message to the user.
From b13b401b798d339e724b9cbeeda516002a60ecaa Mon Sep 17 00:00:00 2001
From: kaibaek
Date: Sun, 17 Nov 2024 17:21:58 -0500
Subject: [PATCH 039/138] Modified view models code to receive servings from
users
---
.../recipe_search/RecipeSearchController.java | 8 +--
.../recipe_search/ServingsViewModel.java | 45 ++++++++++++++++
src/main/java/view/RecipeSearchView.java | 51 ++++++++++---------
3 files changed, 77 insertions(+), 27 deletions(-)
create mode 100644 src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index a65be3d50..7ede9710b 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -18,9 +18,9 @@ public RecipeSearchController(RecipeSearch recipeSearchUseCase) {
* Execute a recipe search with the given ingredients.
* @param ingredients List of ingredients to search for
*/
- public void executeSearch(List ingredients) {
+ public void executeSearch(List ingredients, int servings) {
try {
- recipeSearchUseCase.searchRecipes(ingredients);
+ recipeSearchUseCase.searchRecipes(ingredients, servings);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
@@ -31,9 +31,9 @@ public void executeSearch(List ingredients) {
* Execute a recipe search with the given restrictions.
* @param ingredients Map of ingredients to search for
*/
- public void executeRestrictionSearch(Map> ingredients) {
+ public void executeRestrictionSearch(Map> ingredients, int servings) {
try {
- recipeSearchUseCase.searchRestrictionRecipes(ingredients);
+ recipeSearchUseCase.searchRestrictionRecipes(ingredients, servings);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
diff --git a/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java b/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
new file mode 100644
index 000000000..2897b36f2
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
@@ -0,0 +1,45 @@
+package interface_adapter.recipe_search;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * A ViewModel for managing servings.
+ */
+public class ServingsViewModel {
+ private int servings = 1;
+ private final List availableServings = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+ private final PropertyChangeSupport pcs;
+
+ public ServingsViewModel() {
+ this.pcs = new PropertyChangeSupport(this);
+ }
+
+ // Getter for current servings
+ public int getServings() {
+ return servings;
+ }
+
+ // Setter for servings with PropertyChangeSupport
+ public void setServings(int newServings) {
+ int oldServings = this.servings;
+ this.servings = newServings;
+ pcs.firePropertyChange("servings", oldServings, newServings);
+ }
+
+ // Getter for available servings
+ public List getAvailableServings() {
+ return availableServings;
+ }
+
+ // Methods to add and remove property change listeners
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(listener);
+ }
+}
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index 46f0deb41..85027df86 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -3,6 +3,7 @@
import interface_adapter.recipe_search.RecipeSearchController;
import interface_adapter.recipe_search.RecipeSearchState;
import interface_adapter.recipe_search.RecipeSearchViewModel;
+import interface_adapter.recipe_search.ServingsViewModel;
import java.awt.BorderLayout;
import java.awt.Component;
@@ -17,16 +18,7 @@
import java.util.List;
import java.util.Map;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.DefaultListModel;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextField;
+import javax.swing.*;
/**
* A view for searching recipes by ingredients.
@@ -52,6 +44,7 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private final JPanel resultsPanel;
private final DefaultListModel recipeListModel;
private final JList recipeResults;
+ private final JComboBox servingsDropdown;
private final List ingredients;
private final JPanel restrictionPanel;
private final JButton addRestrictionButton;
@@ -61,15 +54,20 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private Map> restrictionMap;
private final RecipeSearchViewModel recipeSearchViewModel;
+ private final ServingsViewModel servingsViewModel;
private RecipeSearchController recipeSearchController;
/**
* Constructs a new RecipeSearchView.
*
* @param viewModel The view model for recipe search
+ * @param servingsViewModel The view model for servings
*/
- public RecipeSearchView(RecipeSearchViewModel viewModel) {
+ public RecipeSearchView(RecipeSearchViewModel viewModel, ServingsViewModel servingsViewModel) {
this.recipeSearchViewModel = viewModel;
+ this.servingsViewModel = servingsViewModel;
+
+ this.servingsViewModel.addPropertyChangeListener(this);
this.recipeSearchViewModel.addPropertyChangeListener(this);
// Initialize components
@@ -103,6 +101,10 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
restrictions = new ArrayList<>();
restrictionMap = new HashMap<>();
+ servingsDropdown = new JComboBox<>(servingsViewModel.getAvailableServings().toArray(new Integer[0]));
+ servingsDropdown.setSelectedItem(servingsViewModel.getServings());
+ servingsDropdown.addActionListener(this);
+
// Set up the main layout
this.setLayout(new BorderLayout());
@@ -150,7 +152,11 @@ public void setRecipeSearchController(RecipeSearchController controller) {
@Override
public void actionPerformed(ActionEvent evt) {
- if (evt.getSource().equals(addIngredientButton)) {
+ if (evt.getSource().equals(servingsDropdown)) {
+ int selectedServings = (int) servingsDropdown.getSelectedItem();
+ servingsViewModel.setServings(selectedServings);
+ }
+ else if (evt.getSource().equals(addIngredientButton)) {
final String ingredient = ingredientField.getText().trim();
if (!ingredient.isEmpty()) {
ingredients.add(ingredient);
@@ -176,9 +182,9 @@ public void actionPerformed(ActionEvent evt) {
} else if (evt.getSource().equals(searchButton)) {
if (recipeSearchController != null) {
if (restrictions.isEmpty()) {
- recipeSearchController.executeSearch(new ArrayList<>(ingredients));
+ recipeSearchController.executeSearch(new ArrayList<>(ingredients), servingsViewModel.getServings());
} else {
- recipeSearchController.executeRestrictionSearch(restrictionMap);
+ recipeSearchController.executeRestrictionSearch(restrictionMap, servingsViewModel.getServings());
}
}
}
@@ -186,6 +192,9 @@ public void actionPerformed(ActionEvent evt) {
@Override
public void propertyChange(PropertyChangeEvent evt) {
+ if ("servings".equals(evt.getPropertyName())) {
+ servingsDropdown.setSelectedItem(evt.getNewValue());
+ }
RecipeSearchState state = (RecipeSearchState) evt.getNewValue();
if (state != null) {
if (state.getError() != null) {
@@ -196,10 +205,6 @@ public void propertyChange(PropertyChangeEvent evt) {
}
}
- public void updateSelection(String selectionText) {
- restrictionListModel.addElement(selectionText);
- }
-
private void setupInputPanel() {
inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
@@ -209,11 +214,11 @@ private void setupInputPanel() {
addIngredientPanel.add(addIngredientButton);
addIngredientPanel.add(addRestrictionButton);
- final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
- ingredientScrollPane.setPreferredSize(
- new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
+ final JPanel servingsPanel = new JPanel();
+ servingsPanel.add(new JLabel("Select Servings:"));
+ servingsPanel.add(servingsDropdown);
- final JScrollPane restrictionScrollPane = new JScrollPane(restrictionList);
+ final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
ingredientScrollPane.setPreferredSize(
new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
@@ -221,9 +226,9 @@ private void setupInputPanel() {
removeButtonPanel.add(removeIngredientButton);
inputPanel.add(addIngredientPanel);
+ inputPanel.add(servingsPanel);
inputPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
inputPanel.add(ingredientScrollPane);
- inputPanel.add(restrictionScrollPane);
inputPanel.add(removeButtonPanel);
}
From 321a9d244305647a05c4facef5790d85fb7a1c25 Mon Sep 17 00:00:00 2001
From: Alan-Na
Date: Sun, 17 Nov 2024 18:08:07 -0500
Subject: [PATCH 040/138] Fix bugs in restrictions searching
---
.../java/data_access/RecipeSearchEdamam.java | 20 ++++---
.../recipe_search/RecipeSearchImpl.java | 6 +--
src/main/java/view/FilterFrameView.java | 52 +++++++++++++++----
src/main/java/view/RecipeSearchView.java | 5 +-
4 files changed, 61 insertions(+), 22 deletions(-)
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index 899d1155e..41faa08b7 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -68,7 +68,7 @@ public List searchRecipesByFoodName(String foodName) {
/**
* Get recipes by selecting restrictions.
*/
- public List searchRecipesByRestriction(String foodName, String diet, String health, String cuisineType) {
+ public List searchRecipesByRestriction(String foodName, String[] diet, String[] health, String[] cuisineType) {
List recipes = new ArrayList<>();
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
@@ -78,14 +78,20 @@ public List searchRecipesByRestriction(String foodName, String
urlBuilder.addQueryParameter("app_key", APP_KEY);
// Add optional filters if provided
- if (diet != null && !diet.isEmpty()) {
- urlBuilder.addQueryParameter("diet", diet);
+ if (diet != null) {
+ for (String d : diet) {
+ urlBuilder.addQueryParameter("diet", d);
+ }
}
- if (health != null && !health.isEmpty()) {
- urlBuilder.addQueryParameter("health", health);
+ if (health != null) {
+ for (String h : health) {
+ urlBuilder.addQueryParameter("health", h);
+ }
}
- if (cuisineType != null && !cuisineType.isEmpty()) {
- urlBuilder.addQueryParameter("cuisineType", cuisineType);
+ if (cuisineType != null && cuisineType.length != 0) {
+ for (String c : cuisineType) {
+ urlBuilder.addQueryParameter("cuisineType", c);
+ }
}
Request request = new Request.Builder()
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
index 939d9c94a..57f7bf00c 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
@@ -49,9 +49,9 @@ public void searchRestrictionRecipes(Map> restrictions) thr
try {
// Join ingredients with commas for the API search
String searchFoodQuery = String.join(",", restrictions.get("Food Name"));
- String searchDietQuery = String.join(",", restrictions.get("Diet Label"));
- String searchHealthQuery = String.join(",", restrictions.get("Health Label"));
- String searchCuisineQuery = String.join(",", restrictions.get("Cuisine Type"));
+ String[] searchDietQuery = restrictions.get("Diet Label").toArray(new String[0]);
+ String[] searchHealthQuery = restrictions.get("Health Label").toArray(new String[0]);
+ String[] searchCuisineQuery = restrictions.get("Cuisine Label").toArray(new String[0]);
// Get recipes from Edamam API
List searchResults = recipeSearchEdamam.searchRecipesByRestriction(searchFoodQuery, searchDietQuery, searchHealthQuery, searchCuisineQuery);
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
index 4696ee116..92835bb1a 100644
--- a/src/main/java/view/FilterFrameView.java
+++ b/src/main/java/view/FilterFrameView.java
@@ -146,31 +146,61 @@ public String getSelectedFilters() {
return resultText.toString();
}
- public Map> getSelectedFiltersMap() {
- final Map> filtersMap = new HashMap<>();
-
- // Diet Label
+// public Map> getSelectedFiltersMap() {
+// final Map> filtersMap = new HashMap<>();
+//
+// // Diet Label
+// for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
+// if (checkBox.isSelected()) {
+// addValueToKey(filtersMap, "Diet Types", checkBox.getText());
+// }
+// }
+//
+// // Health Label
+// for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
+// if (checkBox.isSelected()) {
+// addValueToKey(filtersMap, "Health Types", checkBox.getText());
+// }
+// }
+//
+// // Cuisine Type
+// for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
+// if (checkBox.isSelected()) {
+// addValueToKey(filtersMap, "Cuisine Types", checkBox.getText());
+// }
+// }
+//
+// return filtersMap;
+// }
+
+ public List getDietType() {
+ ArrayList dietList = new ArrayList<>();
for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
if (checkBox.isSelected()) {
- addValueToKey(filtersMap, "Diet Types", checkBox.getText());
+ dietList.add(checkBox.getText());
}
}
+ return dietList;
+ }
- // Health Label
+ public List getHealthType() {
+ ArrayList healthList = new ArrayList<>();
for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
if (checkBox.isSelected()) {
- addValueToKey(filtersMap, "Health Labels", checkBox.getText());
+ healthList.add(checkBox.getText());
}
}
+ return healthList;
+ }
- // Cuisine Type
+ public List getCuisineType() {
+ ArrayList cuisineList = new ArrayList<>();
for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
if (checkBox.isSelected()) {
- addValueToKey(filtersMap, "Cuisine Types", checkBox.getText());
+ cuisineList.add(checkBox.getText());
}
}
-
- return filtersMap;
+ return cuisineList;
}
private static void addValueToKey(Map> map, String key, String value) {
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index 46f0deb41..5def0381b 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -161,7 +161,10 @@ public void actionPerformed(ActionEvent evt) {
} else if (evt.getSource().equals(addRestrictionButton)) {
FilterFrameView filterFrame = new FilterFrameView(this);
final String restriction = filterFrame.getSelectedFilters().trim();
- restrictionMap = filterFrame.getSelectedFiltersMap();
+// restrictionMap = filterFrame.getSelectedFiltersMap();
+ restrictionMap.put("Diet Types", filterFrame.getDietType());
+ restrictionMap.put("Health Types", filterFrame.getHealthType());
+ restrictionMap.put("Cuisine Types", filterFrame.getCuisineType());
if (!restrictions.isEmpty()) {
restrictions.add(restriction);
From 89c7a7546cea57541c1302eb1579612fac90dbb7 Mon Sep 17 00:00:00 2001
From: Haris Anjum
Date: Sun, 17 Nov 2024 18:36:25 -0500
Subject: [PATCH 041/138] Domain Layer for the Meal Planner
---
.../data_access/MealPlanningDataAccess.java | 14 ++++
.../MealPlanningDataAccessObject.java | 67 +++++++++++++++++++
.../data_access/SavedRecipesDataAccess.java | 11 +++
.../SavedRecipesDataAccessObject.java | 55 +++++++++++++++
src/main/java/entity/MealPlanEntry.java | 12 +++-
5 files changed, 158 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/data_access/MealPlanningDataAccess.java
create mode 100644 src/main/java/data_access/MealPlanningDataAccessObject.java
create mode 100644 src/main/java/data_access/SavedRecipesDataAccess.java
create mode 100644 src/main/java/data_access/SavedRecipesDataAccessObject.java
diff --git a/src/main/java/data_access/MealPlanningDataAccess.java b/src/main/java/data_access/MealPlanningDataAccess.java
new file mode 100644
index 000000000..14c7733d3
--- /dev/null
+++ b/src/main/java/data_access/MealPlanningDataAccess.java
@@ -0,0 +1,14 @@
+package data_access;
+
+import entity.MealPlanEntry;
+import java.time.LocalDate;
+import java.util.List;
+
+public interface MealPlanningDataAccess {
+ void addMealPlanEntry(int userId, int recipeId, LocalDate date, String mealType);
+ void removeMealPlanEntry(int userId, int mealPlanEntryId);
+ void updateMealStatus(int userId, int entryId, String status);
+
+ MealPlanEntry getMealPlanEntry(int userId, int entryId);
+ List getWeeklyPlan(int userId, LocalDate weekStart);
+}
diff --git a/src/main/java/data_access/MealPlanningDataAccessObject.java b/src/main/java/data_access/MealPlanningDataAccessObject.java
new file mode 100644
index 000000000..ac31610cd
--- /dev/null
+++ b/src/main/java/data_access/MealPlanningDataAccessObject.java
@@ -0,0 +1,67 @@
+package data_access;
+
+import entity.MealPlanEntry;
+import entity.Recipe;
+import java.time.LocalDate;
+import java.util.*;
+
+public class MealPlanningDataAccessObject implements MealPlanningDataAccess {
+
+ private final Map mealPlanEntries = new HashMap<>();
+ private final SavedRecipesDataAccess savedRecipesDataAccess;
+ private int nextEntryId = 1;
+
+ public MealPlanningDataAccessObject(SavedRecipesDataAccess savedRecipesDataAccess) {
+ this.savedRecipesDataAccess = savedRecipesDataAccess;
+ }
+
+ @Override
+ public MealPlanEntry getMealPlanEntry(int userId, int entryId) {
+ MealPlanEntry entry = mealPlanEntries.get(entryId);
+ if (entry != null && entry.getUserId() == userId) {
+ return entry;
+ }
+ return null;
+ }
+
+ @Override
+ public void updateMealStatus(int userId, int entryId, String status) {
+ MealPlanEntry entry = mealPlanEntries.get(entryId);
+ if (entry != null && entry.getUserId() == userId) {
+ entry.setStatus(status);
+ }
+ else {
+ throw new IllegalArgumentException("Entry not found or unauthorized access");
+ }
+ }
+
+ @Override
+ public void addMealPlanEntry(int userId, int recipeId, LocalDate date, String mealType) {
+ Recipe recipe = savedRecipesDataAccess.getSavedRecipe(userId, recipeId);
+ if (recipe == null) {
+ throw new IllegalArgumentException("Recipe not found in saved recipes");
+ }
+
+ MealPlanEntry entry = new MealPlanEntry(nextEntryId++, recipe, date, userId, mealType);
+ mealPlanEntries.put(entry.getEntryId(), entry);
+ }
+
+ @Override
+ public void removeMealPlanEntry(int userId, int mealPlanEntryId) {
+ MealPlanEntry entry = mealPlanEntries.get(mealPlanEntryId);
+ if (entry != null && entry.getUserId() == userId) {
+ mealPlanEntries.remove(mealPlanEntryId);
+ }
+ }
+
+ @Override
+ public List getWeeklyPlan(int userId, LocalDate weekStart) {
+ return mealPlanEntries.values().stream()
+ .filter(entry -> entry.getUserId() == userId)
+ .filter(entry -> {
+ LocalDate date = entry.getDate();
+ return !date.isBefore(weekStart) && date.isBefore(weekStart.plusDays(7));
+ })
+ .toList();
+ }
+}
diff --git a/src/main/java/data_access/SavedRecipesDataAccess.java b/src/main/java/data_access/SavedRecipesDataAccess.java
new file mode 100644
index 000000000..ed28ecf87
--- /dev/null
+++ b/src/main/java/data_access/SavedRecipesDataAccess.java
@@ -0,0 +1,11 @@
+package data_access;
+
+import entity.Recipe;
+import java.util.*;
+
+public interface SavedRecipesDataAccess {
+ void saveRecipe(int userId, Recipe recipe);
+ void removeRecipe(int userId, int recipeId);
+ List getSavedRecipes(int userId);
+ Recipe getSavedRecipe(int userId, int recipeId);
+}
diff --git a/src/main/java/data_access/SavedRecipesDataAccessObject.java b/src/main/java/data_access/SavedRecipesDataAccessObject.java
new file mode 100644
index 000000000..4a5e41a6f
--- /dev/null
+++ b/src/main/java/data_access/SavedRecipesDataAccessObject.java
@@ -0,0 +1,55 @@
+package data_access;
+
+import entity.Recipe;
+import java.util.*;
+
+public class SavedRecipesDataAccessObject implements SavedRecipesDataAccess {
+ private final Map> userRecipes = new HashMap<>();
+
+ public SavedRecipesDataAccessObject() {
+ // Empty constructor
+ }
+
+ @Override
+ public void saveRecipe(int userId, Recipe recipe) {
+ if (recipe == null) {
+ throw new IllegalArgumentException("Recipe cannot be null");
+ }
+ userRecipes.computeIfAbsent(userId, k -> new HashMap<>())
+ .put(recipe.getRecipeId(), recipe);
+ }
+
+ @Override
+ public void removeRecipe(int userId, int recipeId) {
+ Map userRecipeMap = userRecipes.get(userId);
+ if (userRecipeMap != null) {
+ Recipe removedRecipe = userRecipeMap.remove(recipeId);
+ if (userRecipeMap.isEmpty()) {
+ userRecipes.remove(userId);
+ }
+ if (removedRecipe == null) {
+ throw new IllegalArgumentException("Recipe not found for user");
+ }
+ } else {
+ throw new IllegalArgumentException("No recipes found for user");
+ }
+ }
+
+ @Override
+ public List getSavedRecipes(int userId) {
+ return new ArrayList<>(userRecipes.getOrDefault(userId, new HashMap<>()).values());
+ }
+
+ @Override
+ public Recipe getSavedRecipe(int userId, int recipeId) {
+ Map userRecipeMap = userRecipes.get(userId);
+ if (userRecipeMap == null) {
+ return null;
+ }
+ Recipe recipe = userRecipeMap.get(recipeId);
+ if (recipe == null) {
+ throw new IllegalArgumentException("Recipe not found");
+ }
+ return recipe;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/entity/MealPlanEntry.java b/src/main/java/entity/MealPlanEntry.java
index 98b7620a7..78caad798 100644
--- a/src/main/java/entity/MealPlanEntry.java
+++ b/src/main/java/entity/MealPlanEntry.java
@@ -7,8 +7,8 @@ public class MealPlanEntry {
private Recipe recipe;
private LocalDate date;
private int userId;
- // e.g., "Breakfast", "Lunch", "Dinner"
private String mealType;
+ private String status; // Added to track state like "planned", "completed", etc.
public MealPlanEntry(int entryId, Recipe recipe, LocalDate date, int userId, String mealType) {
this.entryId = entryId;
@@ -16,9 +16,19 @@ public MealPlanEntry(int entryId, Recipe recipe, LocalDate date, int userId, Str
this.date = date;
this.userId = userId;
this.mealType = mealType;
+ this.status = "planned"; // Default status
}
// Getters and setters
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
public int getEntryId() {
return entryId;
}
From 8f64f6a0666f74dbf0f5b8beb17077c7aaa8284e Mon Sep 17 00:00:00 2001
From: Jerry Zeng
Date: Sun, 17 Nov 2024 18:58:32 -0500
Subject: [PATCH 042/138] Change in RecipeSearchEdamam.java, including the
implementation of analyzeNutrition method that gets info from API. There are
Some relative changes to interface and use case.
---
.../java/data_access/RecipeSearchEdamam.java | 60 +++++++++++++++++--
src/main/java/entity/Nutrient.java | 16 +++--
.../NutritionAnalysisPresenter.java | 14 ++++-
.../nutrition_analysis/NutritionAnalysis.java | 5 +-
.../NutritionAnalysisImpl.java | 8 ++-
.../NutritionAnalysisOutputBoundary.java | 3 +-
6 files changed, 86 insertions(+), 20 deletions(-)
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index 899d1155e..0b85bd3ee 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -1,14 +1,13 @@
package data_access;
+import entity.Nutrient;
import entity.RecipeForSearch;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
+
+import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -16,6 +15,7 @@ public class RecipeSearchEdamam {
private static final String APP_ID = "35f28703";
private static final String APP_KEY = "acb2a3e8e5cd69c1e0bcacefd85ea880";
private static final String BASE_URL = "https://api.edamam.com/api/recipes/v2";
+ private static final String NA_URL = "https://api.edamam.com/api/nutrition-details";
private final OkHttpClient httpClient = new OkHttpClient();
/**
@@ -121,4 +121,56 @@ public List searchRecipesByRestriction(String foodName, String
}
return recipes;
}
+
+ /**
+ * Get totalNutrients from a POST request from the API.
+ * @param recipeName The recipe name.
+ * @param ingredients A list of ingredients.
+ * @return A Nutrient entity
+ * @throws IOException If an I/O error occurs.
+ */
+ public List analyzeNutrition(String recipeName, List ingredients) throws IOException {
+ List nutrientsList = new ArrayList<>();
+
+ // Build the URL using HttpUrl.Builder
+ HttpUrl urlBuilder = HttpUrl.parse(NA_URL).newBuilder().addQueryParameter("app_id", APP_ID).addQueryParameter("app_key", APP_KEY).build();
+
+ // Build the request body.
+ JSONObject requestBody = new JSONObject();
+ requestBody.put("title", recipeName);
+ requestBody.put("ingr", ingredients);
+
+ Request request = new Request.Builder()
+ .url(urlBuilder)
+ .post(RequestBody.create(
+ requestBody.toString(),
+ MediaType.get("application/json; charset=utf-8")
+ ))
+ .build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (response.body() != null) {
+ String jsonData = response.body().string();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ JSONObject totalNutrients = jsonObject.getJSONObject("totalNutrients");
+
+ // Iterate over each nutrient in the JSON object totalNutrients.
+ for (String key : totalNutrients.keySet()) {
+ // Extract
+ JSONObject nutrient = totalNutrients.getJSONObject(key);
+
+ // Combine the values into a string and add them to the list
+ String aNutrient = "Label: " + nutrient.getString("label")
+ + ", Quantity: " + nutrient.getInt("quantity")
+ + ", Unit: " + nutrient.getString("unit");
+
+ nutrientsList.add(new Nutrient(aNutrient));
+ }
+ }
+ }
+ catch (IOException e) {
+ throw new IOException(e);
+ }
+ return nutrientsList;
+ }
}
diff --git a/src/main/java/entity/Nutrient.java b/src/main/java/entity/Nutrient.java
index 8e09a742b..203e0b37b 100644
--- a/src/main/java/entity/Nutrient.java
+++ b/src/main/java/entity/Nutrient.java
@@ -1,23 +1,21 @@
package entity;
-import java.util.List;
-
/**
* The class represents the NutritionInfo for food, in form of the details of each Nutrient.
*/
public class Nutrient {
- private List nutrients;
+ private String nutrientInfo;
- public Nutrient(List nutrients) {
- this.nutrients = nutrients;
+ public Nutrient(String nutrients) {
+ this.nutrientInfo = nutrients;
}
- public List getNutrients() {
- return nutrients;
+ public String getNutrients() {
+ return nutrientInfo;
}
- public void setNutrients(List nutrients) {
- this.nutrients = nutrients;
+ public void setNutrients(String nutrients) {
+ this.nutrientInfo = nutrients;
}
}
diff --git a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
index a9fa01ab5..a54160f56 100644
--- a/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
+++ b/src/main/java/interface_adapter/nutrition_analysis/NutritionAnalysisPresenter.java
@@ -1,8 +1,12 @@
package interface_adapter.nutrition_analysis;
import entity.Nutrient;
+import use_case.nutrition_analysis.NutritionAnalysis;
import use_case.nutrition_analysis.NutritionAnalysisOutputBoundary;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Presenter for the Nutrition Analysis functionality.
*/
@@ -14,10 +18,16 @@ public NutritionAnalysisPresenter(NutritionAnalysisViewModel viewModel) {
}
@Override
- public void presentNutritionInfo(Nutrient NutritionInfo) {
+ public void presentNutritionInfo(List NutritionInfo) {
NutritionAnalysisState state = new NutritionAnalysisState();
+ List nutritionResults = new ArrayList<>();
- state.setNutritionResults(NutritionInfo.getNutrients());
+ for (Nutrient nutrient : NutritionInfo) {
+ StringBuilder description = new StringBuilder();
+ description.append(nutrient.getNutrients()).append("\n");
+ nutritionResults.add(description.toString());
+ }
+ state.setNutritionResults(nutritionResults);
viewModel.setState(state);
viewModel.firePropertyChanged();
}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
index 50443e2ca..b10d3428b 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysis.java
@@ -1,5 +1,7 @@
package use_case.nutrition_analysis;
+import java.util.List;
+
/**
* Interface for the Nutrition Analysis use case.
*/
@@ -9,7 +11,8 @@ public interface NutritionAnalysis {
* Searches for recipes based on a list of ingredients.
*
* @param RecipeName the title of recipe to analyze.
+ * @param ingredients the list of ingredients of this recipe.
* @throws NutritionAnalysisException if search fails
*/
- void analyzeNutrition(String RecipeName) throws NutritionAnalysisException;
+ void analyzeNutrition(String RecipeName, List ingredients) throws NutritionAnalysisException;
}
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
index d47e2689d..201f9008d 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisImpl.java
@@ -4,6 +4,8 @@
import use_case.recipe_search.RecipeSearchException;
import entity.Nutrient;
+import java.util.List;
+
/**
* Implementation of the Nutrition Analysis use case.
*/
@@ -19,13 +21,13 @@ public NutritionAnalysisImpl(RecipeSearchEdamam recipeSearchEdamam,
}
@Override
- public void analyzeNutrition(String RecipeName) throws NutritionAnalysisException {
+ public void analyzeNutrition(String RecipeName, List ingredients) throws NutritionAnalysisException {
try {
// Get NutritionInfo from Edamam API by the RecipeName.
- Nutrient NutritionInfo = recipeSearchEdamam.AnalyzeNutritionByRecipeName(RecipeName);
+ List nutritionInfo = recipeSearchEdamam.analyzeNutrition(RecipeName, ingredients);
// Present success
- outputBoundary.presentNutritionInfo(NutritionInfo);
+ outputBoundary.presentNutritionInfo(nutritionInfo);
}
catch (Exception e) {
// Present error
diff --git a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
index ca2562c8c..a246fac10 100644
--- a/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
+++ b/src/main/java/use_case/nutrition_analysis/NutritionAnalysisOutputBoundary.java
@@ -1,6 +1,7 @@
package use_case.nutrition_analysis;
import entity.Nutrient;
+import java.util.List;
/**
* Output boundary for the Nutrition Analysis use case.
@@ -10,7 +11,7 @@ public interface NutritionAnalysisOutputBoundary {
* Present the Nutrition information to the user.
* @param NutritionInfo List of recipes to present
*/
- void presentNutritionInfo(Nutrient NutritionInfo);
+ void presentNutritionInfo(List NutritionInfo);
/**
* Present an error message to the user.
From 26c2e133421a0fd1c33937f73ba003b7e10a8785 Mon Sep 17 00:00:00 2001
From: kaibaek
Date: Sun, 17 Nov 2024 17:21:58 -0500
Subject: [PATCH 043/138] Modified view models code to receive servings from
users
---
.../recipe_search/RecipeSearchController.java | 8 +--
.../recipe_search/ServingsViewModel.java | 45 ++++++++++++++++
src/main/java/view/RecipeSearchView.java | 51 ++++++++++---------
3 files changed, 77 insertions(+), 27 deletions(-)
create mode 100644 src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index a65be3d50..7ede9710b 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -18,9 +18,9 @@ public RecipeSearchController(RecipeSearch recipeSearchUseCase) {
* Execute a recipe search with the given ingredients.
* @param ingredients List of ingredients to search for
*/
- public void executeSearch(List ingredients) {
+ public void executeSearch(List ingredients, int servings) {
try {
- recipeSearchUseCase.searchRecipes(ingredients);
+ recipeSearchUseCase.searchRecipes(ingredients, servings);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
@@ -31,9 +31,9 @@ public void executeSearch(List ingredients) {
* Execute a recipe search with the given restrictions.
* @param ingredients Map of ingredients to search for
*/
- public void executeRestrictionSearch(Map> ingredients) {
+ public void executeRestrictionSearch(Map> ingredients, int servings) {
try {
- recipeSearchUseCase.searchRestrictionRecipes(ingredients);
+ recipeSearchUseCase.searchRestrictionRecipes(ingredients, servings);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
diff --git a/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java b/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
new file mode 100644
index 000000000..2897b36f2
--- /dev/null
+++ b/src/main/java/interface_adapter/recipe_search/ServingsViewModel.java
@@ -0,0 +1,45 @@
+package interface_adapter.recipe_search;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * A ViewModel for managing servings.
+ */
+public class ServingsViewModel {
+ private int servings = 1;
+ private final List availableServings = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+ private final PropertyChangeSupport pcs;
+
+ public ServingsViewModel() {
+ this.pcs = new PropertyChangeSupport(this);
+ }
+
+ // Getter for current servings
+ public int getServings() {
+ return servings;
+ }
+
+ // Setter for servings with PropertyChangeSupport
+ public void setServings(int newServings) {
+ int oldServings = this.servings;
+ this.servings = newServings;
+ pcs.firePropertyChange("servings", oldServings, newServings);
+ }
+
+ // Getter for available servings
+ public List getAvailableServings() {
+ return availableServings;
+ }
+
+ // Methods to add and remove property change listeners
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ pcs.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ pcs.removePropertyChangeListener(listener);
+ }
+}
diff --git a/src/main/java/view/RecipeSearchView.java b/src/main/java/view/RecipeSearchView.java
index 5def0381b..ac8a50509 100644
--- a/src/main/java/view/RecipeSearchView.java
+++ b/src/main/java/view/RecipeSearchView.java
@@ -3,6 +3,7 @@
import interface_adapter.recipe_search.RecipeSearchController;
import interface_adapter.recipe_search.RecipeSearchState;
import interface_adapter.recipe_search.RecipeSearchViewModel;
+import interface_adapter.recipe_search.ServingsViewModel;
import java.awt.BorderLayout;
import java.awt.Component;
@@ -17,16 +18,7 @@
import java.util.List;
import java.util.Map;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.DefaultListModel;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextField;
+import javax.swing.*;
/**
* A view for searching recipes by ingredients.
@@ -52,6 +44,7 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private final JPanel resultsPanel;
private final DefaultListModel recipeListModel;
private final JList recipeResults;
+ private final JComboBox servingsDropdown;
private final List ingredients;
private final JPanel restrictionPanel;
private final JButton addRestrictionButton;
@@ -61,15 +54,20 @@ public class RecipeSearchView extends JPanel implements ActionListener, Property
private Map> restrictionMap;
private final RecipeSearchViewModel recipeSearchViewModel;
+ private final ServingsViewModel servingsViewModel;
private RecipeSearchController recipeSearchController;
/**
* Constructs a new RecipeSearchView.
*
* @param viewModel The view model for recipe search
+ * @param servingsViewModel The view model for servings
*/
- public RecipeSearchView(RecipeSearchViewModel viewModel) {
+ public RecipeSearchView(RecipeSearchViewModel viewModel, ServingsViewModel servingsViewModel) {
this.recipeSearchViewModel = viewModel;
+ this.servingsViewModel = servingsViewModel;
+
+ this.servingsViewModel.addPropertyChangeListener(this);
this.recipeSearchViewModel.addPropertyChangeListener(this);
// Initialize components
@@ -103,6 +101,10 @@ public RecipeSearchView(RecipeSearchViewModel viewModel) {
restrictions = new ArrayList<>();
restrictionMap = new HashMap<>();
+ servingsDropdown = new JComboBox<>(servingsViewModel.getAvailableServings().toArray(new Integer[0]));
+ servingsDropdown.setSelectedItem(servingsViewModel.getServings());
+ servingsDropdown.addActionListener(this);
+
// Set up the main layout
this.setLayout(new BorderLayout());
@@ -150,7 +152,11 @@ public void setRecipeSearchController(RecipeSearchController controller) {
@Override
public void actionPerformed(ActionEvent evt) {
- if (evt.getSource().equals(addIngredientButton)) {
+ if (evt.getSource().equals(servingsDropdown)) {
+ int selectedServings = (int) servingsDropdown.getSelectedItem();
+ servingsViewModel.setServings(selectedServings);
+ }
+ else if (evt.getSource().equals(addIngredientButton)) {
final String ingredient = ingredientField.getText().trim();
if (!ingredient.isEmpty()) {
ingredients.add(ingredient);
@@ -179,9 +185,9 @@ public void actionPerformed(ActionEvent evt) {
} else if (evt.getSource().equals(searchButton)) {
if (recipeSearchController != null) {
if (restrictions.isEmpty()) {
- recipeSearchController.executeSearch(new ArrayList<>(ingredients));
+ recipeSearchController.executeSearch(new ArrayList<>(ingredients), servingsViewModel.getServings());
} else {
- recipeSearchController.executeRestrictionSearch(restrictionMap);
+ recipeSearchController.executeRestrictionSearch(restrictionMap, servingsViewModel.getServings());
}
}
}
@@ -189,6 +195,9 @@ public void actionPerformed(ActionEvent evt) {
@Override
public void propertyChange(PropertyChangeEvent evt) {
+ if ("servings".equals(evt.getPropertyName())) {
+ servingsDropdown.setSelectedItem(evt.getNewValue());
+ }
RecipeSearchState state = (RecipeSearchState) evt.getNewValue();
if (state != null) {
if (state.getError() != null) {
@@ -199,10 +208,6 @@ public void propertyChange(PropertyChangeEvent evt) {
}
}
- public void updateSelection(String selectionText) {
- restrictionListModel.addElement(selectionText);
- }
-
private void setupInputPanel() {
inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.Y_AXIS));
@@ -212,11 +217,11 @@ private void setupInputPanel() {
addIngredientPanel.add(addIngredientButton);
addIngredientPanel.add(addRestrictionButton);
- final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
- ingredientScrollPane.setPreferredSize(
- new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
+ final JPanel servingsPanel = new JPanel();
+ servingsPanel.add(new JLabel("Select Servings:"));
+ servingsPanel.add(servingsDropdown);
- final JScrollPane restrictionScrollPane = new JScrollPane(restrictionList);
+ final JScrollPane ingredientScrollPane = new JScrollPane(ingredientList);
ingredientScrollPane.setPreferredSize(
new Dimension(INGREDIENT_LIST_WIDTH, INGREDIENT_LIST_HEIGHT));
@@ -224,9 +229,9 @@ private void setupInputPanel() {
removeButtonPanel.add(removeIngredientButton);
inputPanel.add(addIngredientPanel);
+ inputPanel.add(servingsPanel);
inputPanel.add(Box.createVerticalStrut(VERTICAL_SPACING));
inputPanel.add(ingredientScrollPane);
- inputPanel.add(restrictionScrollPane);
inputPanel.add(removeButtonPanel);
}
From 270350fd7908ad5f882fde1ae0545e352fd19689 Mon Sep 17 00:00:00 2001
From: kaibaek
Date: Mon, 18 Nov 2024 01:41:20 -0500
Subject: [PATCH 044/138] Just to save the testing copy for the newest version
---
src/main/java/app/MainRecipeApplication.java | 23 +-
src/main/java/app/RecipeAppBuilder.java | 122 ++++-
.../data_access/DBNoteDataAccessObject.java | 2 +-
.../data_access/MealPlanningDataAccess.java | 2 +-
.../MealPlanningDataAccessObject.java | 2 +-
.../java/data_access/RecipeSearchEdamam.java | 91 +++-
.../data_access/SavedRecipesDataAccess.java | 2 +-
src/main/java/entity/DietPlan.java | 1 -
src/main/java/entity/Food.java | 1 -
src/main/java/entity/Ingredient.java | 1 -
src/main/java/entity/MealPlanEntry.java | 12 +-
src/main/java/entity/Nutrition.java | 2 +-
src/main/java/entity/Recipe.java | 2 +-
src/main/java/entity/RecipeForSearch.java | 2 +-
src/main/java/entity/ShoppingList.java | 1 -
src/main/java/entity/User.java | 2 +-
src/main/java/entity/UserFactory.java | 2 +-
.../meal_planning/MealPlanningController.java | 29 +-
.../meal_planning/MealPlanningPresenter.java | 60 +++
.../meal_planning/MealPlanningState.java | 80 ++-
.../meal_planning/MealPlanningViewModel.java | 4 +-
.../recipe_search/RecipeSearchController.java | 19 +-
.../recipe_search/RecipeSearchPresenter.java | 14 +-
.../recipe_search/RecipeSearchState.java | 71 ++-
.../recipe_search/RecipeSearchViewModel.java | 2 +-
.../use_case/meal_planning/MealPlanning.java | 13 +
.../meal_planning/MealPlanningInteractor.java | 76 +++
.../MealPlanningOutputBoundary.java | 14 +
.../use_case/recipe_search/RecipeSearch.java | 2 +
.../recipe_search/RecipeSearchException.java | 2 +-
.../recipe_search/RecipeSearchImpl.java | 42 +-
.../RecipeSearchOutputBoundary.java | 11 +-
src/main/java/view/FilterFrameView.java | 53 +-
src/main/java/view/MealPlanningView.java | 494 +++++++++++++++++-
src/main/java/view/RecipeSearchView.java | 165 +++---
src/main/java/view/StartPageView.java | 2 +-
36 files changed, 1168 insertions(+), 255 deletions(-)
create mode 100644 src/main/java/use_case/meal_planning/MealPlanning.java
create mode 100644 src/main/java/use_case/meal_planning/MealPlanningInteractor.java
create mode 100644 src/main/java/use_case/meal_planning/MealPlanningOutputBoundary.java
diff --git a/src/main/java/app/MainRecipeApplication.java b/src/main/java/app/MainRecipeApplication.java
index 2b9614692..7db92623a 100644
--- a/src/main/java/app/MainRecipeApplication.java
+++ b/src/main/java/app/MainRecipeApplication.java
@@ -11,17 +11,9 @@
*
*/
public class MainRecipeApplication {
-
/**
- * The main entry point of the application.
- *
- * The program will show a search interface where users can:
- * - Add ingredients to their search
- * - Remove ingredients from their search
- * - Search for recipes matching their ingredients
- * - View recipe results including ingredients and basic instructions
- *
- * @param args commandline arguments are ignored
+ * Main method for the Recipe Application.
+ * @param args The command line arguments
*/
public static void main(String[] args) {
// Create the data access object for recipe search
@@ -29,10 +21,15 @@ public static void main(String[] args) {
// Create and configure the application using the builder
final RecipeAppBuilder builder = new RecipeAppBuilder();
+
+ // Build the application with correct order
builder.addRecipeSearchAPI(recipeSearchEdamam)
+ .addMealPlanningView()
+ .addMealPlanningUseCase()
.addRecipeSearchView()
- .addRecipeSearchUseCase()
- .build()
- .setVisible(true);
+ .addRecipeSearchUseCase();
+
+ // Build and show the frame
+ builder.build().setVisible(true);
}
}
\ No newline at end of file
diff --git a/src/main/java/app/RecipeAppBuilder.java b/src/main/java/app/RecipeAppBuilder.java
index 834461a17..eb49e9e0b 100644
--- a/src/main/java/app/RecipeAppBuilder.java
+++ b/src/main/java/app/RecipeAppBuilder.java
@@ -1,40 +1,95 @@
package app;
-import javax.swing.JFrame;
-import javax.swing.WindowConstants;
-
-import data_access.RecipeSearchEdamam;
+import javax.swing.*;
+import data_access.*;
import interface_adapter.recipe_search.*;
+import interface_adapter.meal_planning.*;
import use_case.recipe_search.*;
-import view.RecipeSearchView;
+import use_case.meal_planning.*;
+import view.*;
/**
- * Builder for the Recipe Search Application.
+ * Builder class for the RecipeApp.
*/
public class RecipeAppBuilder {
- public static final int HEIGHT = 600; // Increased for better recipe display
- public static final int WIDTH = 800; // Increased for better recipe display
+ public static final int HEIGHT = 600;
+ public static final int WIDTH = 800;
private RecipeSearchEdamam recipeSearchEdamam;
+ private SavedRecipesDataAccess savedRecipesDataAccess;
+ private MealPlanningDataAccess mealPlanningDataAccess;
+
private RecipeSearchViewModel recipeSearchViewModel;
+ private MealPlanningViewModel mealPlanningViewModel;
+
private RecipeSearchView recipeSearchView;
+ private MealPlanningView mealPlanningView;
+
private RecipeSearch recipeSearchUseCase;
+ private MealPlanning mealPlanningUseCase;
/**
- * Sets the Recipe Search API to be used in this application.
- * @param recipeSearchEdamam the API client to use
- * @return this builder
+ * Add the recipe search API.
+ * @param recipeSearchEdamam The recipe search API
+ * @return The builder instance
*/
public RecipeAppBuilder addRecipeSearchAPI(RecipeSearchEdamam recipeSearchEdamam) {
this.recipeSearchEdamam = recipeSearchEdamam;
+ this.savedRecipesDataAccess = new SavedRecipesDataAccessObject();
+ this.mealPlanningDataAccess = new MealPlanningDataAccessObject(this.savedRecipesDataAccess);
+ return this;
+ }
+
+ /**
+ * Add the meal planning view.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addMealPlanningView() {
+ mealPlanningViewModel = new MealPlanningViewModel();
+ mealPlanningView = new MealPlanningView(mealPlanningViewModel);
return this;
}
/**
- * Creates the objects for the Recipe Search Use Case and connects the RecipeSearchView to its
- * controller.
- * @return this builder
- * @throws RuntimeException if this method is called before addRecipeSearchView
+ * Add the recipe search view.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addRecipeSearchView() {
+ // Make sure mealPlanningView is created before this
+ if (mealPlanningView == null) {
+ throw new RuntimeException("addMealPlanningView must be called before addRecipeSearchView");
+ }
+
+ recipeSearchViewModel = new RecipeSearchViewModel();
+ recipeSearchView = new RecipeSearchView(recipeSearchViewModel);
+ recipeSearchView.setMealPlanningView(mealPlanningView);
+ return this;
+ }
+
+ /**
+ * Add the meal planning use case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addMealPlanningUseCase() {
+ if (mealPlanningView == null) {
+ throw new RuntimeException("addMealPlanningView must be called before addMealPlanningUseCase");
+ }
+
+ MealPlanningPresenter presenter = new MealPlanningPresenter(mealPlanningViewModel);
+ mealPlanningUseCase = new MealPlanningInteractor(
+ mealPlanningDataAccess,
+ savedRecipesDataAccess,
+ presenter
+ );
+ MealPlanningController controller = new MealPlanningController(mealPlanningUseCase);
+ mealPlanningView.setController(controller);
+
+ return this;
+ }
+
+ /**
+ * Add the recipe search use case.
+ * @return The builder instance
*/
public RecipeAppBuilder addRecipeSearchUseCase() {
if (recipeSearchView == null) {
@@ -42,7 +97,11 @@ public RecipeAppBuilder addRecipeSearchUseCase() {
}
RecipeSearchPresenter presenter = new RecipeSearchPresenter(recipeSearchViewModel);
- recipeSearchUseCase = new RecipeSearchImpl(recipeSearchEdamam, presenter);
+ recipeSearchUseCase = new RecipeSearchImpl(
+ recipeSearchEdamam,
+ savedRecipesDataAccess,
+ presenter
+ );
RecipeSearchController controller = new RecipeSearchController(recipeSearchUseCase);
recipeSearchView.setRecipeSearchController(controller);
@@ -50,28 +109,37 @@ public RecipeAppBuilder addRecipeSearchUseCase() {
}
/**
- * Creates the RecipeSearchView and underlying ViewModel.
- * @return this builder
+ * Get the saved recipes data access.
+ * @return The saved recipes data access
*/
- public RecipeAppBuilder addRecipeSearchView() {
- recipeSearchViewModel = new RecipeSearchViewModel();
- recipeSearchView = new RecipeSearchView(recipeSearchViewModel);
- return this;
+ public SavedRecipesDataAccess getSavedRecipesDataAccess() {
+ return savedRecipesDataAccess;
+ }
+
+ /**
+ * Get the meal planning data access.
+ * @return The meal planning data access
+ */
+ public MealPlanningDataAccess getMealPlanningDataAccess() {
+ return mealPlanningDataAccess;
}
/**
- * Builds the application.
- * @return the JFrame for the application
+ * Build the main frame.
+ * @return The main frame
*/
public JFrame build() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- frame.setTitle("Recipe Search Application");
+ frame.setTitle("Recipe Wiz");
frame.setSize(WIDTH, HEIGHT);
- frame.add(recipeSearchView);
- frame.setLocationRelativeTo(null); // Center on screen
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.addTab("Recipe Search", recipeSearchView);
+ tabbedPane.addTab("Meal Planning", mealPlanningView);
+ frame.add(tabbedPane);
+ frame.setLocationRelativeTo(null);
return frame;
}
}
\ No newline at end of file
diff --git a/src/main/java/data_access/DBNoteDataAccessObject.java b/src/main/java/data_access/DBNoteDataAccessObject.java
index dadb0cab0..c8a1e5c35 100644
--- a/src/main/java/data_access/DBNoteDataAccessObject.java
+++ b/src/main/java/data_access/DBNoteDataAccessObject.java
@@ -104,4 +104,4 @@ public String loadNote(User user) throws DataAccessException {
throw new RuntimeException(ex);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/MealPlanningDataAccess.java b/src/main/java/data_access/MealPlanningDataAccess.java
index 14c7733d3..62b012e1b 100644
--- a/src/main/java/data_access/MealPlanningDataAccess.java
+++ b/src/main/java/data_access/MealPlanningDataAccess.java
@@ -11,4 +11,4 @@ public interface MealPlanningDataAccess {
MealPlanEntry getMealPlanEntry(int userId, int entryId);
List getWeeklyPlan(int userId, LocalDate weekStart);
-}
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/MealPlanningDataAccessObject.java b/src/main/java/data_access/MealPlanningDataAccessObject.java
index ac31610cd..656f9c2d6 100644
--- a/src/main/java/data_access/MealPlanningDataAccessObject.java
+++ b/src/main/java/data_access/MealPlanningDataAccessObject.java
@@ -64,4 +64,4 @@ public List getWeeklyPlan(int userId, LocalDate weekStart) {
})
.toList();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/RecipeSearchEdamam.java b/src/main/java/data_access/RecipeSearchEdamam.java
index 41faa08b7..47ba375e5 100644
--- a/src/main/java/data_access/RecipeSearchEdamam.java
+++ b/src/main/java/data_access/RecipeSearchEdamam.java
@@ -1,7 +1,11 @@
package data_access;
+import entity.*;
+
import entity.RecipeForSearch;
+import java.util.*;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -68,7 +72,7 @@ public List searchRecipesByFoodName(String foodName) {
/**
* Get recipes by selecting restrictions.
*/
- public List searchRecipesByRestriction(String foodName, String[] diet, String[] health, String[] cuisineType) {
+ public List searchRecipesByRestriction(String foodName, String diet, String health, String cuisineType) {
List recipes = new ArrayList<>();
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
@@ -78,20 +82,14 @@ public List searchRecipesByRestriction(String foodName, String[
urlBuilder.addQueryParameter("app_key", APP_KEY);
// Add optional filters if provided
- if (diet != null) {
- for (String d : diet) {
- urlBuilder.addQueryParameter("diet", d);
- }
+ if (diet != null && !diet.isEmpty()) {
+ urlBuilder.addQueryParameter("diet", diet);
}
- if (health != null) {
- for (String h : health) {
- urlBuilder.addQueryParameter("health", h);
- }
+ if (health != null && !health.isEmpty()) {
+ urlBuilder.addQueryParameter("health", health);
}
- if (cuisineType != null && cuisineType.length != 0) {
- for (String c : cuisineType) {
- urlBuilder.addQueryParameter("cuisineType", c);
- }
+ if (cuisineType != null && !cuisineType.isEmpty()) {
+ urlBuilder.addQueryParameter("cuisineType", cuisineType);
}
Request request = new Request.Builder()
@@ -127,4 +125,69 @@ public List searchRecipesByRestriction(String foodName, String[
}
return recipes;
}
-}
+
+ /**
+ * Retrieve detailed recipe information by recipe ID.
+ * Needed for meal planning to get full recipe details when adding to calendar.
+ */
+
+ public Recipe getRecipeById(String recipeId) {
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL).newBuilder();
+ urlBuilder.addQueryParameter("type", "public");
+ urlBuilder.addQueryParameter("id", recipeId);
+ urlBuilder.addQueryParameter("app_id", APP_ID);
+ urlBuilder.addQueryParameter("app_key", APP_KEY);
+
+ Request request = new Request.Builder()
+ .url(urlBuilder.build().toString())
+ .build();
+
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (response.body() != null) {
+ String jsonData = response.body().string();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ JSONObject recipeJson = jsonObject.getJSONObject("recipe");
+
+ // Extract basic info
+ String name = recipeJson.getString("label");
+ String instructions = recipeJson.optString("url", "Instructions not available");
+
+ // Extract ingredients
+ List ingredients = new ArrayList<>();
+ JSONArray ingredientsArray = recipeJson.getJSONArray("ingredients");
+ for (int i = 0; i < ingredientsArray.length(); i++) {
+ JSONObject ingredientJson = ingredientsArray.getJSONObject(i);
+ int ingredientId = i + 1;
+ String ingredientName = ingredientJson.getString("food");
+ double quantity = ingredientJson.optDouble("quantity", 0.0);
+ String unit = ingredientJson.optString("measure", "");
+
+ ingredients.add(new Ingredient(ingredientId, ingredientName, quantity, unit));
+ }
+
+ // Extract nutrition
+ JSONObject nutritionJson = recipeJson.getJSONObject("totalNutrients");
+ Nutrition nutrition = new Nutrition(
+ nutritionJson.getJSONObject("ENERC_KCAL").getDouble("quantity"),
+ nutritionJson.getJSONObject("PROCNT").getDouble("quantity"),
+ nutritionJson.getJSONObject("FAT").getDouble("quantity"),
+ nutritionJson.getJSONObject("CHOCDF").getDouble("quantity"),
+ nutritionJson.getJSONObject("FIBTG").getDouble("quantity"),
+ nutritionJson.getJSONObject("SUGAR").getDouble("quantity")
+ );
+
+ return new Recipe(
+ Integer.parseInt(recipeId),
+ name,
+ ingredients,
+ instructions,
+ nutrition,
+ new ArrayList<>() // Food list
+ );
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to get recipe details: " + e.getMessage());
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/SavedRecipesDataAccess.java b/src/main/java/data_access/SavedRecipesDataAccess.java
index ed28ecf87..a5a16314b 100644
--- a/src/main/java/data_access/SavedRecipesDataAccess.java
+++ b/src/main/java/data_access/SavedRecipesDataAccess.java
@@ -8,4 +8,4 @@ public interface SavedRecipesDataAccess {
void removeRecipe(int userId, int recipeId);
List getSavedRecipes(int userId);
Recipe getSavedRecipe(int userId, int recipeId);
-}
+}
\ No newline at end of file
diff --git a/src/main/java/entity/DietPlan.java b/src/main/java/entity/DietPlan.java
index eeaa43faa..5ee848739 100644
--- a/src/main/java/entity/DietPlan.java
+++ b/src/main/java/entity/DietPlan.java
@@ -62,4 +62,3 @@ public void setGoals(String goals) {
this.goals = goals;
}
}
-
diff --git a/src/main/java/entity/Food.java b/src/main/java/entity/Food.java
index 8916bcc54..a12f3f982 100644
--- a/src/main/java/entity/Food.java
+++ b/src/main/java/entity/Food.java
@@ -70,4 +70,3 @@ public void setNutrition(Nutrition nutrition) {
this.nutrition = nutrition;
}
}
-
diff --git a/src/main/java/entity/Ingredient.java b/src/main/java/entity/Ingredient.java
index 19c41c0ae..8d8eb94d5 100644
--- a/src/main/java/entity/Ingredient.java
+++ b/src/main/java/entity/Ingredient.java
@@ -49,4 +49,3 @@ public void setUnit(String unit) {
this.unit = unit;
}
}
-
diff --git a/src/main/java/entity/MealPlanEntry.java b/src/main/java/entity/MealPlanEntry.java
index 78caad798..98b7620a7 100644
--- a/src/main/java/entity/MealPlanEntry.java
+++ b/src/main/java/entity/MealPlanEntry.java
@@ -7,8 +7,8 @@ public class MealPlanEntry {
private Recipe recipe;
private LocalDate date;
private int userId;
+ // e.g., "Breakfast", "Lunch", "Dinner"
private String mealType;
- private String status; // Added to track state like "planned", "completed", etc.
public MealPlanEntry(int entryId, Recipe recipe, LocalDate date, int userId, String mealType) {
this.entryId = entryId;
@@ -16,19 +16,9 @@ public MealPlanEntry(int entryId, Recipe recipe, LocalDate date, int userId, Str
this.date = date;
this.userId = userId;
this.mealType = mealType;
- this.status = "planned"; // Default status
}
// Getters and setters
-
- public String getStatus() {
- return status;
- }
-
- public void setStatus(String status) {
- this.status = status;
- }
-
public int getEntryId() {
return entryId;
}
diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java
index b94fcef60..9c68444f6 100644
--- a/src/main/java/entity/Nutrition.java
+++ b/src/main/java/entity/Nutrition.java
@@ -69,4 +69,4 @@ public double getSugar() {
public void setSugar(double sugar) {
this.sugar = sugar;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java
index 7c553dd22..f0012589e 100644
--- a/src/main/java/entity/Recipe.java
+++ b/src/main/java/entity/Recipe.java
@@ -70,4 +70,4 @@ public List getFood() {
public void setFood(List food) {
this.food = food;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/entity/RecipeForSearch.java b/src/main/java/entity/RecipeForSearch.java
index 077ebfa2f..fdbd50e25 100644
--- a/src/main/java/entity/RecipeForSearch.java
+++ b/src/main/java/entity/RecipeForSearch.java
@@ -50,4 +50,4 @@ public String getInstructions() {
public void setInstructions(String instructions) {
this.instructions = instructions;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/entity/ShoppingList.java b/src/main/java/entity/ShoppingList.java
index ec8867439..af36dc8f0 100644
--- a/src/main/java/entity/ShoppingList.java
+++ b/src/main/java/entity/ShoppingList.java
@@ -61,4 +61,3 @@ public void setStatus(String status) {
this.status = status;
}
}
-
diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java
index a57b49ec5..89be376a6 100644
--- a/src/main/java/entity/User.java
+++ b/src/main/java/entity/User.java
@@ -71,4 +71,4 @@ public List getSavedRecipes() {
public void setSavedRecipes(List savedRecipes) {
this.savedRecipes = savedRecipes;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java
index e367faeb2..bd14aae53 100644
--- a/src/main/java/entity/UserFactory.java
+++ b/src/main/java/entity/UserFactory.java
@@ -14,4 +14,4 @@ public class UserFactory {
public User create(String name, String password) {
return new User(name, password);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningController.java b/src/main/java/interface_adapter/meal_planning/MealPlanningController.java
index 5654af374..8ca2684ee 100644
--- a/src/main/java/interface_adapter/meal_planning/MealPlanningController.java
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningController.java
@@ -1,7 +1,34 @@
package interface_adapter.meal_planning;
import use_case.meal_planning.MealPlanning;
+import entity.Recipe;
import java.time.LocalDate;
+import java.util.List;
public class MealPlanningController {
-}
+ private final MealPlanning mealPlanningUseCase;
+
+ public MealPlanningController(MealPlanning mealPlanningUseCase) {
+ this.mealPlanningUseCase = mealPlanningUseCase;
+ }
+
+ public List getSavedRecipes(int userId) {
+ return mealPlanningUseCase.getSavedRecipes(userId);
+ }
+
+ public void updateMealStatus(int userId, int entryId, String status) {
+ mealPlanningUseCase.updateMealStatus(userId, entryId, status);
+ }
+
+ public void addToCalendar(int userId, int recipeId, LocalDate date, String mealType) {
+ mealPlanningUseCase.addToCalendar(userId, recipeId, date, mealType);
+ }
+
+ public void removeFromCalendar(int userId, int mealPlanEntryId) {
+ mealPlanningUseCase.removeFromCalendar(userId, mealPlanEntryId);
+ }
+
+ public void viewCalendarWeek(int userId, LocalDate weekStart) {
+ mealPlanningUseCase.getCalendarWeek(userId, weekStart);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java b/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
index a54d4e92e..377a33545 100644
--- a/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningPresenter.java
@@ -1,8 +1,68 @@
package interface_adapter.meal_planning;
import entity.MealPlanEntry;
+import entity.Recipe;
import use_case.meal_planning.MealPlanningOutputBoundary;
import java.util.List;
public class MealPlanningPresenter implements MealPlanningOutputBoundary {
+ private final MealPlanningViewModel viewModel;
+
+ public MealPlanningPresenter(MealPlanningViewModel viewModel) {
+ this.viewModel = viewModel;
+ }
+
+ @Override
+ public void presentCalendarWeek(List entries) {
+ MealPlanningState currentState = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(currentState); // Copy constructor
+ newState.setMealPlanEntries(entries);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentAddSuccess(String message) {
+ MealPlanningState state = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(state);
+ newState.setMessage(message);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentRemoveSuccess(String message) {
+ MealPlanningState state = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(state);
+ newState.setMessage(message);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentStatusUpdateSuccess(String message) {
+ MealPlanningState state = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(state);
+ newState.setMessage(message);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentError(String error) {
+ MealPlanningState state = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(state);
+ newState.setError(error);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
+
+ @Override
+ public void presentSavedRecipes(List recipes) {
+ MealPlanningState currentState = viewModel.getState();
+ MealPlanningState newState = new MealPlanningState(currentState);
+ newState.setSavedRecipes(recipes);
+ viewModel.setState(newState);
+ viewModel.firePropertyChanged();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningState.java b/src/main/java/interface_adapter/meal_planning/MealPlanningState.java
index fc6e924d6..a80f654ea 100644
--- a/src/main/java/interface_adapter/meal_planning/MealPlanningState.java
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningState.java
@@ -1,4 +1,82 @@
package interface_adapter.meal_planning;
+import entity.MealPlanEntry;
+import entity.Recipe;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
public class MealPlanningState {
-}
+ private List mealPlanEntries = new ArrayList<>();
+ private List savedRecipes = new ArrayList<>(); // Added to store saved recipes
+ private LocalDate currentWeekStart = LocalDate.now();
+ private String message;
+ private String error;
+ private boolean isLoading = false;
+
+ public MealPlanningState(MealPlanningState copy) {
+ if (copy != null) {
+ this.mealPlanEntries = new ArrayList<>(copy.mealPlanEntries);
+ this.savedRecipes = new ArrayList<>(copy.savedRecipes);
+ this.currentWeekStart = copy.currentWeekStart;
+ this.message = copy.message;
+ this.error = copy.error;
+ this.isLoading = copy.isLoading;
+ }
+ }
+
+ // Default constructor
+ public MealPlanningState() {
+
+ }
+
+ // Getters and setters
+
+ public List getSavedRecipes() {
+ return new ArrayList<>(savedRecipes);
+ }
+
+ public void setSavedRecipes(List recipes) {
+ this.savedRecipes = new ArrayList<>(recipes);
+ }
+
+ public boolean isLoading() {
+ return isLoading;
+ }
+
+ public void setLoading(boolean loading) {
+ isLoading = loading;
+ }
+
+ public List getMealPlanEntries() {
+ return new ArrayList<>(mealPlanEntries);
+ }
+
+ public void setMealPlanEntries(List entries) {
+ this.mealPlanEntries = new ArrayList<>(entries);
+ }
+
+ public LocalDate getCurrentWeekStart() {
+ return currentWeekStart;
+ }
+
+ public void setCurrentWeekStart(LocalDate date) {
+ this.currentWeekStart = date;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java b/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
index 93dfe92dd..9337cb552 100644
--- a/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
+++ b/src/main/java/interface_adapter/meal_planning/MealPlanningViewModel.java
@@ -2,7 +2,7 @@
import interface_adapter.ViewModel;
-public class MealPlanningViewModel extends ViewModel {
+public class MealPlanningViewModel extends ViewModel {
public static final String TITLE_LABEL = "Meal Planning Calendar";
public static final String ADD_BUTTON_LABEL = "Add to Calendar";
public static final String REMOVE_BUTTON_LABEL = "Remove from Calendar";
@@ -12,4 +12,4 @@ public class MealPlanningViewModel extends ViewModel {
public MealPlanningViewModel() {
super("Meal Planning Calendar");
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
index 7ede9710b..cd1a89028 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchController.java
@@ -1,5 +1,6 @@
package interface_adapter.recipe_search;
+import entity.Recipe;
import use_case.recipe_search.RecipeSearch;
import java.util.List;
import java.util.Map;
@@ -18,9 +19,9 @@ public RecipeSearchController(RecipeSearch recipeSearchUseCase) {
* Execute a recipe search with the given ingredients.
* @param ingredients List of ingredients to search for
*/
- public void executeSearch(List ingredients, int servings) {
+ public void executeSearch(List ingredients) {
try {
- recipeSearchUseCase.searchRecipes(ingredients, servings);
+ recipeSearchUseCase.searchRecipes(ingredients);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
@@ -31,13 +32,21 @@ public void executeSearch(List ingredients, int servings) {
* Execute a recipe search with the given restrictions.
* @param ingredients Map of ingredients to search for
*/
- public void executeRestrictionSearch(Map> ingredients, int servings) {
+ public void executeRestrictionSearch(Map> ingredients) {
try {
- recipeSearchUseCase.searchRestrictionRecipes(ingredients, servings);
+ recipeSearchUseCase.searchRestrictionRecipes(ingredients);
}
catch (Exception e) {
// Error will be handled by the presenter through output boundary
}
}
-}
+ public void saveRecipe(int userId, Recipe recipe) {
+ try {
+ recipeSearchUseCase.saveRecipe(userId, recipe);
+ }
+ catch (Exception e) {
+ // Error will be handled by the presenter
+ }
+ }
+}
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
index c46ec77b6..94a4fbb90 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchPresenter.java
@@ -7,7 +7,7 @@
import java.util.ArrayList;
/**
- * Presenter for the recipe search functionality.
+ * Presenter for the recipe search use case.
*/
public class RecipeSearchPresenter implements RecipeSearchOutputBoundary {
private final RecipeSearchViewModel viewModel;
@@ -21,6 +21,10 @@ public void presentRecipes(List recipes) {
RecipeSearchState state = new RecipeSearchState();
List recipeResults = new ArrayList<>();
+ // Store the actual Recipe objects
+ state.setRecipes(recipes);
+
+ // Create string representations for display
for (Recipe recipe : recipes) {
StringBuilder description = new StringBuilder();
description.append(recipe.getTitle()).append("\n");
@@ -43,4 +47,12 @@ public void presentError(String error) {
viewModel.setState(state);
viewModel.firePropertyChanged();
}
+
+ @Override
+ public void presentSaveSuccess(Recipe recipe) {
+ RecipeSearchState state = new RecipeSearchState();
+ state.setMessage("Recipe '" + recipe.getTitle() + "' saved successfully!");
+ viewModel.setState(state);
+ viewModel.firePropertyChanged();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
index 7a6dcf1d4..0cd0f69e6 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchState.java
@@ -1,5 +1,6 @@
package interface_adapter.recipe_search;
+import entity.Recipe;
import java.util.ArrayList;
import java.util.List;
@@ -9,16 +10,22 @@
public class RecipeSearchState {
private List ingredients = new ArrayList<>();
private List recipeResults = new ArrayList<>();
+ private List recipes = new ArrayList<>();
private String error;
+ private String message;
/**
* Copy constructor for RecipeSearchState.
* @param copy The state to copy from
*/
public RecipeSearchState(RecipeSearchState copy) {
- ingredients = new ArrayList<>(copy.ingredients);
- recipeResults = new ArrayList<>(copy.recipeResults);
- error = copy.error;
+ if (copy != null) {
+ ingredients = new ArrayList<>(copy.ingredients);
+ recipeResults = new ArrayList<>(copy.recipeResults);
+ recipes = new ArrayList<>(copy.recipes);
+ error = copy.error;
+ message = copy.message;
+ }
}
/**
@@ -28,27 +35,83 @@ public RecipeSearchState() {
}
+ /**
+ * Getter for ingredients.
+ * @return A copy of the ingredients list
+ */
public List getIngredients() {
return new ArrayList<>(ingredients);
}
+ /**
+ * Setter for ingredients.
+ * @param ingredients The ingredients to set
+ */
public void setIngredients(List ingredients) {
this.ingredients = new ArrayList<>(ingredients);
}
+ /**
+ * Getter for recipeResults.
+ * @return A copy of the recipeResults list
+ */
public List getRecipeResults() {
return new ArrayList<>(recipeResults);
}
+ /**
+ * Setter for recipeResults.
+ * @param recipeResults The recipeResults to set
+ */
public void setRecipeResults(List recipeResults) {
this.recipeResults = new ArrayList<>(recipeResults);
}
+ /**
+ * Getter for error.
+ * @return The error message
+ */
public String getError() {
return error;
}
+ /**
+ * Setter for error.
+ * @param error The error message to set
+ */
public void setError(String error) {
this.error = error;
}
-}
+
+ /**
+ * Getter for message.
+ * @return The message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Setter for message.
+ * @param message The message to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Getter for recipes.
+ * @return A copy of the recipes list
+ */
+ public List getRecipes() {
+ return new ArrayList<>(recipes);
+ }
+
+ /**
+ * Setter for recipes.
+ * @param recipes The recipes to set
+ */
+ public void setRecipes(List recipes) {
+ this.recipes = new ArrayList<>(recipes);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
index 58e77cbc1..76fa0b66e 100644
--- a/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
+++ b/src/main/java/interface_adapter/recipe_search/RecipeSearchViewModel.java
@@ -19,4 +19,4 @@ public RecipeSearchViewModel() {
// However, this view name is not used in RecipeSearchView class.
super("Recipe Search");
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/meal_planning/MealPlanning.java b/src/main/java/use_case/meal_planning/MealPlanning.java
new file mode 100644
index 000000000..a770b69b2
--- /dev/null
+++ b/src/main/java/use_case/meal_planning/MealPlanning.java
@@ -0,0 +1,13 @@
+package use_case.meal_planning;
+
+import java.time.LocalDate;
+import java.util.List;
+import entity.Recipe;
+
+public interface MealPlanning {
+ void addToCalendar(int userId, int recipeId, LocalDate date, String mealType);
+ void removeFromCalendar(int userId, int mealPlanEntryId);
+ void getCalendarWeek(int userId, LocalDate weekStart);
+ List getSavedRecipes(int userId); // Added to get user's saved recipes
+ void updateMealStatus(int userId, int entryId, String status); // Added to update meal status
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/meal_planning/MealPlanningInteractor.java b/src/main/java/use_case/meal_planning/MealPlanningInteractor.java
new file mode 100644
index 000000000..5cfe9fc5f
--- /dev/null
+++ b/src/main/java/use_case/meal_planning/MealPlanningInteractor.java
@@ -0,0 +1,76 @@
+package use_case.meal_planning;
+
+import data_access.MealPlanningDataAccess;
+import data_access.SavedRecipesDataAccess;
+import entity.MealPlanEntry;
+import entity.Recipe;
+import java.time.LocalDate;
+import java.util.List;
+
+public class MealPlanningInteractor implements MealPlanning {
+ private final MealPlanningDataAccess dataAccess;
+ private final SavedRecipesDataAccess savedRecipesDataAccess;
+ private final MealPlanningOutputBoundary outputBoundary;
+
+ public MealPlanningInteractor(MealPlanningDataAccess dataAccess,
+ SavedRecipesDataAccess savedRecipesDataAccess,
+ MealPlanningOutputBoundary outputBoundary) {
+ this.dataAccess = dataAccess;
+ this.savedRecipesDataAccess = savedRecipesDataAccess;
+ this.outputBoundary = outputBoundary;
+ }
+
+ @Override
+ public List getSavedRecipes(int userId) {
+ try {
+ List recipes = savedRecipesDataAccess.getSavedRecipes(userId);
+ outputBoundary.presentSavedRecipes(recipes);
+ return recipes;
+ } catch (Exception e) {
+ outputBoundary.presentError("Failed to load saved recipes: " + e.getMessage());
+ return List.of();
+ }
+ }
+
+ @Override
+ public void updateMealStatus(int userId, int entryId, String status) {
+ try {
+ dataAccess.updateMealStatus(userId, entryId, status);
+ outputBoundary.presentStatusUpdateSuccess("Meal status updated");
+ } catch (Exception e) {
+ outputBoundary.presentError("Failed to update meal status: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void addToCalendar(int userId, int recipeId, LocalDate date, String mealType) {
+ try {
+ dataAccess.addMealPlanEntry(userId, recipeId, date, mealType);
+ outputBoundary.presentAddSuccess("Recipe added to calendar");
+ } catch (Exception e) {
+ outputBoundary.presentError("Failed to add recipe to calendar: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void removeFromCalendar(int userId, int mealPlanEntryId) {
+ try {
+ dataAccess.removeMealPlanEntry(userId, mealPlanEntryId);
+ outputBoundary.presentRemoveSuccess("Recipe removed from calendar");
+ }
+ catch (Exception e) {
+ outputBoundary.presentError("Failed to remove recipe from calendar: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void getCalendarWeek(int userId, LocalDate weekStart) {
+ try {
+ List entries = dataAccess.getWeeklyPlan(userId, weekStart);
+ outputBoundary.presentCalendarWeek(entries);
+ } catch (Exception e) {
+ outputBoundary.presentError("Failed to load calendar: " + e.getMessage());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/meal_planning/MealPlanningOutputBoundary.java b/src/main/java/use_case/meal_planning/MealPlanningOutputBoundary.java
new file mode 100644
index 000000000..9b86979e7
--- /dev/null
+++ b/src/main/java/use_case/meal_planning/MealPlanningOutputBoundary.java
@@ -0,0 +1,14 @@
+package use_case.meal_planning;
+
+import entity.MealPlanEntry;
+import entity.Recipe;
+import java.util.List;
+
+public interface MealPlanningOutputBoundary {
+ void presentCalendarWeek(List entries);
+ void presentAddSuccess(String message);
+ void presentRemoveSuccess(String message);
+ void presentStatusUpdateSuccess(String message);
+ void presentError(String error);
+ void presentSavedRecipes(List recipes);
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/recipe_search/RecipeSearch.java b/src/main/java/use_case/recipe_search/RecipeSearch.java
index 5f4633a1a..3e67d776b 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearch.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearch.java
@@ -21,4 +21,6 @@ public interface RecipeSearch {
* @throws RecipeSearchException if search fails
*/
void searchRestrictionRecipes(Map> restrictions) throws RecipeSearchException;
+
+ void saveRecipe(int userId, Recipe recipe) throws RecipeSearchException;
}
\ No newline at end of file
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchException.java b/src/main/java/use_case/recipe_search/RecipeSearchException.java
index 692fc4044..992608d5a 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchException.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchException.java
@@ -11,4 +11,4 @@ public RecipeSearchException(String message) {
public RecipeSearchException(String message, Throwable cause) {
super(message, cause);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
index 57f7bf00c..a9c846e8d 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchImpl.java
@@ -1,5 +1,6 @@
package use_case.recipe_search;
+import data_access.SavedRecipesDataAccess;
import entity.Recipe;
import entity.RecipeForSearch;
import entity.Ingredient;
@@ -9,19 +10,35 @@
import java.util.ArrayList;
import java.util.Map;
-/**
- * Implementation of the recipe search use case.
- */
public class RecipeSearchImpl implements RecipeSearch {
private final RecipeSearchEdamam recipeSearchEdamam;
private final RecipeSearchOutputBoundary outputBoundary;
+ private final SavedRecipesDataAccess savedRecipesDataAccess;
public RecipeSearchImpl(RecipeSearchEdamam recipeSearchEdamam,
+ SavedRecipesDataAccess savedRecipesDataAccess,
RecipeSearchOutputBoundary outputBoundary) {
this.recipeSearchEdamam = recipeSearchEdamam;
+ this.savedRecipesDataAccess = savedRecipesDataAccess;
this.outputBoundary = outputBoundary;
}
+ @Override
+ public void saveRecipe(int userId, Recipe recipe) throws RecipeSearchException {
+ try {
+ // Save the recipe
+ savedRecipesDataAccess.saveRecipe(userId, recipe);
+
+ // Present success
+ outputBoundary.presentSaveSuccess(recipe);
+ }
+ catch (Exception e) {
+ // Present error
+ outputBoundary.presentError("Failed to save recipe: " + e.getMessage());
+ throw new RecipeSearchException("Failed to save recipe", e);
+ }
+ }
+
@Override
public void searchRecipes(List ingredients) throws RecipeSearchException {
try {
@@ -49,17 +66,18 @@ public void searchRestrictionRecipes(Map> restrictions) thr
try {
// Join ingredients with commas for the API search
String searchFoodQuery = String.join(",", restrictions.get("Food Name"));
- String[] searchDietQuery = restrictions.get("Diet Label").toArray(new String[0]);
- String[] searchHealthQuery = restrictions.get("Health Label").toArray(new String[0]);
- String[] searchCuisineQuery = restrictions.get("Cuisine Label").toArray(new String[0]);
+ String searchDietQuery = String.join(",", restrictions.get("Diet Label"));
+ String searchHealthQuery = String.join(",", restrictions.get("Health Label"));
+ String searchCuisineQuery = String.join(",", restrictions.get("Cuisine Type"));
// Get recipes from Edamam API
- List searchResults = recipeSearchEdamam.searchRecipesByRestriction(searchFoodQuery, searchDietQuery, searchHealthQuery, searchCuisineQuery);
+ List searchResults = recipeSearchEdamam.searchRecipesByRestriction(
+ searchFoodQuery, searchDietQuery, searchHealthQuery, searchCuisineQuery);
// Convert API results to Recipe entities
List recipes = convertToRecipes(searchResults);
- // Present success
+ // // Present success
outputBoundary.presentRecipes(recipes);
}
catch (Exception e) {
@@ -81,8 +99,8 @@ private List convertToRecipes(List searchResults) {
ingredients.add(new Ingredient(
ingredientId++,
ingredientStr,
- 0.0, // Default quantity
- "" // Default unit
+ 0.0,
+ ""
));
}
@@ -92,8 +110,8 @@ private List convertToRecipes(List searchResults) {
result.getTitle(),
ingredients,
result.getInstructions(),
- new Nutrition(0, 0, 0, 0, 0, 0), // Default nutrition
- new ArrayList<>() // Empty food list
+ new Nutrition(0, 0, 0, 0, 0, 0), // Default nutrition
+ new ArrayList<>() // Empty food list
);
recipes.add(recipe);
diff --git a/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java b/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
index d96d9367d..e0512ecb2 100644
--- a/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
+++ b/src/main/java/use_case/recipe_search/RecipeSearchOutputBoundary.java
@@ -3,9 +3,6 @@
import entity.Recipe;
import java.util.List;
-/**
- * Output boundary for the recipe search use case.
- */
public interface RecipeSearchOutputBoundary {
/**
* Present the recipes to the user.
@@ -18,4 +15,10 @@ public interface RecipeSearchOutputBoundary {
* @param error Error message to present
*/
void presentError(String error);
-}
+
+ /**
+ * Present a success message when a recipe is saved.
+ * @param recipe The recipe that was successfully saved
+ */
+ void presentSaveSuccess(Recipe recipe);
+}
\ No newline at end of file
diff --git a/src/main/java/view/FilterFrameView.java b/src/main/java/view/FilterFrameView.java
index 92835bb1a..9800af8fa 100644
--- a/src/main/java/view/FilterFrameView.java
+++ b/src/main/java/view/FilterFrameView.java
@@ -146,61 +146,31 @@ public String getSelectedFilters() {
return resultText.toString();
}
-// public Map> getSelectedFiltersMap() {
-// final Map> filtersMap = new HashMap<>();
-//
-// // Diet Label
-// for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
-// if (checkBox.isSelected()) {
-// addValueToKey(filtersMap, "Diet Types", checkBox.getText());
-// }
-// }
-//
-// // Health Label
-// for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
-// if (checkBox.isSelected()) {
-// addValueToKey(filtersMap, "Health Types", checkBox.getText());
-// }
-// }
-//
-// // Cuisine Type
-// for (JCheckBox checkBox : getCheckBoxes(cuisinePanel)) {
-// if (checkBox.isSelected()) {
-// addValueToKey(filtersMap, "Cuisine Types", checkBox.getText());
-// }
-// }
-//
-// return filtersMap;
-// }
-
- public List getDietType() {
- ArrayList dietList = new ArrayList<>();
+ public Map> getSelectedFiltersMap() {
+ final Map> filtersMap = new HashMap<>();
+
+ // Diet Label
for (JCheckBox checkBox : getCheckBoxes(dietPanel)) {
if (checkBox.isSelected()) {
- dietList.add(checkBox.getText());
+ addValueToKey(filtersMap, "Diet Types", checkBox.getText());
}
}
- return dietList;
- }
- public List getHealthType() {
- ArrayList healthList = new ArrayList<>();
+ // Health Label
for (JCheckBox checkBox : getCheckBoxes(healthPanel)) {
if (checkBox.isSelected()) {
- healthList.add(checkBox.getText());
+ addValueToKey(filtersMap, "Health Labels", checkBox.getText());
}
}
- return healthList;
- }
- public List getCuisineType() {
- ArrayList