From 36d88ae535c85c33cd92fe5f6f1cc77d0e50b3dd Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Sun, 3 Nov 2024 22:19:11 -0500 Subject: [PATCH 001/148] Start README.md for project --- README.md | 189 ++++++------------------------------------------------ 1 file changed, 20 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index 8c4770018..d15432b25 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,32 @@ -# Lab 5: Logout +# CSC207 Project - Recipe Generator -## Preamble +## Team Members -In the current homework, you added code to the login use case to save the currently-logged-in -user by saving the user in the Data Access Layer. You also added a unit test for this. +⁠Farshad Haddadi +* GitHub username: farshad-haddadi +* Email:⁠⁠ farshad.haddadi@mail.utoronto.ca -In this lab, you will complete a logout use case as a team. You will also begin to discuss your project -and the use cases that need to be implemented. +Sung-chi (William) Wu +* GitHub username: ⁠⁠wusungch +* Email:⁠⁠ sungchi2024@outlook.com -We have created all the Clean Architecture classes necessary for the logout use case. +Xinyu (Cindy) Zhang +* GitHub username: Cindyzzz616 +* Email: cxy.zhang16@gmail.com -By Friday, your team will submit: -- your completed lab code [for credit] -- a draft of your project blueprint proposal. [required, but not for credit] +Shuxin (Kate) Zhou +* GitHub username: kayzoo8 +* Email: katezhou2005@gmail.com -# Phase 2 [for credit] -_(recall, Phase 1 was your solo task of adding the storage of the currently-logged-in user)_ -## Task 0: Fork this repo on GitHub -**To get started, one team member should fork this repo on GitHub and share it with the team. -All of you should then clone it.** +## User Stories +* Member 1: The core feature of the program allows users to input ingredients they have on hand and receive recipe suggestions. Users can enter ingredients separated by commas, and the system will generate relevant recipes. If some ingredients are missing, the system can suggest close alternatives or highlight what else is needed. This feature helps users efficiently use the ingredients they already own, potentially reducing food waste and unnecessary shopping trips. -* * * +* Member 2: A key component of the system is the ability for users to filter recipes by diet and cuisine. This feature allows users to customize results based on dietary needs—such as vegan, gluten-free, or keto—or by preferred cuisines like Italian or Mexican. Users can apply multiple filters simultaneously to find recipes that meet both dietary restrictions and taste preferences. Managing these filters effectively ensures that the results remain useful even when dietary and cultural preferences overlap. -Suggested logistics: One of you should invite the others to collaborate on their fork of the -original repo on GitHub. You can do this in your repo on GitHub under `Settings -> Collaborators`. -This will allow you to push branches to a common repo and then use pull requests to contribute -your code and review. To prevent others from pushing directly to the main branch, -we recommend you set branch protection rules on GitHub. Below are how the settings might look if you -add branch protection rules: +* Member 3: Another feature provides detailed nutritional information for each recipe. Users can click a button to access data such as calorie counts, macronutrients (carbs, proteins, fats), and other key nutrients. This feature supports those who are tracking their nutrition or following specific dietary goals. It is important for the system to present this information clearly, accounting for potential variations in portion sizes and ingredient substitutions. -![image of branch protection rules for main with the -requirement of two approvers to merge in pull requests. -](images/branch_protection_rules.png) +* Member 4: The system will also allow users to bookmark recipes for future reference. This feature enables users to save recipes they find interesting or useful and organize them into categories like "Dinner" or "Favorites." Additionally, a "Recently Viewed" section can help users retrieve recipes they forgot to bookmark. The ability to sync bookmarks across devices ensures easy access whether users are on a phone or computer. -* * * +* Member 5: Finally, the program offers a shopping list feature for adjacent recipes. When users are missing one or two ingredients for a recipe, the system will generate a shopping list with only the required items. This function can help users decide what to buy if they want to try new meals without doing extensive shopping. Export options, such as saving the list as a PDF or sharing it with other apps, enhance usability. The system will also need to distinguish between essential ingredients and common pantry staples to keep the lists concise. -Open the project in IntelliJ and make sure you can successfully run `app/Main.java`. -Note: you may need to set the Project SDK in the `Project Structure...` menu, and possibly -also manually link the Maven project, as you did in Phase 1. - -## Task 1: Understanding the Program - -You may notice that we have refactored the CA engine code _slightly_ since Phase 1, but the -way we build the engine is drastically different: we have switched from using Factories to -using the Builder design pattern, which we'll be discussing in lecture soon. - -Open up `app.Main` and read it as a team. -- What are the Views and what are the current Use Cases? -- Which Uses Cases are triggered from each View? -- Which version of the DAO is `app.Main` using? - -The major change since Phase 1 is that we have added the `app.AppBuilder` class which makes -it easier to understand how our CA engine is being constructed — it also makes `app.Main` nice and concise! -- Why do all those `addX` methods end in `return this;`? - -Run the program and make sure the signup and login Use Cases work. - -Currently, you'll notice that the "Log Out" button still doesn't actually log you out. It's time to fix -that button, which is part of the `LoggedInView`. -We have created all the classes for you, but some of the code is missing. -As a team, your task is to fill in the missing code so that the Logout Use Case is implemented. -**The next part of the readme describes how your team will do this.** - -* * * - -**Your team will know when you are done when:** - -- Clicking the "Log Out" button takes the user back to the Login View when you use the program. -- The provided `LogoutInteractorTest` test passes. - -The "Log Out" button is an instance variable in class `LoggedInVew`. Go find it. -Also look at the `interface_adapter.change_password.LoggedInViewModel`, which contains any -data showing on the `LoggedInVew`. - -* * * - -## Task 2: Dividing up the work - -There are `TODO` comments left in the files -Recall that you can use the TODO tool window to conveniently pull up a complete list. - -Once the TODOs are all complete, the "Log Out" button _should_ work! - -As a team, split up the TODOs (see below) between the members of your team. - -There are TODOs in seven of the files. -Make sure each member has at least one TODO which they will be responsible for completing. -If your team prefers to work in pairs, that is fine too. Your individual branches -will not be graded for this — only the final, working version. - -The TODOs are summarized below (by file) to help your team decide how to split them up: - -* * * - -- `Main.java` - - - [ ] TODO: add the Logout Use Case to the app using the appBuilder - -* * * - -- `LoggedInView.java` (tip: refer to the other views for similar code) - - - [ ] TODO: save the logout controller in the instance variable. - - [ ] TODO: execute the logout use case through the Controller - -* * * - -- `LogoutController.java` (tip: refer to the other controllers for similar code) - - - [ ] TODO: Save the interactor in the instance variable. - - [ ] TODO: run the use case interactor for the logout use case - -* * * - -- `LogoutInputData.java` (should be done with the LogoutInteractor TODOs below) - - - [ ] TODO: save the current username in an instance variable and add a getter. - -- `LogoutInteractor.java` (tip: refer to `ChangePasswordInteractor.java` for similar code) - - - [ ] TODO: save the DAO and Presenter in the instance variables. - - [ ] TODO: implement the logic of the Logout Use Case - -* * * - -- `LogoutOutputData.java` - - - [ ] TODO: save the parameters in the instance variables. - -* * * - -- `LogoutPresenter.java` (tip: refer to `SignupPresenter.java` for similar code) - - - [ ] TODO: assign to the three instance variables. - - [ ] TODO: have prepareSuccessView update the LoggedInState - - [ ] TODO: have prepareSuccessView update the LoginState - -* * * - -1. Make a branch named the first part of your UofT email address, everything before the `@`. -For example, if your email address is `paul.gries@mail.utoronto.ca`, then the branch name would -be `paul.gries`. - -Make sure you switch to the new branch. - -In the terminal, this would look like below, but replaced with your own information: -``` -git branch paul.gries -git switch paul.gries -``` - -2. Complete your assigned TODOs and make a pull request on GitHub. In your pull request, - briefly describe what your TODOs were and how you implemented them. If you aren't sure - about part of it, include this in your pull request so everyone knows what to look - for when reviewing — or you can of course discuss with your team before making your - pull request since you are physically working in the same space. - - **Important: don't push any changes to the `.idea` folder, as that - may cause issues for your other teammates, as some files contain - configurations specific to your individual IntelliJ projects.** - -3. Review each other's pull requests to ensure each TODO is correctly implemented and that - there are no Checkstyle issues in the files that were modified. - -4. Once all TODOs are completed, your team should debug as needed to ensure the - correctness of the code. Setting a breakpoint where the log-out use case - interactor starts its work will likely be a great place to start when debugging. - -And that's it; you now have a working Logout Use Case! Instructions for -how to submit your work on MarkUs will be posted later. - -Your team should spend the rest of the lab working on your project blueprint. - -* * * - -# Project Blueprint - -See Quercus for details about the project blueprint! By the end of the week, -the goal is for your team to have a fully drafted blueprint so that your team -will be ready to get started on your project after Reading Week. From 708402bdd6ffe40a14ddab94e8c5e617d6017a1d Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 4 Nov 2024 14:04:28 -0500 Subject: [PATCH 002/148] created a basic recipe, ingredient, and nutrition class to build upon --- src/main/java/entity/Ingredient.java | 4 ++++ src/main/java/entity/Nutrition.java | 4 ++++ src/main/java/entity/Recipe.java | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 src/main/java/entity/Ingredient.java create mode 100644 src/main/java/entity/Nutrition.java create mode 100644 src/main/java/entity/Recipe.java diff --git a/src/main/java/entity/Ingredient.java b/src/main/java/entity/Ingredient.java new file mode 100644 index 000000000..4aac1a933 --- /dev/null +++ b/src/main/java/entity/Ingredient.java @@ -0,0 +1,4 @@ +package entity; + +public class Ingredient { +} diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java new file mode 100644 index 000000000..1922e6402 --- /dev/null +++ b/src/main/java/entity/Nutrition.java @@ -0,0 +1,4 @@ +package entity; + +public class Nutrition { +} diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java new file mode 100644 index 000000000..2e119e203 --- /dev/null +++ b/src/main/java/entity/Recipe.java @@ -0,0 +1,4 @@ +package entity; + +public class Recipe { +} From ff1cf7814be1e60470b74697ae75ca8b426d9248 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 4 Nov 2024 14:17:10 -0500 Subject: [PATCH 003/148] Use Case 1 Completed --- src/main/java/app/Main.java | 29 +++-------- src/main/java/data_access/RecipeDAO.java | 12 +++++ src/main/java/data_access/RecipeDAOImpl.java | 27 ++++++++++ src/main/java/entity/Ingredient.java | 13 +++++ src/main/java/entity/Recipe.java | 21 ++++++++ .../interface_adapter/RecipeController.java | 18 +++++++ .../java/use_case/SearchRecipeUseCase.java | 30 +++++++++++ src/main/java/view/RecipeView.java | 52 +++++++++++++++++++ 8 files changed, 181 insertions(+), 21 deletions(-) create mode 100644 src/main/java/data_access/RecipeDAO.java create mode 100644 src/main/java/data_access/RecipeDAOImpl.java create mode 100644 src/main/java/entity/Ingredient.java create mode 100644 src/main/java/entity/Recipe.java create mode 100644 src/main/java/interface_adapter/RecipeController.java create mode 100644 src/main/java/use_case/SearchRecipeUseCase.java create mode 100644 src/main/java/view/RecipeView.java diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index bef63ad7a..db631e255 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,28 +1,15 @@ package app; -import javax.swing.JFrame; +import data_access.RecipeDAOImpl; +import interface_adapter.RecipeController; +import use_case.SearchRecipeUseCase; +import view.RecipeView; -/** - * The Main class of our application. - */ public class Main { - /** - * Builds and runs the CA architecture of the application. - * @param args unused arguments - */ public static void main(String[] args) { - final AppBuilder appBuilder = new AppBuilder(); - // TODO: add the Logout Use Case to the app using the appBuilder - final JFrame application = appBuilder - .addLoginView() - .addSignupView() - .addLoggedInView() - .addSignupUseCase() - .addLoginUseCase() - .addChangePasswordUseCase() - .build(); - - application.pack(); - application.setVisible(true); + RecipeDAOImpl recipeDAO = new RecipeDAOImpl(); + SearchRecipeUseCase searchRecipeUseCase = new SearchRecipeUseCase(recipeDAO); + RecipeController controller = new RecipeController(searchRecipeUseCase); + new RecipeView(controller); } } diff --git a/src/main/java/data_access/RecipeDAO.java b/src/main/java/data_access/RecipeDAO.java new file mode 100644 index 000000000..91af1812d --- /dev/null +++ b/src/main/java/data_access/RecipeDAO.java @@ -0,0 +1,12 @@ +package data_access; + +import entity.Recipe; + +import java.util.List; + +public interface RecipeDAO { + List getRecipesByIngredients(List ingredients); + + List getAllRecipes(); +} + diff --git a/src/main/java/data_access/RecipeDAOImpl.java b/src/main/java/data_access/RecipeDAOImpl.java new file mode 100644 index 000000000..0c392c11e --- /dev/null +++ b/src/main/java/data_access/RecipeDAOImpl.java @@ -0,0 +1,27 @@ +package data_access; + +import entity.Recipe; + +import java.util.ArrayList; +import java.util.List; + +public class RecipeDAOImpl implements RecipeDAO { + @Override + public List getRecipesByIngredients(List ingredients) { + List recipes = new ArrayList<>(); + recipes.add(new Recipe("Pasta", "https://example.com/pasta", List.of("pasta", "tomato sauce"))); + recipes.add(new Recipe("Salad", "https://example.com/salad", List.of("lettuce", "tomato", "cucumber"))); + recipes.add(new Recipe("Tomato Soup", "https://example.com/tomato_soup", List.of("tomato", "onion", "garlic"))); + return recipes; + } + + @Override + public List getAllRecipes() { + // Mock data - replace this with actual data retrieval from a database or API + List recipes = new ArrayList<>(); + recipes.add(new Recipe("Pasta", "https://example.com/pasta", List.of("pasta", "tomato sauce"))); + recipes.add(new Recipe("Salad", "https://example.com/salad", List.of("lettuce", "tomato", "cucumber"))); + recipes.add(new Recipe("Tomato Soup", "https://example.com/tomato_soup", List.of("tomato", "onion", "garlic"))); + return recipes; + } +} diff --git a/src/main/java/entity/Ingredient.java b/src/main/java/entity/Ingredient.java new file mode 100644 index 000000000..9459b2966 --- /dev/null +++ b/src/main/java/entity/Ingredient.java @@ -0,0 +1,13 @@ +package entity; + +public class Ingredient { + private String name; + + public Ingredient(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java new file mode 100644 index 000000000..cc7ec769b --- /dev/null +++ b/src/main/java/entity/Recipe.java @@ -0,0 +1,21 @@ +package entity; + +import java.util.List; + +public class Recipe { + private String name; + private String url; + private List ingredients; + + public Recipe(String name, String url, List ingredients) { + this.name = name; + this.url = url; + this.ingredients = ingredients; + } + + public String getName() {return name;} + + public String getUrl() {return url;} + + public List getIngredients() {return ingredients; } +} diff --git a/src/main/java/interface_adapter/RecipeController.java b/src/main/java/interface_adapter/RecipeController.java new file mode 100644 index 000000000..011671020 --- /dev/null +++ b/src/main/java/interface_adapter/RecipeController.java @@ -0,0 +1,18 @@ +package interface_adapter; + +import entity.Recipe; +import use_case.SearchRecipeUseCase; + +import java.util.List; + +public class RecipeController { + private final SearchRecipeUseCase searchRecipeUseCase; + + public RecipeController(SearchRecipeUseCase searchRecipeUseCase) { + this.searchRecipeUseCase = searchRecipeUseCase; + } + + public List getRecipes(List ingredients) { + return searchRecipeUseCase.searchRecipes(ingredients); + } +} diff --git a/src/main/java/use_case/SearchRecipeUseCase.java b/src/main/java/use_case/SearchRecipeUseCase.java new file mode 100644 index 000000000..cc2590706 --- /dev/null +++ b/src/main/java/use_case/SearchRecipeUseCase.java @@ -0,0 +1,30 @@ +package use_case; + +import data_access.RecipeDAO; +import entity.Recipe; + +import java.util.ArrayList; +import java.util.List; + +public class SearchRecipeUseCase { + private final RecipeDAO recipeDAO; + + public SearchRecipeUseCase(RecipeDAO recipeDAO) { + this.recipeDAO = recipeDAO; + } + + public List searchRecipes(List ingredients) { + List allRecipes = recipeDAO.getAllRecipes(); + List filteredRecipes = new ArrayList<>(); + + for (Recipe recipe : allRecipes){ + for (String ingredient : ingredients){ + if (recipe.getIngredients().contains(ingredient.trim().toLowerCase())){ + filteredRecipes.add(recipe); + break; + } + } + } + return filteredRecipes; + } +} diff --git a/src/main/java/view/RecipeView.java b/src/main/java/view/RecipeView.java new file mode 100644 index 000000000..6ec4256a4 --- /dev/null +++ b/src/main/java/view/RecipeView.java @@ -0,0 +1,52 @@ +package view; + +import interface_adapter.RecipeController; +import entity.Recipe; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.List; + +public class RecipeView extends JFrame { + private JTextField ingredientInput; + private JButton searchButton; + private JList recipeList; + private RecipeController controller; + + public RecipeView(RecipeController controller) { + this.controller = controller; + setTitle("Recipe Generator"); + setSize(800, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + ingredientInput = new JTextField(20); + searchButton = new JButton("Find Recipes"); + recipeList = new JList<>(); + + searchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String ingredientsText = ingredientInput.getText(); + List ingredients = Arrays.asList(ingredientsText.split(",")); + List recipes = controller.getRecipes(ingredients); + + String[] recipeNames = recipes.stream() + .map(recipe -> recipe.getName() + " - " + recipe.getUrl()) + .toArray(String[]::new); + recipeList.setListData(recipeNames); + } + }); + + JPanel panel = new JPanel(); + panel.add(new JLabel("Enter ingredients (comma-separated):")); + panel.add(ingredientInput); + panel.add(searchButton); + add(panel, BorderLayout.NORTH); + add(new JScrollPane(recipeList), BorderLayout.CENTER); + + setVisible(true); + } +} From 7ce968b6775b90c1670524d3e64b0fec2ecf18ec Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 4 Nov 2024 14:18:45 -0500 Subject: [PATCH 004/148] Use Case 1 Completed --- .idea/compiler.xml | 13 +++++++++++++ .idea/encodings.xml | 7 +++++++ .idea/misc.xml | 12 ++++++++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ homework-5.iml | 11 +++++++++++ 6 files changed, 57 insertions(+) create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 homework-5.iml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..69d41be32 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..aa00ffab7 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..9e0563eb9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..48c5ce071 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/homework-5.iml b/homework-5.iml new file mode 100644 index 000000000..ffc2b2afe --- /dev/null +++ b/homework-5.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file From bf93b2b730eb2fe24ad68bd88152e383663b36e2 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 4 Nov 2024 14:29:17 -0500 Subject: [PATCH 005/148] getting the API to work --- pom.xml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e5c1e881f..64c4b6416 100644 --- a/pom.xml +++ b/pom.xml @@ -4,6 +4,22 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + + jitpack.io + https://jitpack.io + + + edu.csc207 homework-5 1.0-SNAPSHOT @@ -34,6 +50,12 @@ test + + com.github.MatthewWeisCaps + spoonacular-food-api-java + 1.0 + + @@ -44,4 +66,5 @@ UTF-8 - \ No newline at end of file + + From 23ad3cb851e91dc04eea2c100e06471554a4d628 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 4 Nov 2024 14:31:34 -0500 Subject: [PATCH 006/148] added Hongcheng to README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d15432b25..2a364c8eb 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,20 @@ ## Team Members -⁠Farshad Haddadi +Farshad Haddadi * GitHub username: farshad-haddadi -* Email:⁠⁠ farshad.haddadi@mail.utoronto.ca + +Hongcheng Huo +* GitHub username: hhcgoodluck Sung-chi (William) Wu -* GitHub username: ⁠⁠wusungch -* Email:⁠⁠ sungchi2024@outlook.com +* GitHub username: wusungch Xinyu (Cindy) Zhang * GitHub username: Cindyzzz616 -* Email: cxy.zhang16@gmail.com Shuxin (Kate) Zhou * GitHub username: kayzoo8 -* Email: katezhou2005@gmail.com ## User Stories From 53bcbc6e9166aa96a6a2a565ce0bcfd8962064c1 Mon Sep 17 00:00:00 2001 From: hhc <154641865+hhcgoodluck@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:37:24 -0500 Subject: [PATCH 007/148] Update README.md team member information --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d15432b25..bba02c7c1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ Shuxin (Kate) Zhou * GitHub username: kayzoo8 * Email: katezhou2005@gmail.com +Hongcheng (Morgan)Huo +* Github username: hhcgoodluck +* Email: hhcgoodluck@outlook.com ## User Stories * Member 1: The core feature of the program allows users to input ingredients they have on hand and receive recipe suggestions. Users can enter ingredients separated by commas, and the system will generate relevant recipes. If some ingredients are missing, the system can suggest close alternatives or highlight what else is needed. This feature helps users efficiently use the ingredients they already own, potentially reducing food waste and unnecessary shopping trips. From 2e027112a81498776ff7075289d6c51416e12f39 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 4 Nov 2024 14:39:34 -0500 Subject: [PATCH 008/148] Lab files deleted --- src/main/java/app/AppBuilder.java | 185 ---------------- .../data_access/DBUserDataAccessObject.java | 165 --------------- .../data_access/FileUserDataAccessObject.java | 117 ---------- .../InMemoryUserDataAccessObject.java | 55 ----- src/main/java/entity/CommonUser.java | 26 --- src/main/java/entity/CommonUserFactory.java | 12 -- src/main/java/entity/User.java | 20 -- src/main/java/entity/UserFactory.java | 15 -- .../interface_adapter/ViewManagerModel.java | 14 -- .../java/interface_adapter/ViewModel.java | 64 ------ .../ChangePasswordController.java | 26 --- .../ChangePasswordPresenter.java | 31 --- .../change_password/LoggedInState.java | 42 ---- .../change_password/LoggedInViewModel.java | 15 -- .../login/LoginController.java | 28 --- .../login/LoginPresenter.java | 45 ---- .../interface_adapter/login/LoginState.java | 35 --- .../login/LoginViewModel.java | 15 -- .../logout/LogoutController.java | 25 --- .../logout/LogoutPresenter.java | 54 ----- .../signup/SignupController.java | 36 ---- .../signup/SignupPresenter.java | 50 ----- .../interface_adapter/signup/SignupState.java | 70 ------ .../signup/SignupViewModel.java | 25 --- .../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 | 22 -- .../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 -- src/main/java/view/LabelTextPanel.java | 15 -- src/main/java/view/LoggedInView.java | 143 ------------- src/main/java/view/LoginView.java | 160 -------------- src/main/java/view/SignupView.java | 199 ------------------ src/main/java/view/ViewManager.java | 34 --- 53 files changed, 2273 deletions(-) delete mode 100644 src/main/java/app/AppBuilder.java delete mode 100644 src/main/java/data_access/DBUserDataAccessObject.java delete mode 100644 src/main/java/data_access/FileUserDataAccessObject.java delete mode 100644 src/main/java/data_access/InMemoryUserDataAccessObject.java delete mode 100644 src/main/java/entity/CommonUser.java delete mode 100644 src/main/java/entity/CommonUserFactory.java delete mode 100644 src/main/java/entity/User.java delete mode 100644 src/main/java/entity/UserFactory.java delete mode 100644 src/main/java/interface_adapter/ViewManagerModel.java delete mode 100644 src/main/java/interface_adapter/ViewModel.java delete mode 100644 src/main/java/interface_adapter/change_password/ChangePasswordController.java delete mode 100644 src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java delete mode 100644 src/main/java/interface_adapter/change_password/LoggedInState.java delete mode 100644 src/main/java/interface_adapter/change_password/LoggedInViewModel.java delete mode 100644 src/main/java/interface_adapter/login/LoginController.java delete mode 100644 src/main/java/interface_adapter/login/LoginPresenter.java delete mode 100644 src/main/java/interface_adapter/login/LoginState.java delete mode 100644 src/main/java/interface_adapter/login/LoginViewModel.java delete mode 100644 src/main/java/interface_adapter/logout/LogoutController.java delete mode 100644 src/main/java/interface_adapter/logout/LogoutPresenter.java delete mode 100644 src/main/java/interface_adapter/signup/SignupController.java delete mode 100644 src/main/java/interface_adapter/signup/SignupPresenter.java delete mode 100644 src/main/java/interface_adapter/signup/SignupState.java delete mode 100644 src/main/java/interface_adapter/signup/SignupViewModel.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordInputBoundary.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordInputData.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordInteractor.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordOutputData.java delete mode 100644 src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java delete mode 100644 src/main/java/use_case/login/LoginInputBoundary.java delete mode 100644 src/main/java/use_case/login/LoginInputData.java delete mode 100644 src/main/java/use_case/login/LoginInteractor.java delete mode 100644 src/main/java/use_case/login/LoginOutputBoundary.java delete mode 100644 src/main/java/use_case/login/LoginOutputData.java delete mode 100644 src/main/java/use_case/login/LoginUserDataAccessInterface.java delete mode 100644 src/main/java/use_case/logout/LogoutInputBoundary.java delete mode 100644 src/main/java/use_case/logout/LogoutInputData.java delete mode 100644 src/main/java/use_case/logout/LogoutInteractor.java delete mode 100644 src/main/java/use_case/logout/LogoutOutputBoundary.java delete mode 100644 src/main/java/use_case/logout/LogoutOutputData.java delete mode 100644 src/main/java/use_case/logout/LogoutUserDataAccessInterface.java delete mode 100644 src/main/java/use_case/signup/SignupInputBoundary.java delete mode 100644 src/main/java/use_case/signup/SignupInputData.java delete mode 100644 src/main/java/use_case/signup/SignupInteractor.java delete mode 100644 src/main/java/use_case/signup/SignupOutputBoundary.java delete mode 100644 src/main/java/use_case/signup/SignupOutputData.java delete mode 100644 src/main/java/use_case/signup/SignupUserDataAccessInterface.java delete mode 100644 src/main/java/view/LabelTextPanel.java delete mode 100644 src/main/java/view/LoggedInView.java delete mode 100644 src/main/java/view/LoginView.java delete mode 100644 src/main/java/view/SignupView.java delete mode 100644 src/main/java/view/ViewManager.java diff --git a/src/main/java/app/AppBuilder.java b/src/main/java/app/AppBuilder.java deleted file mode 100644 index e9eef5c81..000000000 --- a/src/main/java/app/AppBuilder.java +++ /dev/null @@ -1,185 +0,0 @@ -package app; - -import java.awt.CardLayout; - -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.WindowConstants; - -import data_access.InMemoryUserDataAccessObject; -import entity.CommonUserFactory; -import entity.UserFactory; -import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.ChangePasswordController; -import interface_adapter.change_password.ChangePasswordPresenter; -import interface_adapter.change_password.LoggedInViewModel; -import interface_adapter.login.LoginController; -import interface_adapter.login.LoginPresenter; -import interface_adapter.login.LoginViewModel; -import interface_adapter.logout.LogoutController; -import interface_adapter.logout.LogoutPresenter; -import interface_adapter.signup.SignupController; -import interface_adapter.signup.SignupPresenter; -import interface_adapter.signup.SignupViewModel; -import use_case.change_password.ChangePasswordInputBoundary; -import use_case.change_password.ChangePasswordInteractor; -import use_case.change_password.ChangePasswordOutputBoundary; -import use_case.login.LoginInputBoundary; -import use_case.login.LoginInteractor; -import use_case.login.LoginOutputBoundary; -import use_case.logout.LogoutInputBoundary; -import use_case.logout.LogoutInteractor; -import use_case.logout.LogoutOutputBoundary; -import use_case.signup.SignupInputBoundary; -import use_case.signup.SignupInteractor; -import use_case.signup.SignupOutputBoundary; -import view.LoggedInView; -import view.LoginView; -import view.SignupView; -import view.ViewManager; - -/** - * The AppBuilder class is responsible for putting together the pieces of - * our CA architecture; piece by piece. - *

- * This is done by adding each View and then adding related Use Cases. - */ -// Checkstyle note: you can ignore the "Class Data Abstraction Coupling" -// and the "Class Fan-Out Complexity" issues for this lab; we encourage -// your team to think about ways to refactor the code to resolve these -// if your team decides to work with this as your starter code -// for your final project this term. -public class AppBuilder { - private final JPanel cardPanel = new JPanel(); - private final CardLayout cardLayout = new CardLayout(); - // thought question: is the hard dependency below a problem? - private final UserFactory userFactory = new CommonUserFactory(); - private final ViewManagerModel viewManagerModel = new ViewManagerModel(); - private final ViewManager viewManager = new ViewManager(cardPanel, cardLayout, viewManagerModel); - - // thought question: is the hard dependency below a problem? - private final InMemoryUserDataAccessObject userDataAccessObject = new InMemoryUserDataAccessObject(); - - private SignupView signupView; - private SignupViewModel signupViewModel; - private LoginViewModel loginViewModel; - private LoggedInViewModel loggedInViewModel; - private LoggedInView loggedInView; - private LoginView loginView; - - public AppBuilder() { - cardPanel.setLayout(cardLayout); - } - - /** - * Adds the Signup View to the application. - * @return this builder - */ - public AppBuilder addSignupView() { - signupViewModel = new SignupViewModel(); - signupView = new SignupView(signupViewModel); - cardPanel.add(signupView, signupView.getViewName()); - return this; - } - - /** - * Adds the Login View to the application. - * @return this builder - */ - public AppBuilder addLoginView() { - loginViewModel = new LoginViewModel(); - loginView = new LoginView(loginViewModel); - cardPanel.add(loginView, loginView.getViewName()); - return this; - } - - /** - * Adds the LoggedIn View to the application. - * @return this builder - */ - public AppBuilder addLoggedInView() { - loggedInViewModel = new LoggedInViewModel(); - loggedInView = new LoggedInView(loggedInViewModel); - cardPanel.add(loggedInView, loggedInView.getViewName()); - return this; - } - - /** - * Adds the Signup Use Case to the application. - * @return this builder - */ - public AppBuilder addSignupUseCase() { - final SignupOutputBoundary signupOutputBoundary = new SignupPresenter(viewManagerModel, - signupViewModel, loginViewModel); - final SignupInputBoundary userSignupInteractor = new SignupInteractor( - userDataAccessObject, signupOutputBoundary, userFactory); - - final SignupController controller = new SignupController(userSignupInteractor); - signupView.setSignupController(controller); - return this; - } - - /** - * Adds the Login Use Case to the application. - * @return this builder - */ - public AppBuilder addLoginUseCase() { - final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, - loggedInViewModel, loginViewModel); - final LoginInputBoundary loginInteractor = new LoginInteractor( - userDataAccessObject, loginOutputBoundary); - - final LoginController loginController = new LoginController(loginInteractor); - loginView.setLoginController(loginController); - return this; - } - - /** - * Adds the Change Password Use Case to the application. - * @return this builder - */ - public AppBuilder addChangePasswordUseCase() { - final ChangePasswordOutputBoundary changePasswordOutputBoundary = - new ChangePasswordPresenter(loggedInViewModel); - - final ChangePasswordInputBoundary changePasswordInteractor = - new ChangePasswordInteractor(userDataAccessObject, changePasswordOutputBoundary, userFactory); - - final ChangePasswordController changePasswordController = - new ChangePasswordController(changePasswordInteractor); - loggedInView.setChangePasswordController(changePasswordController); - return this; - } - - /** - * Adds the Logout Use Case to the application. - * @return this builder - */ - public AppBuilder addLogoutUseCase() { - final LogoutOutputBoundary logoutOutputBoundary = new LogoutPresenter(viewManagerModel, - loggedInViewModel, loginViewModel); - - final LogoutInputBoundary logoutInteractor = - new LogoutInteractor(userDataAccessObject, logoutOutputBoundary); - - final LogoutController logoutController = new LogoutController(logoutInteractor); - loggedInView.setLogoutController(logoutController); - return this; - } - - /** - * Creates the JFrame for the application and initially sets the SignupView to be displayed. - * @return the application - */ - public JFrame build() { - final JFrame application = new JFrame("Login Example"); - application.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - - application.add(cardPanel); - - viewManagerModel.setState(signupView.getViewName()); - viewManagerModel.firePropertyChanged(); - - return application; - } -} diff --git a/src/main/java/data_access/DBUserDataAccessObject.java b/src/main/java/data_access/DBUserDataAccessObject.java deleted file mode 100644 index 377ee6e7e..000000000 --- a/src/main/java/data_access/DBUserDataAccessObject.java +++ /dev/null @@ -1,165 +0,0 @@ -package data_access; - -import java.io.IOException; - -import org.json.JSONException; -import org.json.JSONObject; - -import entity.User; -import entity.UserFactory; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.logout.LogoutUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; - -/** - * The DAO for user data. - */ -public class DBUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface, - LogoutUserDataAccessInterface { - private static final int SUCCESS_CODE = 200; - private static final String CONTENT_TYPE_LABEL = "Content-Type"; - private static final String CONTENT_TYPE_JSON = "application/json"; - private static final String STATUS_CODE_LABEL = "status_code"; - private static final String USERNAME = "username"; - private static final String PASSWORD = "password"; - private static final String MESSAGE = "message"; - private final UserFactory userFactory; - - public DBUserDataAccessObject(UserFactory userFactory) { - this.userFactory = userFactory; - // No need to do anything to reinitialize a user list! The data is the cloud that may be miles away. - } - - @Override - public User get(String username) { - // Make an API call to get the user object. - final OkHttpClient client = new OkHttpClient().newBuilder().build(); - final Request request = new Request.Builder() - .url(String.format("http://vm003.teach.cs.toronto.edu:20112/user?username=%s", username)) - .addHeader("Content-Type", CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - final JSONObject userJSONObject = responseBody.getJSONObject("user"); - final String name = userJSONObject.getString(USERNAME); - final String password = userJSONObject.getString(PASSWORD); - - return userFactory.create(name, password); - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void setCurrentUsername(String name) { - // this isn't implemented for the lab - } - - @Override - public boolean existsByName(String username) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - final Request request = new Request.Builder() - .url(String.format("http://vm003.teach.cs.toronto.edu:20112/checkIfUserExists?username=%s", username)) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - return responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE; - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void save(User user) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - - // POST METHOD - final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON); - final JSONObject requestBody = new JSONObject(); - requestBody.put(USERNAME, user.getName()); - requestBody.put(PASSWORD, user.getPassword()); - final RequestBody body = RequestBody.create(requestBody.toString(), mediaType); - final Request request = new Request.Builder() - .url("http://vm003.teach.cs.toronto.edu:20112/user") - .method("POST", body) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - // success! - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void changePassword(User user) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - - // POST METHOD - final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON); - final JSONObject requestBody = new JSONObject(); - requestBody.put(USERNAME, user.getName()); - requestBody.put(PASSWORD, user.getPassword()); - final RequestBody body = RequestBody.create(requestBody.toString(), mediaType); - final Request request = new Request.Builder() - .url("http://vm003.teach.cs.toronto.edu:20112/user") - .method("PUT", body) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - // success! - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public String getCurrentUsername() { - return null; - } -} diff --git a/src/main/java/data_access/FileUserDataAccessObject.java b/src/main/java/data_access/FileUserDataAccessObject.java deleted file mode 100644 index d301a3241..000000000 --- a/src/main/java/data_access/FileUserDataAccessObject.java +++ /dev/null @@ -1,117 +0,0 @@ -package data_access; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import entity.User; -import entity.UserFactory; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; - -/** - * DAO for user data implemented using a File to persist the data. - */ -public class FileUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface { - - private static final String HEADER = "username,password"; - - private final File csvFile; - private final Map headers = new LinkedHashMap<>(); - private final Map accounts = new HashMap<>(); - private String currentUsername; - - public FileUserDataAccessObject(String csvPath, UserFactory userFactory) throws IOException { - - csvFile = new File(csvPath); - headers.put("username", 0); - headers.put("password", 1); - - if (csvFile.length() == 0) { - save(); - } - else { - - try (BufferedReader reader = new BufferedReader(new FileReader(csvFile))) { - final String header = reader.readLine(); - - if (!header.equals(HEADER)) { - throw new RuntimeException(String.format("header should be%n: %s%but was:%n%s", HEADER, header)); - } - - String row; - while ((row = reader.readLine()) != null) { - final String[] col = row.split(","); - final String username = String.valueOf(col[headers.get("username")]); - final String password = String.valueOf(col[headers.get("password")]); - final User user = userFactory.create(username, password); - accounts.put(username, user); - } - } - } - } - - private void save() { - final BufferedWriter writer; - try { - writer = new BufferedWriter(new FileWriter(csvFile)); - writer.write(String.join(",", headers.keySet())); - writer.newLine(); - - for (User user : accounts.values()) { - final String line = String.format("%s,%s", - user.getName(), user.getPassword()); - writer.write(line); - writer.newLine(); - } - - writer.close(); - - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void save(User user) { - accounts.put(user.getName(), user); - this.save(); - } - - @Override - public User get(String username) { - return accounts.get(username); - } - - @Override - public void setCurrentUsername(String name) { - this.currentUsername = name; - } - - @Override - public String getCurrentUsername() { - return this.currentUsername; - } - - @Override - public boolean existsByName(String identifier) { - return accounts.containsKey(identifier); - } - - @Override - public void changePassword(User user) { - // Replace the User object in the map - accounts.put(user.getName(), user); - save(); - } -} diff --git a/src/main/java/data_access/InMemoryUserDataAccessObject.java b/src/main/java/data_access/InMemoryUserDataAccessObject.java deleted file mode 100644 index 71f00862c..000000000 --- a/src/main/java/data_access/InMemoryUserDataAccessObject.java +++ /dev/null @@ -1,55 +0,0 @@ -package data_access; - -import java.util.HashMap; -import java.util.Map; - -import entity.User; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.logout.LogoutUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; - -/** - * In-memory implementation of the DAO for storing user data. This implementation does - * NOT persist data between runs of the program. - */ -public class InMemoryUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface, - LogoutUserDataAccessInterface { - - private final Map users = new HashMap<>(); - - private String currentUsername; - - @Override - public boolean existsByName(String identifier) { - return users.containsKey(identifier); - } - - @Override - public void save(User user) { - users.put(user.getName(), user); - } - - @Override - public User get(String username) { - return users.get(username); - } - - @Override - public void changePassword(User user) { - // Replace the old entry with the new password - users.put(user.getName(), user); - } - - @Override - public void setCurrentUsername(String name) { - this.currentUsername = name; - } - - @Override - public String getCurrentUsername() { - return this.currentUsername; - } -} diff --git a/src/main/java/entity/CommonUser.java b/src/main/java/entity/CommonUser.java deleted file mode 100644 index ba25fd20a..000000000 --- a/src/main/java/entity/CommonUser.java +++ /dev/null @@ -1,26 +0,0 @@ -package entity; - -/** - * A simple implementation of the User interface. - */ -public class CommonUser implements User { - - private final String name; - private final String password; - - public CommonUser(String name, String password) { - this.name = name; - this.password = password; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getPassword() { - return password; - } - -} diff --git a/src/main/java/entity/CommonUserFactory.java b/src/main/java/entity/CommonUserFactory.java deleted file mode 100644 index ebede69e3..000000000 --- a/src/main/java/entity/CommonUserFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package entity; - -/** - * Factory for creating CommonUser objects. - */ -public class CommonUserFactory implements UserFactory { - - @Override - public User create(String name, String password) { - return new CommonUser(name, password); - } -} diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java deleted file mode 100644 index 0ad073902..000000000 --- a/src/main/java/entity/User.java +++ /dev/null @@ -1,20 +0,0 @@ -package entity; - -/** - * The representation of a user in our program. - */ -public interface User { - - /** - * Returns the username of the user. - * @return the username of the user. - */ - String getName(); - - /** - * Returns the password of the user. - * @return the password of the user. - */ - String getPassword(); - -} diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java deleted file mode 100644 index c7a508708..000000000 --- a/src/main/java/entity/UserFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package entity; - -/** - * Factory for creating users. - */ -public interface 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 - */ - User create(String name, String password); - -} diff --git a/src/main/java/interface_adapter/ViewManagerModel.java b/src/main/java/interface_adapter/ViewManagerModel.java deleted file mode 100644 index 99dc0ffe5..000000000 --- a/src/main/java/interface_adapter/ViewManagerModel.java +++ /dev/null @@ -1,14 +0,0 @@ -package interface_adapter; - -/** - * Model for the View Manager. Its state is the name of the View which - * is currently active. An initial state of "" is used. - */ -public class ViewManagerModel extends ViewModel { - - public ViewManagerModel() { - super("view manager"); - this.setState(""); - } - -} diff --git a/src/main/java/interface_adapter/ViewModel.java b/src/main/java/interface_adapter/ViewModel.java deleted file mode 100644 index bc2dea65b..000000000 --- a/src/main/java/interface_adapter/ViewModel.java +++ /dev/null @@ -1,64 +0,0 @@ -package interface_adapter; - -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; - -/** - * The ViewModel for our CA implementation. - * This class delegates work to a PropertyChangeSupport object for - * managing the property change events. - * - * @param The type of state object contained in the model. - */ -public class ViewModel { - - private final String viewName; - - private final PropertyChangeSupport support = new PropertyChangeSupport(this); - - private T state; - - public ViewModel(String viewName) { - this.viewName = viewName; - } - - public String getViewName() { - return this.viewName; - } - - public T getState() { - return this.state; - } - - public void setState(T state) { - this.state = state; - } - - /** - * Fires a property changed event for the state of this ViewModel. - */ - public void firePropertyChanged() { - this.support.firePropertyChange("state", null, this.state); - } - - /** - * Fires a property changed event for the state of this ViewModel, which - * allows the user to specify a different propertyName. This can be useful - * when a class is listening for multiple kinds of property changes. - *

- * For example, the LoggedInView listens for two kinds of property changes; - * it can use the property name to distinguish which property has changed. - * @param propertyName the label for the property that was changed - */ - public void firePropertyChanged(String propertyName) { - this.support.firePropertyChange(propertyName, null, this.state); - } - - /** - * Adds a PropertyChangeListener to this ViewModel. - * @param listener The PropertyChangeListener to be added - */ - public void addPropertyChangeListener(PropertyChangeListener listener) { - this.support.addPropertyChangeListener(listener); - } -} diff --git a/src/main/java/interface_adapter/change_password/ChangePasswordController.java b/src/main/java/interface_adapter/change_password/ChangePasswordController.java deleted file mode 100644 index a7abd075f..000000000 --- a/src/main/java/interface_adapter/change_password/ChangePasswordController.java +++ /dev/null @@ -1,26 +0,0 @@ -package interface_adapter.change_password; - -import use_case.change_password.ChangePasswordInputBoundary; -import use_case.change_password.ChangePasswordInputData; - -/** - * Controller for the Change Password Use Case. - */ -public class ChangePasswordController { - private final ChangePasswordInputBoundary userChangePasswordUseCaseInteractor; - - public ChangePasswordController(ChangePasswordInputBoundary userChangePasswordUseCaseInteractor) { - this.userChangePasswordUseCaseInteractor = userChangePasswordUseCaseInteractor; - } - - /** - * Executes the Change Password Use Case. - * @param password the new password - * @param username the user whose password to change - */ - public void execute(String password, String username) { - final ChangePasswordInputData changePasswordInputData = new ChangePasswordInputData(username, password); - - userChangePasswordUseCaseInteractor.execute(changePasswordInputData); - } -} diff --git a/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java b/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java deleted file mode 100644 index 3efca4e46..000000000 --- a/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java +++ /dev/null @@ -1,31 +0,0 @@ -package interface_adapter.change_password; - -import use_case.change_password.ChangePasswordOutputBoundary; -import use_case.change_password.ChangePasswordOutputData; - -/** - * The Presenter for the Change Password Use Case. - */ -public class ChangePasswordPresenter implements ChangePasswordOutputBoundary { - - private final LoggedInViewModel loggedInViewModel; - - public ChangePasswordPresenter(LoggedInViewModel loggedInViewModel) { - this.loggedInViewModel = loggedInViewModel; - } - - @Override - public void prepareSuccessView(ChangePasswordOutputData outputData) { - // currently there isn't anything to change based on the output data, - // since the output data only contains the username, which remains the same. - // We still fire the property changed event, but just to let the view know that - // it can alert the user that their password was changed successfully.. - loggedInViewModel.firePropertyChanged("password"); - - } - - @Override - public void prepareFailView(String error) { - // note: this use case currently can't fail - } -} diff --git a/src/main/java/interface_adapter/change_password/LoggedInState.java b/src/main/java/interface_adapter/change_password/LoggedInState.java deleted file mode 100644 index 54ed80537..000000000 --- a/src/main/java/interface_adapter/change_password/LoggedInState.java +++ /dev/null @@ -1,42 +0,0 @@ -package interface_adapter.change_password; - -/** - * The State information representing the logged-in user. - */ -public class LoggedInState { - private String username = ""; - - private String password = ""; - private String passwordError; - - public LoggedInState(LoggedInState copy) { - username = copy.username; - password = copy.password; - passwordError = copy.passwordError; - } - - // Because of the previous copy constructor, the default constructor must be explicit. - public LoggedInState() { - - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; - } - - public String getPassword() { - return password; - } -} diff --git a/src/main/java/interface_adapter/change_password/LoggedInViewModel.java b/src/main/java/interface_adapter/change_password/LoggedInViewModel.java deleted file mode 100644 index 85c81e4e6..000000000 --- a/src/main/java/interface_adapter/change_password/LoggedInViewModel.java +++ /dev/null @@ -1,15 +0,0 @@ -package interface_adapter.change_password; - -import interface_adapter.ViewModel; - -/** - * The View Model for the Logged In View. - */ -public class LoggedInViewModel extends ViewModel { - - public LoggedInViewModel() { - super("logged in"); - setState(new LoggedInState()); - } - -} diff --git a/src/main/java/interface_adapter/login/LoginController.java b/src/main/java/interface_adapter/login/LoginController.java deleted file mode 100644 index 57e950666..000000000 --- a/src/main/java/interface_adapter/login/LoginController.java +++ /dev/null @@ -1,28 +0,0 @@ -package interface_adapter.login; - -import use_case.login.LoginInputBoundary; -import use_case.login.LoginInputData; - -/** - * The controller for the Login Use Case. - */ -public class LoginController { - - private final LoginInputBoundary loginUseCaseInteractor; - - public LoginController(LoginInputBoundary loginUseCaseInteractor) { - this.loginUseCaseInteractor = loginUseCaseInteractor; - } - - /** - * Executes the Login Use Case. - * @param username the username of the user logging in - * @param password the password of the user logging in - */ - public void execute(String username, String password) { - final LoginInputData loginInputData = new LoginInputData( - username, password); - - loginUseCaseInteractor.execute(loginInputData); - } -} diff --git a/src/main/java/interface_adapter/login/LoginPresenter.java b/src/main/java/interface_adapter/login/LoginPresenter.java deleted file mode 100644 index 66560d51a..000000000 --- a/src/main/java/interface_adapter/login/LoginPresenter.java +++ /dev/null @@ -1,45 +0,0 @@ -package interface_adapter.login; - -import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.LoggedInState; -import interface_adapter.change_password.LoggedInViewModel; -import use_case.login.LoginOutputBoundary; -import use_case.login.LoginOutputData; - -/** - * The Presenter for the Login Use Case. - */ -public class LoginPresenter implements LoginOutputBoundary { - - private final LoginViewModel loginViewModel; - private final LoggedInViewModel loggedInViewModel; - private final ViewManagerModel viewManagerModel; - - public LoginPresenter(ViewManagerModel viewManagerModel, - LoggedInViewModel loggedInViewModel, - LoginViewModel loginViewModel) { - this.viewManagerModel = viewManagerModel; - this.loggedInViewModel = loggedInViewModel; - this.loginViewModel = loginViewModel; - } - - @Override - public void prepareSuccessView(LoginOutputData response) { - // On success, switch to the logged in view. - - final LoggedInState loggedInState = loggedInViewModel.getState(); - loggedInState.setUsername(response.getUsername()); - this.loggedInViewModel.setState(loggedInState); - this.loggedInViewModel.firePropertyChanged(); - - this.viewManagerModel.setState(loggedInViewModel.getViewName()); - this.viewManagerModel.firePropertyChanged(); - } - - @Override - public void prepareFailView(String error) { - final LoginState loginState = loginViewModel.getState(); - loginState.setLoginError(error); - loginViewModel.firePropertyChanged(); - } -} diff --git a/src/main/java/interface_adapter/login/LoginState.java b/src/main/java/interface_adapter/login/LoginState.java deleted file mode 100644 index e42632034..000000000 --- a/src/main/java/interface_adapter/login/LoginState.java +++ /dev/null @@ -1,35 +0,0 @@ -package interface_adapter.login; - -/** - * The state for the Login View Model. - */ -public class LoginState { - private String username = ""; - private String loginError; - private String password = ""; - - public String getUsername() { - return username; - } - - public String getLoginError() { - return loginError; - } - - public String getPassword() { - return password; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setLoginError(String usernameError) { - this.loginError = usernameError; - } - - public void setPassword(String password) { - this.password = password; - } - -} diff --git a/src/main/java/interface_adapter/login/LoginViewModel.java b/src/main/java/interface_adapter/login/LoginViewModel.java deleted file mode 100644 index 41bea7a2e..000000000 --- a/src/main/java/interface_adapter/login/LoginViewModel.java +++ /dev/null @@ -1,15 +0,0 @@ -package interface_adapter.login; - -import interface_adapter.ViewModel; - -/** - * The View Model for the Login View. - */ -public class LoginViewModel extends ViewModel { - - public LoginViewModel() { - super("log in"); - setState(new LoginState()); - } - -} diff --git a/src/main/java/interface_adapter/logout/LogoutController.java b/src/main/java/interface_adapter/logout/LogoutController.java deleted file mode 100644 index e184a3bba..000000000 --- a/src/main/java/interface_adapter/logout/LogoutController.java +++ /dev/null @@ -1,25 +0,0 @@ -package interface_adapter.logout; - -import use_case.logout.LogoutInputBoundary; - -/** - * The controller for the Logout Use Case. - */ -public class LogoutController { - - private LogoutInputBoundary logoutUseCaseInteractor; - - public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { - // TODO: Save the interactor in the instance variable. - } - - /** - * Executes the Logout Use Case. - * @param username the username of the user logging in - */ - public void execute(String username) { - // TODO: run the use case interactor for the logout use case - // 1. instantiate the `LogoutInputData`, which should contain the username. - // 2. tell the Interactor to execute. - } -} diff --git a/src/main/java/interface_adapter/logout/LogoutPresenter.java b/src/main/java/interface_adapter/logout/LogoutPresenter.java deleted file mode 100644 index 78ef306a1..000000000 --- a/src/main/java/interface_adapter/logout/LogoutPresenter.java +++ /dev/null @@ -1,54 +0,0 @@ -package interface_adapter.logout; - -import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.LoggedInViewModel; -import interface_adapter.login.LoginViewModel; -import use_case.logout.LogoutOutputBoundary; -import use_case.logout.LogoutOutputData; - -/** - * The Presenter for the Logout Use Case. - */ -public class LogoutPresenter implements LogoutOutputBoundary { - - private LoggedInViewModel loggedInViewModel; - private ViewManagerModel viewManagerModel; - private LoginViewModel loginViewModel; - - public LogoutPresenter(ViewManagerModel viewManagerModel, - LoggedInViewModel loggedInViewModel, - LoginViewModel loginViewModel) { - // TODO: assign to the three instance variables. - } - - @Override - public void prepareSuccessView(LogoutOutputData response) { - // We need to switch to the login view, which should have - // an empty username and password. - - // We also need to set the username in the LoggedInState to - // the empty string. - - // TODO: have prepareSuccessView update the LoggedInState - // 1. get the LoggedInState out of the appropriate View Model, - // 2. set the username in the state to the empty string - // 3. set the state in the LoggedInViewModel to the updated state - // 4. firePropertyChanged so that the View that is listening is updated. - - // TODO: have prepareSuccessView update the LoginState - // 5. get the LoginState out of the appropriate View Model, - // 6. set the username and password in the state to the empty string - // 7. set the state in the LoginViewModel to the updated state - // 8. firePropertyChanged so that the View that is listening is updated. - - // This code tells the View Manager to switch to the LoginView. - this.viewManagerModel.setState(loginViewModel.getViewName()); - this.viewManagerModel.firePropertyChanged(); - } - - @Override - public void prepareFailView(String error) { - // No need to add code here. We'll assume that logout can't fail. - // Thought question: is this a reasonable assumption? - } -} diff --git a/src/main/java/interface_adapter/signup/SignupController.java b/src/main/java/interface_adapter/signup/SignupController.java deleted file mode 100644 index c01d25aa5..000000000 --- a/src/main/java/interface_adapter/signup/SignupController.java +++ /dev/null @@ -1,36 +0,0 @@ -package interface_adapter.signup; - -import use_case.signup.SignupInputBoundary; -import use_case.signup.SignupInputData; - -/** - * Controller for the Signup Use Case. - */ -public class SignupController { - - private final SignupInputBoundary userSignupUseCaseInteractor; - - public SignupController(SignupInputBoundary userSignupUseCaseInteractor) { - this.userSignupUseCaseInteractor = userSignupUseCaseInteractor; - } - - /** - * Executes the Signup Use Case. - * @param username the username to sign up - * @param password1 the password - * @param password2 the password repeated - */ - public void execute(String username, String password1, String password2) { - final SignupInputData signupInputData = new SignupInputData( - username, password1, password2); - - userSignupUseCaseInteractor.execute(signupInputData); - } - - /** - * Executes the "switch to LoginView" Use Case. - */ - public void switchToLoginView() { - userSignupUseCaseInteractor.switchToLoginView(); - } -} diff --git a/src/main/java/interface_adapter/signup/SignupPresenter.java b/src/main/java/interface_adapter/signup/SignupPresenter.java deleted file mode 100644 index 9b654077e..000000000 --- a/src/main/java/interface_adapter/signup/SignupPresenter.java +++ /dev/null @@ -1,50 +0,0 @@ -package interface_adapter.signup; - -import interface_adapter.ViewManagerModel; -import interface_adapter.login.LoginState; -import interface_adapter.login.LoginViewModel; -import use_case.signup.SignupOutputBoundary; -import use_case.signup.SignupOutputData; - -/** - * The Presenter for the Signup Use Case. - */ -public class SignupPresenter implements SignupOutputBoundary { - - private final SignupViewModel signupViewModel; - private final LoginViewModel loginViewModel; - private final ViewManagerModel viewManagerModel; - - public SignupPresenter(ViewManagerModel viewManagerModel, - SignupViewModel signupViewModel, - LoginViewModel loginViewModel) { - this.viewManagerModel = viewManagerModel; - this.signupViewModel = signupViewModel; - this.loginViewModel = loginViewModel; - } - - @Override - public void prepareSuccessView(SignupOutputData response) { - // On success, switch to the login view. - final LoginState loginState = loginViewModel.getState(); - loginState.setUsername(response.getUsername()); - this.loginViewModel.setState(loginState); - loginViewModel.firePropertyChanged(); - - viewManagerModel.setState(loginViewModel.getViewName()); - viewManagerModel.firePropertyChanged(); - } - - @Override - public void prepareFailView(String error) { - final SignupState signupState = signupViewModel.getState(); - signupState.setUsernameError(error); - signupViewModel.firePropertyChanged(); - } - - @Override - public void switchToLoginView() { - viewManagerModel.setState(loginViewModel.getViewName()); - viewManagerModel.firePropertyChanged(); - } -} diff --git a/src/main/java/interface_adapter/signup/SignupState.java b/src/main/java/interface_adapter/signup/SignupState.java deleted file mode 100644 index 4a7f69327..000000000 --- a/src/main/java/interface_adapter/signup/SignupState.java +++ /dev/null @@ -1,70 +0,0 @@ -package interface_adapter.signup; - -/** - * The state for the Signup View Model. - */ -public class SignupState { - private String username = ""; - private String usernameError; - private String password = ""; - private String passwordError; - private String repeatPassword = ""; - private String repeatPasswordError; - - public String getUsername() { - return username; - } - - public String getUsernameError() { - return usernameError; - } - - public String getPassword() { - return password; - } - - public String getPasswordError() { - return passwordError; - } - - public String getRepeatPassword() { - return repeatPassword; - } - - public String getRepeatPasswordError() { - return repeatPasswordError; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setUsernameError(String usernameError) { - this.usernameError = usernameError; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; - } - - public void setRepeatPassword(String repeatPassword) { - this.repeatPassword = repeatPassword; - } - - public void setRepeatPasswordError(String repeatPasswordError) { - this.repeatPasswordError = repeatPasswordError; - } - - @Override - public String toString() { - return "SignupState{" - + "username='" + username + '\'' - + ", password='" + password + '\'' - + ", repeatPassword='" + repeatPassword + '\'' - + '}'; - } -} diff --git a/src/main/java/interface_adapter/signup/SignupViewModel.java b/src/main/java/interface_adapter/signup/SignupViewModel.java deleted file mode 100644 index 01f0086bc..000000000 --- a/src/main/java/interface_adapter/signup/SignupViewModel.java +++ /dev/null @@ -1,25 +0,0 @@ -package interface_adapter.signup; - -import interface_adapter.ViewModel; - -/** - * The ViewModel for the Signup View. - */ -public class SignupViewModel extends ViewModel { - - public static final String TITLE_LABEL = "Sign Up View"; - public static final String USERNAME_LABEL = "Choose username"; - public static final String PASSWORD_LABEL = "Choose password"; - public static final String REPEAT_PASSWORD_LABEL = "Enter password again"; - - public static final String SIGNUP_BUTTON_LABEL = "Sign up"; - public static final String CANCEL_BUTTON_LABEL = "Cancel"; - - public static final String TO_LOGIN_BUTTON_LABEL = "Go to Login"; - - public SignupViewModel() { - super("sign up"); - setState(new SignupState()); - } - -} diff --git a/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java b/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java deleted file mode 100644 index 06ae9448e..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 8e09d8d12..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInputData.java +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index df91196c1..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInteractor.java +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index fce28367b..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index b47b83fbc..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordOutputData.java +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 6b73ab6b0..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index faf72dc96..000000000 --- a/src/main/java/use_case/login/LoginInputBoundary.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 363316832..000000000 --- a/src/main/java/use_case/login/LoginInputData.java +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 5b36ddcd8..000000000 --- a/src/main/java/use_case/login/LoginInteractor.java +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 08bc4731f..000000000 --- a/src/main/java/use_case/login/LoginOutputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 3ea119a8f..000000000 --- a/src/main/java/use_case/login/LoginOutputData.java +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 681e8a52e..000000000 --- a/src/main/java/use_case/login/LoginUserDataAccessInterface.java +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 189f50168..000000000 --- a/src/main/java/use_case/logout/LogoutInputBoundary.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 56a33b375..000000000 --- a/src/main/java/use_case/logout/LogoutInputData.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 1ca93b44e..000000000 --- a/src/main/java/use_case/logout/LogoutInteractor.java +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 935a06bdc..000000000 --- a/src/main/java/use_case/logout/LogoutOutputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 974279155..000000000 --- a/src/main/java/use_case/logout/LogoutOutputData.java +++ /dev/null @@ -1,22 +0,0 @@ -package use_case.logout; - -/** - * Output Data for the Logout Use Case. - */ -public class LogoutOutputData { - - private String username; - private boolean useCaseFailed; - - public LogoutOutputData(String username, boolean useCaseFailed) { - // TODO: save the parameters in the instance variables. - } - - 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 deleted file mode 100644 index 8263700e2..000000000 --- a/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 1cb69e02e..000000000 --- a/src/main/java/use_case/signup/SignupInputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 86c5e8abc..000000000 --- a/src/main/java/use_case/signup/SignupInputData.java +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 3fd6560c7..000000000 --- a/src/main/java/use_case/signup/SignupInteractor.java +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index 314376b93..000000000 --- a/src/main/java/use_case/signup/SignupOutputBoundary.java +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 6dc74d2fb..000000000 --- a/src/main/java/use_case/signup/SignupOutputData.java +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index b9d60f585..000000000 --- a/src/main/java/use_case/signup/SignupUserDataAccessInterface.java +++ /dev/null @@ -1,22 +0,0 @@ -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); -} diff --git a/src/main/java/view/LabelTextPanel.java b/src/main/java/view/LabelTextPanel.java deleted file mode 100644 index e7138555f..000000000 --- a/src/main/java/view/LabelTextPanel.java +++ /dev/null @@ -1,15 +0,0 @@ -package view; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextField; - -/** - * A panel containing a label and a text field. - */ -class LabelTextPanel extends JPanel { - LabelTextPanel(JLabel label, JTextField textField) { - this.add(label); - this.add(textField); - } -} diff --git a/src/main/java/view/LoggedInView.java b/src/main/java/view/LoggedInView.java deleted file mode 100644 index 76d40c0f1..000000000 --- a/src/main/java/view/LoggedInView.java +++ /dev/null @@ -1,143 +0,0 @@ -package view; - -import java.awt.Component; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import interface_adapter.change_password.ChangePasswordController; -import interface_adapter.change_password.LoggedInState; -import interface_adapter.change_password.LoggedInViewModel; -import interface_adapter.logout.LogoutController; - -/** - * The View for when the user is logged into the program. - */ -public class LoggedInView extends JPanel implements PropertyChangeListener { - - private final String viewName = "logged in"; - private final LoggedInViewModel loggedInViewModel; - private final JLabel passwordErrorField = new JLabel(); - private ChangePasswordController changePasswordController; - private LogoutController logoutController; - - private final JLabel username; - - private final JButton logOut; - - private final JTextField passwordInputField = new JTextField(15); - private final JButton changePassword; - - public LoggedInView(LoggedInViewModel loggedInViewModel) { - this.loggedInViewModel = loggedInViewModel; - this.loggedInViewModel.addPropertyChangeListener(this); - - final JLabel title = new JLabel("Logged In Screen"); - title.setAlignmentX(Component.CENTER_ALIGNMENT); - - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel("Password"), passwordInputField); - - final JLabel usernameInfo = new JLabel("Currently logged in: "); - username = new JLabel(); - - final JPanel buttons = new JPanel(); - logOut = new JButton("Log Out"); - buttons.add(logOut); - - changePassword = new JButton("Change Password"); - buttons.add(changePassword); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final LoggedInState currentState = loggedInViewModel.getState(); - currentState.setPassword(passwordInputField.getText()); - loggedInViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - - changePassword.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. - evt -> { - if (evt.getSource().equals(changePassword)) { - final LoggedInState currentState = loggedInViewModel.getState(); - - this.changePasswordController.execute( - currentState.getUsername(), - currentState.getPassword() - ); - } - } - ); - - logOut.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. - evt -> { - if (evt.getSource().equals(logOut)) { - // TODO: execute the logout use case through the Controller - // 1. get the state out of the loggedInViewModel. It contains the username. - // 2. Execute the logout Controller. - } - } - ); - - this.add(title); - this.add(usernameInfo); - this.add(username); - - this.add(passwordInfo); - this.add(passwordErrorField); - this.add(buttons); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals("state")) { - final LoggedInState state = (LoggedInState) evt.getNewValue(); - username.setText(state.getUsername()); - } - else if (evt.getPropertyName().equals("password")) { - final LoggedInState state = (LoggedInState) evt.getNewValue(); - JOptionPane.showMessageDialog(null, "password updated for " + state.getUsername()); - } - - } - - public String getViewName() { - return viewName; - } - - public void setChangePasswordController(ChangePasswordController changePasswordController) { - this.changePasswordController = changePasswordController; - } - - public void setLogoutController(LogoutController logoutController) { - // TODO: save the logout controller in the instance variable. - } -} diff --git a/src/main/java/view/LoginView.java b/src/main/java/view/LoginView.java deleted file mode 100644 index 96d4f3845..000000000 --- a/src/main/java/view/LoginView.java +++ /dev/null @@ -1,160 +0,0 @@ -package view; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import interface_adapter.login.LoginController; -import interface_adapter.login.LoginState; -import interface_adapter.login.LoginViewModel; - -/** - * The View for when the user is logging into the program. - */ -public class LoginView extends JPanel implements ActionListener, PropertyChangeListener { - - private final String viewName = "log in"; - private final LoginViewModel loginViewModel; - - private final JTextField usernameInputField = new JTextField(15); - private final JLabel usernameErrorField = new JLabel(); - - private final JPasswordField passwordInputField = new JPasswordField(15); - private final JLabel passwordErrorField = new JLabel(); - - private final JButton logIn; - private final JButton cancel; - private LoginController loginController; - - public LoginView(LoginViewModel loginViewModel) { - - this.loginViewModel = loginViewModel; - this.loginViewModel.addPropertyChangeListener(this); - - final JLabel title = new JLabel("Login Screen"); - title.setAlignmentX(Component.CENTER_ALIGNMENT); - - final LabelTextPanel usernameInfo = new LabelTextPanel( - new JLabel("Username"), usernameInputField); - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel("Password"), passwordInputField); - - final JPanel buttons = new JPanel(); - logIn = new JButton("log in"); - buttons.add(logIn); - cancel = new JButton("cancel"); - buttons.add(cancel); - - logIn.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent evt) { - if (evt.getSource().equals(logIn)) { - final LoginState currentState = loginViewModel.getState(); - - loginController.execute( - currentState.getUsername(), - currentState.getPassword() - ); - } - } - } - ); - - cancel.addActionListener(this); - - usernameInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final LoginState currentState = loginViewModel.getState(); - currentState.setUsername(usernameInputField.getText()); - loginViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final LoginState currentState = loginViewModel.getState(); - currentState.setPassword(new String(passwordInputField.getPassword())); - loginViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - - this.add(title); - this.add(usernameInfo); - this.add(usernameErrorField); - this.add(passwordInfo); - this.add(buttons); - } - - /** - * React to a button click that results in evt. - * @param evt the ActionEvent to react to - */ - public void actionPerformed(ActionEvent evt) { - System.out.println("Click " + evt.getActionCommand()); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - final LoginState state = (LoginState) evt.getNewValue(); - setFields(state); - usernameErrorField.setText(state.getLoginError()); - } - - private void setFields(LoginState state) { - usernameInputField.setText(state.getUsername()); - passwordInputField.setText(state.getPassword()); - } - - public String getViewName() { - return viewName; - } - - public void setLoginController(LoginController loginController) { - this.loginController = loginController; - } -} diff --git a/src/main/java/view/SignupView.java b/src/main/java/view/SignupView.java deleted file mode 100644 index f98570d62..000000000 --- a/src/main/java/view/SignupView.java +++ /dev/null @@ -1,199 +0,0 @@ -package view; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import interface_adapter.signup.SignupController; -import interface_adapter.signup.SignupState; -import interface_adapter.signup.SignupViewModel; - -/** - * The View for the Signup Use Case. - */ -public class SignupView extends JPanel implements ActionListener, PropertyChangeListener { - private final String viewName = "sign up"; - - private final SignupViewModel signupViewModel; - private final JTextField usernameInputField = new JTextField(15); - private final JPasswordField passwordInputField = new JPasswordField(15); - private final JPasswordField repeatPasswordInputField = new JPasswordField(15); - private SignupController signupController; - - private final JButton signUp; - private final JButton cancel; - private final JButton toLogin; - - public SignupView(SignupViewModel signupViewModel) { - this.signupViewModel = signupViewModel; - signupViewModel.addPropertyChangeListener(this); - - final JLabel title = new JLabel(SignupViewModel.TITLE_LABEL); - title.setAlignmentX(Component.CENTER_ALIGNMENT); - - final LabelTextPanel usernameInfo = new LabelTextPanel( - new JLabel(SignupViewModel.USERNAME_LABEL), usernameInputField); - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel(SignupViewModel.PASSWORD_LABEL), passwordInputField); - final LabelTextPanel repeatPasswordInfo = new LabelTextPanel( - new JLabel(SignupViewModel.REPEAT_PASSWORD_LABEL), repeatPasswordInputField); - - final JPanel buttons = new JPanel(); - toLogin = new JButton(SignupViewModel.TO_LOGIN_BUTTON_LABEL); - buttons.add(toLogin); - signUp = new JButton(SignupViewModel.SIGNUP_BUTTON_LABEL); - buttons.add(signUp); - cancel = new JButton(SignupViewModel.CANCEL_BUTTON_LABEL); - buttons.add(cancel); - - signUp.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. - new ActionListener() { - public void actionPerformed(ActionEvent evt) { - if (evt.getSource().equals(signUp)) { - final SignupState currentState = signupViewModel.getState(); - - signupController.execute( - currentState.getUsername(), - currentState.getPassword(), - currentState.getRepeatPassword() - ); - } - } - } - ); - - toLogin.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent evt) { - signupController.switchToLoginView(); - } - } - ); - - cancel.addActionListener(this); - - addUsernameListener(); - addPasswordListener(); - addRepeatPasswordListener(); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - this.add(title); - this.add(usernameInfo); - this.add(passwordInfo); - this.add(repeatPasswordInfo); - this.add(buttons); - } - - private void addUsernameListener() { - usernameInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setUsername(usernameInputField.getText()); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - private void addPasswordListener() { - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setPassword(new String(passwordInputField.getPassword())); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - private void addRepeatPasswordListener() { - repeatPasswordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setRepeatPassword(new String(repeatPasswordInputField.getPassword())); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - @Override - public void actionPerformed(ActionEvent evt) { - JOptionPane.showMessageDialog(this, "Cancel not implemented yet."); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - final SignupState state = (SignupState) evt.getNewValue(); - if (state.getUsernameError() != null) { - JOptionPane.showMessageDialog(this, state.getUsernameError()); - } - } - - public String getViewName() { - return viewName; - } - - public void setSignupController(SignupController controller) { - this.signupController = controller; - } -} diff --git a/src/main/java/view/ViewManager.java b/src/main/java/view/ViewManager.java deleted file mode 100644 index 7f9f07ec6..000000000 --- a/src/main/java/view/ViewManager.java +++ /dev/null @@ -1,34 +0,0 @@ -package view; - -import java.awt.CardLayout; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.JPanel; - -import interface_adapter.ViewManagerModel; - -/** - * The View Manager for the program. It listens for property change events - * in the ViewManagerModel and updates which View should be visible. - */ -public class ViewManager implements PropertyChangeListener { - private final CardLayout cardLayout; - private final JPanel views; - private final ViewManagerModel viewManagerModel; - - public ViewManager(JPanel views, CardLayout cardLayout, ViewManagerModel viewManagerModel) { - this.views = views; - this.cardLayout = cardLayout; - this.viewManagerModel = viewManagerModel; - this.viewManagerModel.addPropertyChangeListener(this); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals("state")) { - final String viewModelName = (String) evt.getNewValue(); - cardLayout.show(views, viewModelName); - } - } -} From fb44133d05b5bf430079d96dd6e7068ff4b36607 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 4 Nov 2024 14:40:57 -0500 Subject: [PATCH 009/148] setting up the API --- .idea/misc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 9e0563eb9..f0f82878c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,5 +8,5 @@ - + \ No newline at end of file From 0125522399ddbd36236c72693a2b28796b43e5e0 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 4 Nov 2024 14:42:01 -0500 Subject: [PATCH 010/148] lab files deleted --- .../use_case/login/LoginInteractorTest.java | 118 ------------------ .../use_case/logout/LogoutInteractorTest.java | 40 ------ .../use_case/signup/SignupInteractorTest.java | 98 --------------- 3 files changed, 256 deletions(-) delete mode 100644 src/test/java/use_case/login/LoginInteractorTest.java delete mode 100644 src/test/java/use_case/logout/LogoutInteractorTest.java delete mode 100644 src/test/java/use_case/signup/SignupInteractorTest.java diff --git a/src/test/java/use_case/login/LoginInteractorTest.java b/src/test/java/use_case/login/LoginInteractorTest.java deleted file mode 100644 index 196942bee..000000000 --- a/src/test/java/use_case/login/LoginInteractorTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package use_case.login; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class LoginInteractorTest { - - @Test - void successTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a successPresenter that tests whether the test case is as we expect. - LoginOutputBoundary successPresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", user.getUsername()); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); - interactor.execute(inputData); - } - - @Test - void successUserLoggedInTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a successPresenter that tests whether the test case is as we expect. - LoginOutputBoundary successPresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", userRepository.getCurrentUsername()); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); - assertEquals(null, userRepository.getCurrentUsername()); - - interactor.execute(inputData); - } - - @Test - void failurePasswordMismatchTest() { - LoginInputData inputData = new LoginInputData("Paul", "wrong"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For this failure test, we need to add Paul to the data access repository before we log in, and - // the passwords should not match. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Incorrect password for \"Paul\".", error); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); - } - - @Test - void failureUserDoesNotExistTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // Add Paul to the repo so that when we check later they already exist - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Paul: Account does not exist.", error); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); - } -} \ No newline at end of file diff --git a/src/test/java/use_case/logout/LogoutInteractorTest.java b/src/test/java/use_case/logout/LogoutInteractorTest.java deleted file mode 100644 index 2a24189f9..000000000 --- a/src/test/java/use_case/logout/LogoutInteractorTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package use_case.logout; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class LogoutInteractorTest { - - @Test - void successTest() { - LogoutInputData inputData = new LogoutInputData("Paul"); - InMemoryUserDataAccessObject userRepository = new InMemoryUserDataAccessObject(); - - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - userRepository.setCurrentUsername("Paul"); - - // This creates a successPresenter that tests whether the test case is as we expect. - LogoutOutputBoundary successPresenter = new LogoutOutputBoundary() { - @Override - public void prepareSuccessView(LogoutOutputData user) { - // check that the output data contains the username of who logged out - assertEquals("Paul", user.getUsername()); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - }; - - LogoutInputBoundary interactor = new LogoutInteractor(userRepository, successPresenter); - interactor.execute(inputData); - // check that the user was logged out - assertNull(userRepository.getCurrentUsername()); - } - -} \ No newline at end of file diff --git a/src/test/java/use_case/signup/SignupInteractorTest.java b/src/test/java/use_case/signup/SignupInteractorTest.java deleted file mode 100644 index 681b9ae23..000000000 --- a/src/test/java/use_case/signup/SignupInteractorTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package use_case.signup; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class SignupInteractorTest { - - @Test - void successTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "password"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // This creates a successPresenter that tests whether the test case is as we expect. - SignupOutputBoundary successPresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // 2 things to check: the output data is correct, and the user has been created in the DAO. - assertEquals("Paul", user.getUsername()); - assertTrue(userRepository.existsByName("Paul")); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, successPresenter, new CommonUserFactory()); - interactor.execute(inputData); - } - - @Test - void failurePasswordMismatchTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Passwords don't match.", error); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); - interactor.execute(inputData); - } - - @Test - void failureUserExistsTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // Add Paul to the repo so that when we check later they already exist - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "pwd"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("User already exists.", error); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); - interactor.execute(inputData); - } -} \ No newline at end of file From fc87dd6520d06963a15cea1339e8ae8afc6854d4 Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 4 Nov 2024 14:55:55 -0500 Subject: [PATCH 011/148] fixed some checkstyle errors --- src/main/java/entity/Ingredient.java | 3 +++ src/main/java/entity/Nutrition.java | 3 +++ src/main/java/entity/Recipe.java | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/entity/Ingredient.java b/src/main/java/entity/Ingredient.java index 9459b2966..3c7d189b3 100644 --- a/src/main/java/entity/Ingredient.java +++ b/src/main/java/entity/Ingredient.java @@ -1,5 +1,8 @@ package entity; +/** + * Represents an ingredient from our recipe. + */ public class Ingredient { private String name; diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index 1922e6402..e240fa65d 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,4 +1,7 @@ package entity; +/** + * Represents the nutritional info for a recipe. + */ public class Nutrition { } diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java index cc7ec769b..af3035b1d 100644 --- a/src/main/java/entity/Recipe.java +++ b/src/main/java/entity/Recipe.java @@ -2,6 +2,9 @@ import java.util.List; +/** + * This class returns a recipe from the API. + */ public class Recipe { private String name; private String url; @@ -13,9 +16,15 @@ public Recipe(String name, String url, List ingredients) { this.ingredients = ingredients; } - public String getName() {return name;} + public String getName() { + return name; + } - public String getUrl() {return url;} + public String getUrl() { + return url; + } - public List getIngredients() {return ingredients; } + public List getIngredients() { + return ingredients; + } } From 9f8219bd70c8c055990857fa37e53a4101d1d54b Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Sat, 9 Nov 2024 23:04:06 -0500 Subject: [PATCH 012/148] added working API call example and a file with the API key --- src/main/java/API Key | 0 src/main/java/SpoonacularExample.java | 45 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/API Key create mode 100644 src/main/java/SpoonacularExample.java diff --git a/src/main/java/API Key b/src/main/java/API Key new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/SpoonacularExample.java b/src/main/java/SpoonacularExample.java new file mode 100644 index 000000000..377ccef90 --- /dev/null +++ b/src/main/java/SpoonacularExample.java @@ -0,0 +1,45 @@ +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; + +public class SpoonacularExample { + private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; // Replace with your Spoonacular API key + private static final String BASE_URL = "https://api.spoonacular.com"; + + public static void main(String[] args) { + OkHttpClient client = new OkHttpClient(); + + // Define the endpoint and query parameters + String endpoint = "/recipes/complexSearch"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&query=pasta&number=2"; + + // Build the request + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + // Parse the JSON response + String jsonResponse = response.body().string(); + JSONObject jsonObject = new JSONObject(jsonResponse); + JSONArray results = jsonObject.getJSONArray("results"); + + // Loop through the results and print recipe titles and IDs + for (int i = 0; i < results.length(); i++) { + JSONObject recipe = results.getJSONObject(i); + System.out.println("Recipe: " + recipe.getString("title")); + System.out.println("ID: " + recipe.getInt("id")); + } + } else { + System.out.println("Request failed with code: " + response.code()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} From 461ae91d0395c40dbdc5c82198e72ebd19bbf711 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Sat, 9 Nov 2024 23:06:05 -0500 Subject: [PATCH 013/148] changed names to alphabetical order --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bba02c7c1..6c1a685a4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ * GitHub username: farshad-haddadi * Email:⁠⁠ farshad.haddadi@mail.utoronto.ca +Hongcheng (Morgan)Huo +* Github username: hhcgoodluck +* Email: hhcgoodluck@outlook.com + Sung-chi (William) Wu * GitHub username: ⁠⁠wusungch * Email:⁠⁠ sungchi2024@outlook.com @@ -18,10 +22,6 @@ Shuxin (Kate) Zhou * GitHub username: kayzoo8 * Email: katezhou2005@gmail.com -Hongcheng (Morgan)Huo -* Github username: hhcgoodluck -* Email: hhcgoodluck@outlook.com - ## User Stories * Member 1: The core feature of the program allows users to input ingredients they have on hand and receive recipe suggestions. Users can enter ingredients separated by commas, and the system will generate relevant recipes. If some ingredients are missing, the system can suggest close alternatives or highlight what else is needed. This feature helps users efficiently use the ingredients they already own, potentially reducing food waste and unnecessary shopping trips. From 7fdf52a6e8b956e905f9e9e1801a5b32e01b8c63 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 11 Nov 2024 13:15:21 -0500 Subject: [PATCH 014/148] Updated the code for the first use case to utilize api --- src/main/java/app/Main.java | 9 ++- .../data_access/SpoonacularRecipeDAO.java | 78 +++++++++++++++++++ .../interface_adapter/RecipeController.java | 3 +- .../java/use_case/SearchRecipeUseCase.java | 14 +--- 4 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 src/main/java/data_access/SpoonacularRecipeDAO.java diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index db631e255..dc22b0f85 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,15 +1,20 @@ package app; -import data_access.RecipeDAOImpl; +import data_access.SpoonacularRecipeDAO; import interface_adapter.RecipeController; import use_case.SearchRecipeUseCase; import view.RecipeView; public class Main { public static void main(String[] args) { - RecipeDAOImpl recipeDAO = new RecipeDAOImpl(); + // Initialize dependencies + SpoonacularRecipeDAO recipeDAO = new SpoonacularRecipeDAO(); SearchRecipeUseCase searchRecipeUseCase = new SearchRecipeUseCase(recipeDAO); RecipeController controller = new RecipeController(searchRecipeUseCase); + + // Launch the UI new RecipeView(controller); } } + + diff --git a/src/main/java/data_access/SpoonacularRecipeDAO.java b/src/main/java/data_access/SpoonacularRecipeDAO.java new file mode 100644 index 000000000..02dc6f2ac --- /dev/null +++ b/src/main/java/data_access/SpoonacularRecipeDAO.java @@ -0,0 +1,78 @@ +package data_access; + +import entity.Recipe; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SpoonacularRecipeDAO implements RecipeDAO { + private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; // Replace with your Spoonacular API key + private static final String BASE_URL = "https://api.spoonacular.com"; + private final OkHttpClient client; + + public SpoonacularRecipeDAO() { + this.client = new OkHttpClient(); + } + + @Override + public List getAllRecipes() { + // If you need to fetch all recipes, you could use a default search (e.g., return popular recipes) + return getRecipesByIngredients(List.of("pasta")); // Example default search + } + + @Override + public List getRecipesByIngredients(List ingredients) { + // Convert ingredients list to a comma-separated string + String ingredientsQuery = ingredients.stream() + .map(String::trim) + .collect(Collectors.joining(",")); + + // Construct the URL with the ingredients query + String endpoint = "/recipes/findByIngredients"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&ingredients=" + ingredientsQuery + "&number=5"; + + // Build the request + Request request = new Request.Builder() + .url(url) + .build(); + + List recipes = new ArrayList<>(); + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + // Parse the JSON response + String jsonResponse = response.body().string(); + JSONArray results = new JSONArray(jsonResponse); + + // Loop through the results and create Recipe objects + for (int i = 0; i < results.length(); i++) { + JSONObject recipeJson = results.getJSONObject(i); + String title = recipeJson.getString("title"); + String recipeUrl = BASE_URL + "/recipes/" + recipeJson.getInt("id") + "/information"; // URL to recipe details + JSONArray ingredientsJson = recipeJson.getJSONArray("usedIngredients"); + + // Collect ingredients from the JSON response + List recipeIngredients = new ArrayList<>(); + for (int j = 0; j < ingredientsJson.length(); j++) { + recipeIngredients.add(ingredientsJson.getJSONObject(j).getString("name")); + } + + // Create and add Recipe object to the list + Recipe recipe = new Recipe(title, recipeUrl, recipeIngredients); + recipes.add(recipe); + } + } else { + System.out.println("Request failed with code: " + response.code()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return recipes; + } +} diff --git a/src/main/java/interface_adapter/RecipeController.java b/src/main/java/interface_adapter/RecipeController.java index 011671020..864cb347d 100644 --- a/src/main/java/interface_adapter/RecipeController.java +++ b/src/main/java/interface_adapter/RecipeController.java @@ -1,8 +1,7 @@ package interface_adapter; -import entity.Recipe; import use_case.SearchRecipeUseCase; - +import entity.Recipe; import java.util.List; public class RecipeController { diff --git a/src/main/java/use_case/SearchRecipeUseCase.java b/src/main/java/use_case/SearchRecipeUseCase.java index cc2590706..f33a5a782 100644 --- a/src/main/java/use_case/SearchRecipeUseCase.java +++ b/src/main/java/use_case/SearchRecipeUseCase.java @@ -3,7 +3,6 @@ import data_access.RecipeDAO; import entity.Recipe; -import java.util.ArrayList; import java.util.List; public class SearchRecipeUseCase { @@ -14,17 +13,6 @@ public SearchRecipeUseCase(RecipeDAO recipeDAO) { } public List searchRecipes(List ingredients) { - List allRecipes = recipeDAO.getAllRecipes(); - List filteredRecipes = new ArrayList<>(); - - for (Recipe recipe : allRecipes){ - for (String ingredient : ingredients){ - if (recipe.getIngredients().contains(ingredient.trim().toLowerCase())){ - filteredRecipes.add(recipe); - break; - } - } - } - return filteredRecipes; + return recipeDAO.getRecipesByIngredients(ingredients); } } From 3061d1135fab410ea24ca61da3926ded391e29de Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 11 Nov 2024 13:20:09 -0500 Subject: [PATCH 015/148] edited api sample --- src/main/java/API Key | 1 + src/main/java/SpoonacularExample.java | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/API Key b/src/main/java/API Key index e69de29bb..a40679ad5 100644 --- a/src/main/java/API Key +++ b/src/main/java/API Key @@ -0,0 +1 @@ +5d06ae7be18746aaa4d1f1e5877f4450 \ No newline at end of file diff --git a/src/main/java/SpoonacularExample.java b/src/main/java/SpoonacularExample.java index 377ccef90..afdf1f1a8 100644 --- a/src/main/java/SpoonacularExample.java +++ b/src/main/java/SpoonacularExample.java @@ -3,42 +3,44 @@ import okhttp3.Response; import org.json.JSONArray; import org.json.JSONObject; - import java.io.IOException; public class SpoonacularExample { - private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; // Replace with your Spoonacular API key + private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; private static final String BASE_URL = "https://api.spoonacular.com"; public static void main(String[] args) { OkHttpClient client = new OkHttpClient(); // Define the endpoint and query parameters - String endpoint = "/recipes/complexSearch"; - String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&query=pasta&number=2"; + final String endpoint = "/recipes/complexSearch"; + // This is where we specify the query (ingredients we have, number of recipes we want, cuisine, etc.) + final String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&query=pork&number=5&cuisine=chinese"; // Build the request - Request request = new Request.Builder() + final Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { if (response.isSuccessful() && response.body() != null) { // Parse the JSON response - String jsonResponse = response.body().string(); - JSONObject jsonObject = new JSONObject(jsonResponse); - JSONArray results = jsonObject.getJSONArray("results"); + final String jsonResponse = response.body().string(); + final JSONObject jsonObject = new JSONObject(jsonResponse); + final JSONArray results = jsonObject.getJSONArray("results"); // Loop through the results and print recipe titles and IDs for (int i = 0; i < results.length(); i++) { - JSONObject recipe = results.getJSONObject(i); + final JSONObject recipe = results.getJSONObject(i); System.out.println("Recipe: " + recipe.getString("title")); System.out.println("ID: " + recipe.getInt("id")); } - } else { + } + else { System.out.println("Request failed with code: " + response.code()); } - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); } } From 5f852cd0661c2b7317c28f877563f9b62dd6db7e Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 11 Nov 2024 13:40:31 -0500 Subject: [PATCH 016/148] added a SearchFilter class and amended Recipe class to include cuisine type --- src/main/java/entity/Recipe.java | 5 +++++ src/main/java/entity/SearchFilter.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/main/java/entity/SearchFilter.java diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java index af3035b1d..0bbe90a9b 100644 --- a/src/main/java/entity/Recipe.java +++ b/src/main/java/entity/Recipe.java @@ -9,6 +9,7 @@ public class Recipe { private String name; private String url; private List ingredients; + private String cuisineType; public Recipe(String name, String url, List ingredients) { this.name = name; @@ -27,4 +28,8 @@ public String getUrl() { public List getIngredients() { return ingredients; } + + public String getCuisineType() { + return cuisineType; + } } diff --git a/src/main/java/entity/SearchFilter.java b/src/main/java/entity/SearchFilter.java new file mode 100644 index 000000000..128aa06ef --- /dev/null +++ b/src/main/java/entity/SearchFilter.java @@ -0,0 +1,17 @@ +package entity; + +/** + * This class represents the criteria used to filter search results. + */ +public class SearchFilter { + private String cuisineType; + + /** + * Checks if the recipe matches the cuisine type. + * @param recipe from user + * @return a boolean value for whether selected cuisine type matches cuisine type of recipe + */ + public boolean matchesRecipe(Recipe recipe) { + return cuisineType.equals(recipe.getCuisineType()); + } +} From 595a73a4cd37cc11492c0620ec8840e2bf569e40 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:52:59 -0500 Subject: [PATCH 017/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index e240fa65d..f403a9fee 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,39 @@ package entity; /** - * Represents the nutritional info for a recipe. + * Represents the nutritional information for a recipe. */ public class Nutrition { + private final String name; + private final double amount; + private final String unit; + private final double percentDailyNeeds; + + public Nutrition(String name, double amount, String unit, double percentDailyNeeds) { + this.name = name; + this.amount = amount; + this.unit = unit; + this.percentDailyNeeds = percentDailyNeeds; + } + + public String getName() { + return name; + } + + public double getAmount() { + return amount; + } + + public String getUnit() { + return unit; + } + + public double getPercentDailyNeeds() { + return percentDailyNeeds; + } + + @Override + public String toString() { + return name + ": " + amount + " " + unit + " (" + percentDailyNeeds + "% of daily needs)"; + } } From 40bfd1e06094aa9cbeea1a691249712312abac9c Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:56:49 -0500 Subject: [PATCH 018/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index f403a9fee..546d268e6 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,7 @@ package entity; /** - * Represents the nutritional information for a recipe. + * Represent the nutritional information for a recipe. */ public class Nutrition { private final String name; From cc5c19416c1545b70c80f25e43681064ac78c8fd Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:59:07 -0500 Subject: [PATCH 019/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index 546d268e6..f403a9fee 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,7 @@ package entity; /** - * Represent the nutritional information for a recipe. + * Represents the nutritional information for a recipe. */ public class Nutrition { private final String name; From 2dd3852c4f9665b875baeb9a16e0e7ac598a65d0 Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 11 Nov 2024 14:00:45 -0500 Subject: [PATCH 020/148] created a DietaryPreference class w/ 4 preferences for now --- src/main/java/entity/DietaryPreference.java | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/entity/DietaryPreference.java diff --git a/src/main/java/entity/DietaryPreference.java b/src/main/java/entity/DietaryPreference.java new file mode 100644 index 000000000..70e2af65d --- /dev/null +++ b/src/main/java/entity/DietaryPreference.java @@ -0,0 +1,23 @@ +package entity; + +/** + * Represents the user's dietary preferences for filtering recipes. + */ +public class DietaryPreference { + private boolean isVegetarian; + private boolean isVegan; + private boolean isGlutenFree; + private boolean isKeto; + // implement allergies too! + + /** + * Checks if the recipe matches with the dietary preference. + * @param recipe from user + * @return a boolean value for whether recipe matches diet. + */ + public boolean matchesRecipe(Recipe recipe) { + return true; + // not yet implemented + } + +} From df5d8b45858da5ee4f88b725b61639c34344d085 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:22:19 -0500 Subject: [PATCH 021/148] (data access) implementation of the Nutrition View Interface --- .../ViewNutritionDataAccessImpl.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/main/java/data_access/ViewNutritionDataAccessImpl.java diff --git a/src/main/java/data_access/ViewNutritionDataAccessImpl.java b/src/main/java/data_access/ViewNutritionDataAccessImpl.java new file mode 100644 index 000000000..7a2f1609a --- /dev/null +++ b/src/main/java/data_access/ViewNutritionDataAccessImpl.java @@ -0,0 +1,64 @@ +package data_access; + +import entity.Nutrition; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; +import use_case.ViewNutritionDataAccessInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ViewNutritionDataAccessImpl implements ViewNutritionDataAccessInterface { + private static final String BASE_URL = "https://api.spoonacular.com"; + private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; + + @Override + public List getNutritionDataForRecipe(int recipeId) { + OkHttpClient client = new OkHttpClient(); + + // Define the endpoint and query parameters(None) + String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY; + + // Build the request + Request request = new Request.Builder().url(url).build(); + + List nutritionList = new ArrayList<>(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + + // Parse the JSON response + String jsonResponse = response.body().string(); + JSONObject jsonObject = new JSONObject(jsonResponse); + + // Extract 'nutrients' array + if (jsonObject.has("nutrients")) { + JSONArray nutrients = jsonObject.getJSONArray("nutrients"); + for (int i = 0; i < nutrients.length(); i++) { + JSONObject nutrientJson = nutrients.getJSONObject(i); + + String name = nutrientJson.getString("name"); + double amount = nutrientJson.getDouble("amount"); + String unit = nutrientJson.getString("unit"); + double percentDailyNeeds = nutrientJson.optDouble("percentDailyNeeds", 0); + + Nutrition nutrition = new Nutrition(name, amount, unit, percentDailyNeeds); + nutritionList.add(nutrition); + } + } + return nutritionList; + } else { + System.err.println("Request failed with code: " + response.code()); + } + + } catch (IOException e) { + e.printStackTrace(); + } + return nutritionList; + } +} From c4e46597e700222583b19fd171556eac79f49e1b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:24:29 -0500 Subject: [PATCH 022/148] (use case) Interface of Nutrition View Data Access --- .../use_case/ViewNutritionDataAccessInterface.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/use_case/ViewNutritionDataAccessInterface.java diff --git a/src/main/java/use_case/ViewNutritionDataAccessInterface.java b/src/main/java/use_case/ViewNutritionDataAccessInterface.java new file mode 100644 index 000000000..da088d83f --- /dev/null +++ b/src/main/java/use_case/ViewNutritionDataAccessInterface.java @@ -0,0 +1,10 @@ +package use_case; + +import java.util.List; + +import entity.Nutrition; + +public interface ViewNutritionDataAccessInterface { + + List getNutritionDataForRecipe(int recipeId); +} From 1b7c8ff2005aee4cfe5efd25728cc3cc4fee073b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:30:11 -0500 Subject: [PATCH 023/148] (Test) Data Access Test of Nutrition Information View --- .../java/ViewNutritionDataAccessTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/ViewNutritionDataAccessTest.java diff --git a/src/main/java/ViewNutritionDataAccessTest.java b/src/main/java/ViewNutritionDataAccessTest.java new file mode 100644 index 000000000..7f893aea6 --- /dev/null +++ b/src/main/java/ViewNutritionDataAccessTest.java @@ -0,0 +1,27 @@ +import data_access.ViewNutritionDataAccessImpl; +import entity.Nutrition; + +import java.util.List; + +public class ViewNutritionDataAccessTest { + public static void main(String[] args) { + // 创建一个 ViewNutritionDataAccessImpl 对象 + ViewNutritionDataAccessImpl dataAccess = new ViewNutritionDataAccessImpl(); + + // 传入测试的 recipeId + int recipeId = 1003464; + + // 调用方法获取营养数据 + List nutritionList = dataAccess.getNutritionDataForRecipe(recipeId); + + // 输出结果 + if (!nutritionList.isEmpty()) { + System.out.println("Nutrition data for recipe ID " + recipeId + ":"); + for (Nutrition nutrition : nutritionList) { + System.out.println(nutrition); + } + } else { + System.out.println("No nutrition data found for recipe ID " + recipeId + "."); + } + } +} From 9b65359cda5261482a53ebece25c2930ec166a10 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 11 Nov 2024 14:32:51 -0500 Subject: [PATCH 024/148] created BookmarkView, IndividualRecipeView and NutritionView; added some controllers --- src/main/java/SpoonacularExample.java | 2 +- .../interface_adapter/BookmarkController.java | 22 +++++ .../java/use_case/SearchBookmarkUseCase.java | 4 + src/main/java/view/BookmarkView.java | 89 +++++++++++++++++++ src/main/java/view/IndividualRecipeView.java | 33 +++++++ src/main/java/view/NutritionView.java | 20 +++++ 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/main/java/interface_adapter/BookmarkController.java create mode 100644 src/main/java/use_case/SearchBookmarkUseCase.java create mode 100644 src/main/java/view/BookmarkView.java create mode 100644 src/main/java/view/IndividualRecipeView.java create mode 100644 src/main/java/view/NutritionView.java diff --git a/src/main/java/SpoonacularExample.java b/src/main/java/SpoonacularExample.java index afdf1f1a8..2b4ae1b7a 100644 --- a/src/main/java/SpoonacularExample.java +++ b/src/main/java/SpoonacularExample.java @@ -10,7 +10,7 @@ public class SpoonacularExample { private static final String BASE_URL = "https://api.spoonacular.com"; public static void main(String[] args) { - OkHttpClient client = new OkHttpClient(); + final OkHttpClient client = new OkHttpClient(); // Define the endpoint and query parameters final String endpoint = "/recipes/complexSearch"; diff --git a/src/main/java/interface_adapter/BookmarkController.java b/src/main/java/interface_adapter/BookmarkController.java new file mode 100644 index 000000000..7203f5f3a --- /dev/null +++ b/src/main/java/interface_adapter/BookmarkController.java @@ -0,0 +1,22 @@ +package interface_adapter; + +import entity.Recipe; +import use_case.SearchRecipeUseCase; + +import java.util.List; + +public class BookmarkController { + private final SearchBookmarkUseCase searchBookmarkUseCase; + + public BookmarkController(SearchBookmarkUseCase searchBookmarkUseCase) { + this.searchBookmarkUseCase = searchBookmarkUseCase; + } + + // TODO this method searches for the bookmarked recipes by ingredients contained + public List getBookmarksFromIngredients(List ingredients) { + return searchBookmarkUseCase. + } + + // TODO another method that searches for the bookmarked recipes by the name of recipe + public List getBookmarksFromNames(List names) {} +} diff --git a/src/main/java/use_case/SearchBookmarkUseCase.java b/src/main/java/use_case/SearchBookmarkUseCase.java new file mode 100644 index 000000000..e6b91a4cd --- /dev/null +++ b/src/main/java/use_case/SearchBookmarkUseCase.java @@ -0,0 +1,4 @@ +package use_case; + +public class SearchBookmarkUseCase { +} diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java new file mode 100644 index 000000000..0463b4a75 --- /dev/null +++ b/src/main/java/view/BookmarkView.java @@ -0,0 +1,89 @@ +package view; + +import entity.Recipe; +import interface_adapter.BookmarkController; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.List; + +public class BookmarkView extends JFrame { + // should the search attributes be defined here? + private JTextField searchField; + private JButton searchButton; + + // attributes for default view + private JList bookmarkList; + private BookmarkController controller; + private User user; + + // button to view individual recipes + private JButton viewRecipeButton; + // TODO modify as needed after User entity is added + + /* + Generates the default view of a user's bookmarked recipes. + */ + public BookmarkView(BookmarkController controller, User user) { + this.controller = controller; + // TODO getBookmarks() method + this.bookmarkList = new JList<>(); + + // TODO getname() method + setTitle(user.getname() + "'s Bookmarks"); + setSize(800, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + add(new JScrollPane(bookmarkList), BorderLayout.CENTER); + + // view recipe button for each item in the list + bookmarkList.add("view recipe", viewRecipeButton); + // or do JButton b = new JButton("view recipe")? + viewRecipeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new IndividualRecipeView; + // close the current frame - TODO add a back button? + // Or just don't dispose so you can access another recipe at the same time? + dispose(); + } + }); + + // Search function + ingredientInput = new JTextField(20); + searchButton = new JButton("Find Recipes by Ingredients"); + recipeListByIngredient = new JList<>(); + + nameInput = new JTextField(20); + searchButton = new JButton("Find Recipes by Name"); + recipeListByName = new JList<>(); + + searchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String ingredientsText = ingredientInput.getText(); + java.util.List ingredients = Arrays.asList(ingredientsText.split(",")); + List recipes = controller.getRecipes(ingredients); + + String[] recipeNames = recipes.stream() + .map(recipe -> recipe.getName() + " - " + recipe.getUrl()) + .toArray(String[]::new); + recipeList.setListData(recipeNames); + } + }); + + // Display search bar + final JPanel panel = new JPanel(); + panel.add(new JLabel("Enter ingredients (comma-separated):")); + panel.add(ingredientInput); + panel.add(searchButton); + add(panel, BorderLayout.NORTH); + + // Button to view individual recipes + add(viewRecipeButton, BorderLayout.SOUTH); + + setVisible(true); + } +} diff --git a/src/main/java/view/IndividualRecipeView.java b/src/main/java/view/IndividualRecipeView.java new file mode 100644 index 000000000..ac4d94741 --- /dev/null +++ b/src/main/java/view/IndividualRecipeView.java @@ -0,0 +1,33 @@ +package view; + +import entity.Recipe; +import entity.Nutrition; + +import javax.swing.*; +import java.awt.*; + +public class IndividualRecipeView extends JFrame { + private JButton homeButton; + private JButton nutritionButton; + private JList ingredientsList; + private Recipe recipe; + private Nutrition nutrition; + + public IndividualRecipeView(Recipe recipe, Nutrition nutrition) { + // TODO double check this + this.recipe = recipe; + this.ingredientsList = new JList<>(recipe.getIngredients().toArray(new String[0])); + this.nutrition = nutrition; + this.nutritionButton = new JButton("Nutrition"); + + setTitle(recipe.getName()); + setSize(800, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + add(ingredientsList = new JList<>()); + add(nutritionButton); + nutritionButton.addActionListener(e -> { + new NutritionView(recipe); + }); + setVisible(true); + } +} diff --git a/src/main/java/view/NutritionView.java b/src/main/java/view/NutritionView.java new file mode 100644 index 000000000..8d4460a78 --- /dev/null +++ b/src/main/java/view/NutritionView.java @@ -0,0 +1,20 @@ +package view; + +import entity.Recipe; + +import javax.swing.*; + +public class NutritionView extends JFrame { + private Recipe recipe; + private JList nutritionList; + + NutritionView(Recipe recipe) { + this.nutritionList = new JList<>(); + + setTitle(recipe.getName()); + setSize(800, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + add(ingredientsList = new JList<>()); + setVisible(true); + } +} From 1ed2a3e6e3e5c4b9d88fdf554a8e8d3bd5202c1c Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:52:59 -0500 Subject: [PATCH 025/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 34 ++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index e240fa65d..f403a9fee 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,39 @@ package entity; /** - * Represents the nutritional info for a recipe. + * Represents the nutritional information for a recipe. */ public class Nutrition { + private final String name; + private final double amount; + private final String unit; + private final double percentDailyNeeds; + + public Nutrition(String name, double amount, String unit, double percentDailyNeeds) { + this.name = name; + this.amount = amount; + this.unit = unit; + this.percentDailyNeeds = percentDailyNeeds; + } + + public String getName() { + return name; + } + + public double getAmount() { + return amount; + } + + public String getUnit() { + return unit; + } + + public double getPercentDailyNeeds() { + return percentDailyNeeds; + } + + @Override + public String toString() { + return name + ": " + amount + " " + unit + " (" + percentDailyNeeds + "% of daily needs)"; + } } From d3aa27e8901578b666a1921e7edd027204a6eb8b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:56:49 -0500 Subject: [PATCH 026/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index f403a9fee..546d268e6 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,7 @@ package entity; /** - * Represents the nutritional information for a recipe. + * Represent the nutritional information for a recipe. */ public class Nutrition { private final String name; From 92a57ed61a315507f0dc5430360b34824b8e400c Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 11 Nov 2024 13:40:31 -0500 Subject: [PATCH 027/148] added a SearchFilter class and amended Recipe class to include cuisine type --- src/main/java/entity/Recipe.java | 5 +++++ src/main/java/entity/SearchFilter.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/main/java/entity/SearchFilter.java diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java index af3035b1d..0bbe90a9b 100644 --- a/src/main/java/entity/Recipe.java +++ b/src/main/java/entity/Recipe.java @@ -9,6 +9,7 @@ public class Recipe { private String name; private String url; private List ingredients; + private String cuisineType; public Recipe(String name, String url, List ingredients) { this.name = name; @@ -27,4 +28,8 @@ public String getUrl() { public List getIngredients() { return ingredients; } + + public String getCuisineType() { + return cuisineType; + } } diff --git a/src/main/java/entity/SearchFilter.java b/src/main/java/entity/SearchFilter.java new file mode 100644 index 000000000..128aa06ef --- /dev/null +++ b/src/main/java/entity/SearchFilter.java @@ -0,0 +1,17 @@ +package entity; + +/** + * This class represents the criteria used to filter search results. + */ +public class SearchFilter { + private String cuisineType; + + /** + * Checks if the recipe matches the cuisine type. + * @param recipe from user + * @return a boolean value for whether selected cuisine type matches cuisine type of recipe + */ + public boolean matchesRecipe(Recipe recipe) { + return cuisineType.equals(recipe.getCuisineType()); + } +} From 726a14b97d7e4982016158c4d560d3cace66fefe Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 13:59:07 -0500 Subject: [PATCH 028/148] the design of Nutrition class (version 1.0) --- src/main/java/entity/Nutrition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index 546d268e6..f403a9fee 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -1,7 +1,7 @@ package entity; /** - * Represent the nutritional information for a recipe. + * Represents the nutritional information for a recipe. */ public class Nutrition { private final String name; From 9c20e8195060ce683f50a86397098482a412ad6e Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 11 Nov 2024 14:00:45 -0500 Subject: [PATCH 029/148] created a DietaryPreference class w/ 4 preferences for now --- src/main/java/entity/DietaryPreference.java | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/entity/DietaryPreference.java diff --git a/src/main/java/entity/DietaryPreference.java b/src/main/java/entity/DietaryPreference.java new file mode 100644 index 000000000..70e2af65d --- /dev/null +++ b/src/main/java/entity/DietaryPreference.java @@ -0,0 +1,23 @@ +package entity; + +/** + * Represents the user's dietary preferences for filtering recipes. + */ +public class DietaryPreference { + private boolean isVegetarian; + private boolean isVegan; + private boolean isGlutenFree; + private boolean isKeto; + // implement allergies too! + + /** + * Checks if the recipe matches with the dietary preference. + * @param recipe from user + * @return a boolean value for whether recipe matches diet. + */ + public boolean matchesRecipe(Recipe recipe) { + return true; + // not yet implemented + } + +} From 99b4e18cafb9fc397b21f94c66cf25d4c721f44b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:22:19 -0500 Subject: [PATCH 030/148] (data access) implementation of the Nutrition View Interface --- .../ViewNutritionDataAccessImpl.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/main/java/data_access/ViewNutritionDataAccessImpl.java diff --git a/src/main/java/data_access/ViewNutritionDataAccessImpl.java b/src/main/java/data_access/ViewNutritionDataAccessImpl.java new file mode 100644 index 000000000..7a2f1609a --- /dev/null +++ b/src/main/java/data_access/ViewNutritionDataAccessImpl.java @@ -0,0 +1,64 @@ +package data_access; + +import entity.Nutrition; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONObject; +import use_case.ViewNutritionDataAccessInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ViewNutritionDataAccessImpl implements ViewNutritionDataAccessInterface { + private static final String BASE_URL = "https://api.spoonacular.com"; + private static final String API_KEY = "5d06ae7be18746aaa4d1f1e5877f4450"; + + @Override + public List getNutritionDataForRecipe(int recipeId) { + OkHttpClient client = new OkHttpClient(); + + // Define the endpoint and query parameters(None) + String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY; + + // Build the request + Request request = new Request.Builder().url(url).build(); + + List nutritionList = new ArrayList<>(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + + // Parse the JSON response + String jsonResponse = response.body().string(); + JSONObject jsonObject = new JSONObject(jsonResponse); + + // Extract 'nutrients' array + if (jsonObject.has("nutrients")) { + JSONArray nutrients = jsonObject.getJSONArray("nutrients"); + for (int i = 0; i < nutrients.length(); i++) { + JSONObject nutrientJson = nutrients.getJSONObject(i); + + String name = nutrientJson.getString("name"); + double amount = nutrientJson.getDouble("amount"); + String unit = nutrientJson.getString("unit"); + double percentDailyNeeds = nutrientJson.optDouble("percentDailyNeeds", 0); + + Nutrition nutrition = new Nutrition(name, amount, unit, percentDailyNeeds); + nutritionList.add(nutrition); + } + } + return nutritionList; + } else { + System.err.println("Request failed with code: " + response.code()); + } + + } catch (IOException e) { + e.printStackTrace(); + } + return nutritionList; + } +} From dc37a2d438ad24417280fa7d8bf4f845f02f1b4b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:24:29 -0500 Subject: [PATCH 031/148] (use case) Interface of Nutrition View Data Access --- .../use_case/ViewNutritionDataAccessInterface.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/use_case/ViewNutritionDataAccessInterface.java diff --git a/src/main/java/use_case/ViewNutritionDataAccessInterface.java b/src/main/java/use_case/ViewNutritionDataAccessInterface.java new file mode 100644 index 000000000..da088d83f --- /dev/null +++ b/src/main/java/use_case/ViewNutritionDataAccessInterface.java @@ -0,0 +1,10 @@ +package use_case; + +import java.util.List; + +import entity.Nutrition; + +public interface ViewNutritionDataAccessInterface { + + List getNutritionDataForRecipe(int recipeId); +} From 254d9afe8b09130f5b08282cb057e8e5a4b8c4b1 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 11 Nov 2024 14:30:11 -0500 Subject: [PATCH 032/148] (Test) Data Access Test of Nutrition Information View --- .../java/ViewNutritionDataAccessTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/ViewNutritionDataAccessTest.java diff --git a/src/main/java/ViewNutritionDataAccessTest.java b/src/main/java/ViewNutritionDataAccessTest.java new file mode 100644 index 000000000..7f893aea6 --- /dev/null +++ b/src/main/java/ViewNutritionDataAccessTest.java @@ -0,0 +1,27 @@ +import data_access.ViewNutritionDataAccessImpl; +import entity.Nutrition; + +import java.util.List; + +public class ViewNutritionDataAccessTest { + public static void main(String[] args) { + // 创建一个 ViewNutritionDataAccessImpl 对象 + ViewNutritionDataAccessImpl dataAccess = new ViewNutritionDataAccessImpl(); + + // 传入测试的 recipeId + int recipeId = 1003464; + + // 调用方法获取营养数据 + List nutritionList = dataAccess.getNutritionDataForRecipe(recipeId); + + // 输出结果 + if (!nutritionList.isEmpty()) { + System.out.println("Nutrition data for recipe ID " + recipeId + ":"); + for (Nutrition nutrition : nutritionList) { + System.out.println(nutrition); + } + } else { + System.out.println("No nutrition data found for recipe ID " + recipeId + "."); + } + } +} From 6ae85e4dc9b6a055dad74462f6cee6113ae1ba29 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 11 Nov 2024 14:39:50 -0500 Subject: [PATCH 033/148] Added SignupLoginPage, HomePage, and User Classes --- .idea/uiDesigner.xml | 124 +++++++++++++++++++++ src/main/java/app/Main.java | 16 +-- src/main/java/data_access/UserDAO.java | 9 ++ src/main/java/data_access/UserDAOImpl.java | 97 ++++++++++++++++ src/main/java/entity/Recipe.java | 1 + src/main/java/entity/User.java | 29 +++++ src/main/java/view/HomePage.java | 36 ++++++ src/main/java/view/LoginSignupPage.java | 108 ++++++++++++++++++ 8 files changed, 407 insertions(+), 13 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 src/main/java/data_access/UserDAO.java create mode 100644 src/main/java/data_access/UserDAOImpl.java create mode 100644 src/main/java/entity/User.java create mode 100644 src/main/java/view/HomePage.java create mode 100644 src/main/java/view/LoginSignupPage.java diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 000000000..2b63946d5 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index dc22b0f85..a30d2bc46 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,20 +1,10 @@ package app; -import data_access.SpoonacularRecipeDAO; -import interface_adapter.RecipeController; -import use_case.SearchRecipeUseCase; -import view.RecipeView; +import view.LoginSignupPage; public class Main { public static void main(String[] args) { - // Initialize dependencies - SpoonacularRecipeDAO recipeDAO = new SpoonacularRecipeDAO(); - SearchRecipeUseCase searchRecipeUseCase = new SearchRecipeUseCase(recipeDAO); - RecipeController controller = new RecipeController(searchRecipeUseCase); - - // Launch the UI - new RecipeView(controller); + // Launch the login/signup page as the first view + new LoginSignupPage(); } } - - diff --git a/src/main/java/data_access/UserDAO.java b/src/main/java/data_access/UserDAO.java new file mode 100644 index 000000000..99c8b757d --- /dev/null +++ b/src/main/java/data_access/UserDAO.java @@ -0,0 +1,9 @@ +package data_access; + +import entity.User; + +public interface UserDAO { + boolean addUser(User user); // Returns true if the user is added successfully + User findUserByUsername(String username); // Finds a user by their username + boolean validateUser(String username, String password); // Validates username and password +} diff --git a/src/main/java/data_access/UserDAOImpl.java b/src/main/java/data_access/UserDAOImpl.java new file mode 100644 index 000000000..ea79beb12 --- /dev/null +++ b/src/main/java/data_access/UserDAOImpl.java @@ -0,0 +1,97 @@ +package data_access; + +import entity.User; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class UserDAOImpl implements UserDAO { + private static final String FILE_PATH = "users.json"; + private Map usersDatabase; + + public UserDAOImpl() { + usersDatabase = loadUsersFromFile(); + } + + @Override + public boolean addUser(User user) { + if (usersDatabase.containsKey(user.getUsername())) { + System.out.println("User already exists: " + user.getUsername()); + return false; // User already exists + } + usersDatabase.put(user.getUsername(), user); + saveUsersToFile(); + System.out.println("User added successfully: " + user.getUsername()); + return true; + } + + @Override + public User findUserByUsername(String username) { + return usersDatabase.get(username); // Returns null if the user doesn't exist + } + + + @Override + public boolean validateUser(String username, String password) { + // Check for empty username or password + if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) { + System.out.println("Empty username or password provided."); // Debugging output + return false; + } + + User user = findUserByUsername(username); + if (user == null) { + System.out.println("User not found: " + username); // Debugging output + return false; // User doesn't exist + } else if (!user.getPassword().equals(password)) { + System.out.println("Incorrect password for user: " + username); // Debugging output + return false; // Password doesn't match + } + System.out.println("User validated successfully: " + username); // Debugging output + return true; // User exists and password matches + } + + + + + // Load users from JSON file using org.json + private Map loadUsersFromFile() { + Map users = new HashMap<>(); + try (FileReader reader = new FileReader(FILE_PATH)) { + JSONArray usersArray = new JSONArray(new JSONTokener(reader)); + for (int i = 0; i < usersArray.length(); i++) { + JSONObject userObject = usersArray.getJSONObject(i); + String username = userObject.getString("username"); + String password = userObject.getString("password"); + users.put(username, new User(username, password)); + System.out.println("Loaded user: " + username); // Debugging output + } + } catch (IOException e) { + e.printStackTrace(); + } + return users; + } + + + // Save users to JSON file using org.json + private void saveUsersToFile() { + JSONArray usersArray = new JSONArray(); + for (User user : usersDatabase.values()) { + JSONObject userObject = new JSONObject(); + userObject.put("username", user.getUsername()); + userObject.put("password", user.getPassword()); + usersArray.put(userObject); + } + try (FileWriter writer = new FileWriter(FILE_PATH)) { + writer.write(usersArray.toString(4)); // Indent for readability + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java index af3035b1d..a7b7d4fe2 100644 --- a/src/main/java/entity/Recipe.java +++ b/src/main/java/entity/Recipe.java @@ -6,6 +6,7 @@ * This class returns a recipe from the API. */ public class Recipe { + private String id; private String name; private String url; private List ingredients; diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java new file mode 100644 index 000000000..ebb0d9fea --- /dev/null +++ b/src/main/java/entity/User.java @@ -0,0 +1,29 @@ +package entity; + +public class User { + private String username; + private String password; + + + public User(String username, String password) { + this.username = username; + this.password = password; + } + + // Getters and Setters + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/view/HomePage.java b/src/main/java/view/HomePage.java new file mode 100644 index 000000000..76ab5993b --- /dev/null +++ b/src/main/java/view/HomePage.java @@ -0,0 +1,36 @@ +package view; + +import data_access.SpoonacularRecipeDAO; +import interface_adapter.RecipeController; +import use_case.SearchRecipeUseCase; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class HomePage extends JFrame { + public HomePage() { + setTitle("Welcome to the Recipe Finder"); + setSize(400, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new BorderLayout()); + + JLabel welcomeLabel = new JLabel("Welcome to the Recipe Finder!", JLabel.CENTER); + welcomeLabel.setFont(new Font("Arial", Font.BOLD, 18)); + add(welcomeLabel, BorderLayout.CENTER); + + JButton startButton = new JButton("Start Searching Recipes"); + startButton.setFont(new Font("Arial", Font.PLAIN, 16)); + startButton.addActionListener(e -> { + // Open RecipeView and close HomePage + RecipeController controller = new RecipeController(new SearchRecipeUseCase(new SpoonacularRecipeDAO())); + new RecipeView(controller); + dispose(); + }); + + add(startButton, BorderLayout.SOUTH); + setVisible(true); + } +} + diff --git a/src/main/java/view/LoginSignupPage.java b/src/main/java/view/LoginSignupPage.java new file mode 100644 index 000000000..6918072cb --- /dev/null +++ b/src/main/java/view/LoginSignupPage.java @@ -0,0 +1,108 @@ +package view; + +import data_access.UserDAO; +import data_access.UserDAOImpl; +import entity.User; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LoginSignupPage extends JFrame { + private UserDAO userDAO; + + // UI components + private JTextField usernameField; + private JPasswordField passwordField; + private JButton loginButton; + private JButton signupButton; + + public LoginSignupPage() { + userDAO = new UserDAOImpl(); // Initialize with JSON-backed DAO + setTitle("Login or Sign Up"); + setSize(400, 300); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new GridLayout(4, 1, 10, 10)); + + // Username field + JPanel usernamePanel = new JPanel(); + usernamePanel.add(new JLabel("Username:")); + usernameField = new JTextField(15); + usernamePanel.add(usernameField); + add(usernamePanel); + + // Password field + JPanel passwordPanel = new JPanel(); + passwordPanel.add(new JLabel("Password:")); + passwordField = new JPasswordField(15); + passwordPanel.add(passwordField); + add(passwordPanel); + + // Login button + loginButton = new JButton("Login"); + loginButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleLogin(); + } + }); + + // Signup button + signupButton = new JButton("Sign Up"); + signupButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSignup(); + } + }); + + // Button panel + JPanel buttonPanel = new JPanel(); + buttonPanel.add(loginButton); + buttonPanel.add(signupButton); + add(buttonPanel); + + setVisible(true); + } + + private void handleLogin() { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + + if (userDAO.validateUser(username, password)) { + JOptionPane.showMessageDialog(this, "Login successful!"); + openHomePage(); + } else { + JOptionPane.showMessageDialog(this, "Invalid credentials. Please try again."); + } + } + + + private void handleSignup() { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + + // Check for empty username or password + if (username.trim().isEmpty() || password.trim().isEmpty()) { + JOptionPane.showMessageDialog(this, "Username and password cannot be empty."); + return; + } + + if (userDAO.findUserByUsername(username) != null) { + JOptionPane.showMessageDialog(this, "Username already exists. Please choose another."); + } else { + boolean success = userDAO.addUser(new User(username, password)); + if (success) { + JOptionPane.showMessageDialog(this, "Signup successful! You can now log in."); + } else { + JOptionPane.showMessageDialog(this, "Signup failed. Please try again."); + } + } + } + + private void openHomePage() { + new HomePage(); // Open the HomePage + dispose(); // Close the LoginSignupPage + } +} From fac155775c9809b7919f00c881104db0226165a2 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 11 Nov 2024 14:52:02 -0500 Subject: [PATCH 034/148] commented out unfinished functionality in views --- .../interface_adapter/BookmarkController.java | 52 +++++++++------ .../java/use_case/SearchBookmarkUseCase.java | 24 ++++++- src/main/java/view/BookmarkView.java | 64 ++++++++++--------- src/main/java/view/NutritionView.java | 3 +- 4 files changed, 87 insertions(+), 56 deletions(-) diff --git a/src/main/java/interface_adapter/BookmarkController.java b/src/main/java/interface_adapter/BookmarkController.java index 7203f5f3a..fcabc8fe9 100644 --- a/src/main/java/interface_adapter/BookmarkController.java +++ b/src/main/java/interface_adapter/BookmarkController.java @@ -1,22 +1,32 @@ -package interface_adapter; +/* +In progress; commented out everything for now so it doesn't crash the program. + */ -import entity.Recipe; -import use_case.SearchRecipeUseCase; - -import java.util.List; - -public class BookmarkController { - private final SearchBookmarkUseCase searchBookmarkUseCase; - - public BookmarkController(SearchBookmarkUseCase searchBookmarkUseCase) { - this.searchBookmarkUseCase = searchBookmarkUseCase; - } - - // TODO this method searches for the bookmarked recipes by ingredients contained - public List getBookmarksFromIngredients(List ingredients) { - return searchBookmarkUseCase. - } - - // TODO another method that searches for the bookmarked recipes by the name of recipe - public List getBookmarksFromNames(List names) {} -} +//package interface_adapter; +// +//import entity.Recipe; +//import use_case.SearchBookmarkUseCase; +//import use_case.SearchRecipeUseCase; +// +//import java.util.List; +// +//public class BookmarkController { +// // private final SearchBookmarkUseCase searchBookmarkUseCase; +// private final SearchRecipeUseCase searchRecipeUseCase; +// +// public BookmarkController(SearchRecipeUseCase searchRecipeUseCase) { +// this.searchRecipeUseCase = searchRecipeUseCase; +// } +// +//// public BookmarkController(SearchBookmarkUseCase searchBookmarkUseCase) { +//// this.searchBookmarkUseCase = searchBookmarkUseCase; +//// } +// +// // TODO this method searches for the bookmarked recipes by ingredients contained +// public List getBookmarksFromIngredients(List ingredients) { +// return searchRecipeUseCase.searchRecipes(ingredients); +// } +// +// // TODO another method that searches for the bookmarked recipes by the name of recipe +// public List getBookmarksFromNames(List names) {} +//} diff --git a/src/main/java/use_case/SearchBookmarkUseCase.java b/src/main/java/use_case/SearchBookmarkUseCase.java index e6b91a4cd..91f94d2bf 100644 --- a/src/main/java/use_case/SearchBookmarkUseCase.java +++ b/src/main/java/use_case/SearchBookmarkUseCase.java @@ -1,4 +1,22 @@ -package use_case; +/* +In progress; commented out everything for now so it doesn't crash the program. + */ -public class SearchBookmarkUseCase { -} +//package use_case; +// +//import data_access.RecipeDAO; +//import entity.Recipe; +// +//import java.util.List; +// +//public class SearchBookmarkUseCase { +// private final RecipeDAO recipeDAO; +// +// public SearchRecipeUseCase(RecipeDAO recipeDAO) { +// this.recipeDAO = recipeDAO; +// } +// +// public List searchRecipes(List ingredients) { +// return recipeDAO.getRecipesByIngredients(ingredients); +// } +//} diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index 0463b4a75..fdd2c68ad 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -1,7 +1,7 @@ package view; import entity.Recipe; -import interface_adapter.BookmarkController; +// import interface_adapter.BookmarkController; import javax.swing.*; import java.awt.*; @@ -17,7 +17,7 @@ public class BookmarkView extends JFrame { // attributes for default view private JList bookmarkList; - private BookmarkController controller; + // private BookmarkController controller; private User user; // button to view individual recipes @@ -27,8 +27,7 @@ public class BookmarkView extends JFrame { /* Generates the default view of a user's bookmarked recipes. */ - public BookmarkView(BookmarkController controller, User user) { - this.controller = controller; + public BookmarkView(User user) { // TODO getBookmarks() method this.bookmarkList = new JList<>(); @@ -39,7 +38,8 @@ public BookmarkView(BookmarkController controller, User user) { add(new JScrollPane(bookmarkList), BorderLayout.CENTER); - // view recipe button for each item in the list + // TODO view recipe button for each item in the list - in progress + /* bookmarkList.add("view recipe", viewRecipeButton); // or do JButton b = new JButton("view recipe")? viewRecipeButton.addActionListener(new ActionListener() { @@ -50,31 +50,33 @@ public void actionPerformed(ActionEvent e) { dispose(); } }); - - // Search function - ingredientInput = new JTextField(20); - searchButton = new JButton("Find Recipes by Ingredients"); - recipeListByIngredient = new JList<>(); - - nameInput = new JTextField(20); - searchButton = new JButton("Find Recipes by Name"); - recipeListByName = new JList<>(); - - searchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String ingredientsText = ingredientInput.getText(); - java.util.List ingredients = Arrays.asList(ingredientsText.split(",")); - List recipes = controller.getRecipes(ingredients); - - String[] recipeNames = recipes.stream() - .map(recipe -> recipe.getName() + " - " + recipe.getUrl()) - .toArray(String[]::new); - recipeList.setListData(recipeNames); - } - }); - - // Display search bar + */ + + // TODO Search function - in progress +// ingredientInput = new JTextField(20); +// searchButton = new JButton("Find Recipes by Ingredients"); +// recipeListByIngredient = new JList<>(); +// +// nameInput = new JTextField(20); +// searchButton = new JButton("Find Recipes by Name"); +// recipeListByName = new JList<>(); +// +// searchButton.addActionListener(new ActionListener() { +// @Override +// public void actionPerformed(ActionEvent e) { +// String ingredientsText = ingredientInput.getText(); +// java.util.List ingredients = Arrays.asList(ingredientsText.split(",")); +// List recipes = controller.getRecipes(ingredients); +// +// String[] recipeNames = recipes.stream() +// .map(recipe -> recipe.getName() + " - " + recipe.getUrl()) +// .toArray(String[]::new); +// recipeList.setListData(recipeNames); +// } +// }); + + // TODO Display search bar + /* final JPanel panel = new JPanel(); panel.add(new JLabel("Enter ingredients (comma-separated):")); panel.add(ingredientInput); @@ -83,7 +85,7 @@ public void actionPerformed(ActionEvent e) { // Button to view individual recipes add(viewRecipeButton, BorderLayout.SOUTH); - + */ setVisible(true); } } diff --git a/src/main/java/view/NutritionView.java b/src/main/java/view/NutritionView.java index 8d4460a78..0d7d94ade 100644 --- a/src/main/java/view/NutritionView.java +++ b/src/main/java/view/NutritionView.java @@ -9,12 +9,13 @@ public class NutritionView extends JFrame { private JList nutritionList; NutritionView(Recipe recipe) { + // TODO initialize nutritionList with Nutrition object this.nutritionList = new JList<>(); + add(nutritionList); setTitle(recipe.getName()); setSize(800, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - add(ingredientsList = new JList<>()); setVisible(true); } } From 535a280d1e7e2a2139f1d83a3295ce7a52e5fc3f Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 11 Nov 2024 15:01:31 -0500 Subject: [PATCH 035/148] Changed Main to temporarily display the incomplete BookmarkView upon running the program. --- src/main/java/app/Main.java | 9 +++++++++ src/main/java/view/BookmarkView.java | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index a30d2bc46..a331cd054 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,10 +1,19 @@ package app; +import entity.User; +import view.BookmarkView; import view.LoginSignupPage; public class Main { + // mock user object to test bookmarks page + private static User user; + public static void main(String[] args) { // Launch the login/signup page as the first view new LoginSignupPage(); + + // Temporarily launch the bookmarks page upon running the program + user = new User("Test_username", "Test_password"); + new BookmarkView(user); } } diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index fdd2c68ad..01e117dc4 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -1,6 +1,7 @@ package view; import entity.Recipe; +import entity.User; // import interface_adapter.BookmarkController; import javax.swing.*; @@ -31,8 +32,7 @@ public BookmarkView(User user) { // TODO getBookmarks() method this.bookmarkList = new JList<>(); - // TODO getname() method - setTitle(user.getname() + "'s Bookmarks"); + setTitle(user.getUsername() + "'s Bookmarks"); setSize(800, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); From 6a98788e675819643b274964bd89c2fd29ca412a Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 11 Nov 2024 15:08:36 -0500 Subject: [PATCH 036/148] Nothing --- src/main/java/view/BookmarkView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index fdd2c68ad..cac83bec2 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -1,6 +1,7 @@ package view; import entity.Recipe; +import entity.User; // import interface_adapter.BookmarkController; import javax.swing.*; @@ -32,7 +33,7 @@ public BookmarkView(User user) { this.bookmarkList = new JList<>(); // TODO getname() method - setTitle(user.getname() + "'s Bookmarks"); + setTitle(user.getUsername() + "'s Bookmarks"); setSize(800, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); From 132fa8ab4448804d5f4000f05d534d35905bab20 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Tue, 12 Nov 2024 13:33:34 -0500 Subject: [PATCH 037/148] =?UTF-8?q?=EF=BC=88Test)=20add=20the=20test=20par?= =?UTF-8?q?t=20for=20this=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{main => test}/java/ViewNutritionDataAccessTest.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{main => test}/java/ViewNutritionDataAccessTest.java (100%) diff --git a/src/main/java/ViewNutritionDataAccessTest.java b/src/test/java/ViewNutritionDataAccessTest.java similarity index 100% rename from src/main/java/ViewNutritionDataAccessTest.java rename to src/test/java/ViewNutritionDataAccessTest.java From fa694104119694246f2d34763c30f76ec44855c8 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Tue, 12 Nov 2024 13:39:33 -0500 Subject: [PATCH 038/148] fix the error --- .../java/data_access/ViewNutritionDataAccessImpl.java | 4 ++-- src/main/java/entity/Nutrition.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/data_access/ViewNutritionDataAccessImpl.java b/src/main/java/data_access/ViewNutritionDataAccessImpl.java index 7a2f1609a..c54e5c7fb 100644 --- a/src/main/java/data_access/ViewNutritionDataAccessImpl.java +++ b/src/main/java/data_access/ViewNutritionDataAccessImpl.java @@ -45,9 +45,9 @@ public List getNutritionDataForRecipe(int recipeId) { String name = nutrientJson.getString("name"); double amount = nutrientJson.getDouble("amount"); String unit = nutrientJson.getString("unit"); - double percentDailyNeeds = nutrientJson.optDouble("percentDailyNeeds", 0); + double percentOfDailyNeeds = nutrientJson.optDouble("percentOfDailyNeeds", 0); - Nutrition nutrition = new Nutrition(name, amount, unit, percentDailyNeeds); + Nutrition nutrition = new Nutrition(name, amount, unit, percentOfDailyNeeds); nutritionList.add(nutrition); } } diff --git a/src/main/java/entity/Nutrition.java b/src/main/java/entity/Nutrition.java index f403a9fee..136d65f7e 100644 --- a/src/main/java/entity/Nutrition.java +++ b/src/main/java/entity/Nutrition.java @@ -7,13 +7,13 @@ public class Nutrition { private final String name; private final double amount; private final String unit; - private final double percentDailyNeeds; + private final double percentOfDailyNeeds; public Nutrition(String name, double amount, String unit, double percentDailyNeeds) { this.name = name; this.amount = amount; this.unit = unit; - this.percentDailyNeeds = percentDailyNeeds; + this.percentOfDailyNeeds = percentDailyNeeds; } public String getName() { @@ -29,11 +29,11 @@ public String getUnit() { } public double getPercentDailyNeeds() { - return percentDailyNeeds; + return percentOfDailyNeeds; } @Override public String toString() { - return name + ": " + amount + " " + unit + " (" + percentDailyNeeds + "% of daily needs)"; + return name + ": " + amount + " " + unit + " (" + percentOfDailyNeeds + "% of daily needs)"; } } From 04de60fd5edd0648044b64d15080a82a5cb8538d Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Tue, 12 Nov 2024 20:34:38 -0500 Subject: [PATCH 039/148] added methods to User and Recipe, modified BookmarkView, and added test display in Main for bookmarks page --- .idea/jarRepositories.xml | 5 +++++ src/main/java/app/Main.java | 11 ++++++++++- src/main/java/entity/Recipe.java | 6 ++++++ src/main/java/entity/User.java | 17 ++++++++++++++++- src/main/java/view/BookmarkView.java | 14 ++++++++------ 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 712ab9d98..947ef884c 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -6,6 +6,11 @@

+ * For example, the LoggedInView listens for two kinds of property changes; + * it can use the property name to distinguish which property has changed. + * @param propertyName the label for the property that was changed + */ + public void firePropertyChanged(String propertyName) { + this.support.firePropertyChange(propertyName, null, this.state); + } + + /** + * Adds a PropertyChangeListener to this ViewModel. + * @param listener The PropertyChangeListener to be added + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.support.addPropertyChangeListener(listener); + } +} diff --git a/src/main/java/interface_adapter/BookmarkController.java b/src/main/java/interface_adapter/bookmark/BookmarkController.java similarity index 57% rename from src/main/java/interface_adapter/BookmarkController.java rename to src/main/java/interface_adapter/bookmark/BookmarkController.java index bf2c49eaf..12acec8e0 100644 --- a/src/main/java/interface_adapter/BookmarkController.java +++ b/src/main/java/interface_adapter/bookmark/BookmarkController.java @@ -1,13 +1,23 @@ -package interface_adapter; +package interface_adapter.bookmark; +import interface_adapter.RecipeListController; import use_case.SearchRecipeListByIngredientUseCase; import use_case.SearchRecipeListByNameUseCase; public class BookmarkController extends RecipeListController { + private final BookmarkInputBoundary bookmarkUseCaseInteractor; public BookmarkController(SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase, - SearchRecipeListByNameUseCase searchRecipeListByNameUseCase) { + SearchRecipeListByNameUseCase searchRecipeListByNameUseCase, + BookmarkInputBoundary bookmarkUseCaseInteractor) { super(searchRecipeListByIngredientUseCase, searchRecipeListByNameUseCase); + this.bookmarkUseCaseInteractor = bookmarkUseCaseInteractor; + } + + public void execute(...) { + final BookmarkInputData bookmarkInputData = new BookmarkInputData(...); + + bookmarkUseCaseInteractor.execute(bookmarkInputData); } // public addBookmark(Recipe recipe) { diff --git a/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java b/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java new file mode 100644 index 000000000..154bd9054 --- /dev/null +++ b/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java @@ -0,0 +1,14 @@ +package interface_adapter.bookmark; + +public class BookmarkPresenter implements BookmarkOutputBoundary { + private final BookmarkViewModel bookmarkViewModel; + + public BookmarkPresenter(BookmarkViewModel bookmarkViewModel) { + this.bookmarkViewModel = bookmarkViewModel; + } + + @Override + public void prepareSuccessView(BookmarkOutputData outputData) { + bookmarkViewModel.firePrepertyChanged("property name..."); + } +} diff --git a/src/main/java/view/ViewManager.java b/src/main/java/view/ViewManager.java new file mode 100644 index 000000000..7f9f07ec6 --- /dev/null +++ b/src/main/java/view/ViewManager.java @@ -0,0 +1,34 @@ +package view; + +import java.awt.CardLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JPanel; + +import interface_adapter.ViewManagerModel; + +/** + * The View Manager for the program. It listens for property change events + * in the ViewManagerModel and updates which View should be visible. + */ +public class ViewManager implements PropertyChangeListener { + private final CardLayout cardLayout; + private final JPanel views; + private final ViewManagerModel viewManagerModel; + + public ViewManager(JPanel views, CardLayout cardLayout, ViewManagerModel viewManagerModel) { + this.views = views; + this.cardLayout = cardLayout; + this.viewManagerModel = viewManagerModel; + this.viewManagerModel.addPropertyChangeListener(this); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("state")) { + final String viewModelName = (String) evt.getNewValue(); + cardLayout.show(views, viewModelName); + } + } +} From 6ad4373f4bbfa8e430f0c92b4268e82de4c02064 Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Sun, 1 Dec 2024 21:31:59 -0500 Subject: [PATCH 132/148] created accessibility report and added some initial points --- accessibility-report.md | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 accessibility-report.md diff --git a/accessibility-report.md b/accessibility-report.md new file mode 100644 index 000000000..8a8ffbc34 --- /dev/null +++ b/accessibility-report.md @@ -0,0 +1,42 @@ +# Accessibility Report + +## Question 1 +For each Principle of Universal Design, write 2-3 sentences — or point form notes — explaining which features of your program adhere to that principle. If you do not have any such features, you can either: + +(a) Describe features that you could implement in the future that would adhere to the principle or +(b) Explain why the principle does not apply to a program like yours. + +### Principle 1: Equitable Use +- this software can be accessed from any smart device - it does not require any special hardware or plugins +- we can potentially find an API that allows users to download the entire database of recipes, so that those without connection to internet can still use the software +### Principle 2: Flexibility in Use +- there are not a lot of parameters that a user can change currently... +### Principle 3: Simple and Intuitive Use +- the design of the software is quite minimal, and all the buttons, text fields and dropdown menus are labelled +- since it does not have explicit instructions, this software might require the user to have some experience with softwares in general (and features such as signing in, searching and bookmarking). We can improve this by adding a user manual on the welcome page +### Principle 4: Perceptible Information +- this software has the potential of being incorporated with screen readers, so that it is accessible to those with vision impairments +- in the future, we can add settings that allow users to adjust font size and background colour +### Principle 5: Tolerance for Error +- right now, empty entries on the login/signup and search pages would not crash the program +- in the future, we can incorporate an autocorrect feature into the search function, so that users can still get results if they do not know how to spell an ingredient correctly +### Principle 6: Low Physical Effort +- this software is inclusive because it does not require a lot of physical effort to use +- the shopping list feature can potentially be connected to grocery delivery services, so users who cannot purchase groceries themselves can still benefit from the recipe generator +### Principle 7: Size and Space for Approach and Use +- not applicable here since the software is not connect to any hardware + + +*(for Q2 and 3, just wrote down some bullet points for now)* + +## Question 2 +- university students learning to cook +- busy parents who are too busy for meal planning +- people looking to explore new cuisines and recipes +- athletes who are conscious about nutrition + +## Question 3 +Write a paragraph about whether or not your program is less likely to be used by certain demographics. Your discussion here should be informed by the content of our embedded ethics modules this term. +- older people living alone who are not familiar with technology, or are more familiar with traditional recipes +- people from cultural groups whose cuisines are not represented in the recipe database +- people with religious dietary restrictions (no filters for halal, kosher, etc.) From 7310565ee3260d1c3784cfdf5a0773ac0954199b Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 2 Dec 2024 00:37:39 -0500 Subject: [PATCH 133/148] fixed checkstyle errors --- src/main/java/data_access/AppConstants.java | 4 + src/main/java/data_access/GetRecipeId.java | 31 +++--- src/main/java/data_access/RecipeDAO.java | 4 +- src/main/java/data_access/SpoonacularAPI.java | 93 +++++++++--------- src/main/java/data_access/UserDAO.java | 11 ++- .../ViewNutritionDataAccessImpl.java | 50 +++++----- src/main/java/entity/Ingredient.java | 29 +----- src/main/java/entity/Recipe.java | 26 ++--- src/main/java/entity/User.java | 10 +- .../interface_adapter/RecipeController.java | 5 +- .../RecipeListController.java | 3 +- .../interface_adapter/RecipeListState.java | 37 +++++++ .../RecipeListViewModel.java | 14 +++ .../SearchRecipePresenter.java | 5 +- .../bookmark/BookmarkController.java | 2 +- ...earchRecipeListByIngredientController.java | 28 ++++++ ...SearchRecipeListByIngredientPresenter.java | 27 ++++++ .../use_case/SearchRecipeOutputBoundary.java | 5 - .../SearchRecipeInputBoundary.java | 2 +- .../SearchRecipeInputData.java | 2 +- .../SearchRecipeInteractor.java | 4 +- .../SearchRecipeOutputBoundary.java | 5 + .../SearchRecipeOutputData.java | 2 +- .../SearchRecipeUseCase.java | 7 +- ...peListByIngredientDataAccessInterface.java | 4 + ...chRecipeListByIngredientInputBoundary.java | 14 +++ ...SearchRecipeListByIngredientInputData.java | 19 ++++ ...earchRecipeListByIngredientInteractor.java | 34 +++++++ ...hRecipeListByIngredientOutputBoundary.java | 18 ++++ ...earchRecipeListByIngredientOutputData.java | 28 ++++++ .../SearchRecipeListByIngredientUseCase.java | 2 +- src/main/java/view/BookmarkView.java | 14 +-- src/main/java/view/FolderView.java | 9 -- src/main/java/view/HomePage.java | 68 +++++-------- src/main/java/view/IndividualRecipeView.java | 50 ++++------ src/main/java/view/LoginSignupPage.java | 66 +++++++------ src/main/java/view/NutritionView.java | 24 ++--- src/main/java/view/RecipeListView.java | 58 ++++------- src/main/java/view/RecipeView.java | 28 +++--- src/main/java/view/ShoppingListGUI.java | 52 +++++----- .../nutrition}/NutritionViewTest.java | 2 + .../ViewNutritionDataAccessTest.java | 2 + ...hRecipeListByIngredientInteractorTest.java | 96 +++++++++++++++++++ 43 files changed, 619 insertions(+), 375 deletions(-) create mode 100644 src/main/java/interface_adapter/RecipeListState.java create mode 100644 src/main/java/interface_adapter/RecipeListViewModel.java rename src/main/java/{use_case => interface_adapter}/SearchRecipePresenter.java (77%) create mode 100644 src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java create mode 100644 src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java delete mode 100644 src/main/java/use_case/SearchRecipeOutputBoundary.java rename src/main/java/use_case/{ => search_recipe}/SearchRecipeInputBoundary.java (76%) rename src/main/java/use_case/{ => search_recipe}/SearchRecipeInputData.java (90%) rename src/main/java/use_case/{ => search_recipe}/SearchRecipeInteractor.java (84%) create mode 100644 src/main/java/use_case/search_recipe/SearchRecipeOutputBoundary.java rename src/main/java/use_case/{ => search_recipe}/SearchRecipeOutputData.java (90%) rename src/main/java/use_case/{ => search_recipe}/SearchRecipeUseCase.java (77%) create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputBoundary.java create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputBoundary.java create mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputData.java rename src/main/java/use_case/{ => search_recipe_list_by_ingredient}/SearchRecipeListByIngredientUseCase.java (96%) rename src/test/java/{ => use_case/nutrition}/NutritionViewTest.java (86%) rename src/test/java/{ => use_case/nutrition}/ViewNutritionDataAccessTest.java (96%) create mode 100644 src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java diff --git a/src/main/java/data_access/AppConstants.java b/src/main/java/data_access/AppConstants.java index febc9cbc1..b658f7c75 100644 --- a/src/main/java/data_access/AppConstants.java +++ b/src/main/java/data_access/AppConstants.java @@ -4,6 +4,10 @@ public final class AppConstants { public static final String API_KEY = "9c13038e18cc407ca440bd756225cd13"; public static final String NUMBER_OF_RESULTS = "5"; + public static final String FONT = "Arial"; + public static final int WELCOME_FONT_SIZE = 18; + public static final int BUTTON_FONT_SIZE = 16; + public static final int RESPONSE_CODE_200 = 200; // Prevent instantiation private AppConstants() { diff --git a/src/main/java/data_access/GetRecipeId.java b/src/main/java/data_access/GetRecipeId.java index 815db4e8d..4d3df7954 100644 --- a/src/main/java/data_access/GetRecipeId.java +++ b/src/main/java/data_access/GetRecipeId.java @@ -4,6 +4,7 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; + import org.json.JSONArray; import org.json.JSONObject; @@ -14,42 +15,42 @@ public class GetRecipeId { public static int getRecipeIdByName(String recipeName) { try { + final String apiUrl = BASE_URL + "?query=" + recipeName.replace(" ", "+") + "&apiKey=" + API_KEY; - String apiUrl = BASE_URL + "?query=" + recipeName.replace(" ", "+") + "&apiKey=" + API_KEY; - - URL url = new URL(apiUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + final URL url = new URL(apiUrl); + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); - int responseCode = connection.getResponseCode(); - if (responseCode != 200) { + final int responseCode = connection.getResponseCode(); + if (responseCode != AppConstants.RESPONSE_CODE_200) { System.out.println("Error: API returned response code " + responseCode); return -1; } - BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - StringBuilder response = new StringBuilder(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + final StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); - JSONObject jsonResponse = new JSONObject(response.toString()); - JSONArray results = jsonResponse.getJSONArray("results"); + final JSONObject jsonResponse = new JSONObject(response.toString()); + final JSONArray results = jsonResponse.getJSONArray("results"); - if (results.length() == 0) { + if (results.isEmpty()) { System.out.println("No recipes found for: " + recipeName); return -1; } - JSONObject firstResult = results.getJSONObject(0); - int recipeId = firstResult.getInt("id"); + final JSONObject firstResult = results.getJSONObject(0); + final int recipeId = firstResult.getInt("id"); System.out.println("Recipe ID for " + recipeName + ": " + recipeId); return recipeId; - } catch (Exception e) { - e.printStackTrace(); + } + catch (Exception event) { + event.printStackTrace(); return -1; } } diff --git a/src/main/java/data_access/RecipeDAO.java b/src/main/java/data_access/RecipeDAO.java index db5bf515c..1f869664e 100644 --- a/src/main/java/data_access/RecipeDAO.java +++ b/src/main/java/data_access/RecipeDAO.java @@ -1,9 +1,9 @@ package data_access; -import entity.Recipe; - import java.util.List; +import entity.Recipe; + public interface RecipeDAO { List getRecipesByIngredients(List ingredients); diff --git a/src/main/java/data_access/SpoonacularAPI.java b/src/main/java/data_access/SpoonacularAPI.java index 8659c9ff7..d3705c942 100644 --- a/src/main/java/data_access/SpoonacularAPI.java +++ b/src/main/java/data_access/SpoonacularAPI.java @@ -6,11 +6,13 @@ import java.util.Arrays; import java.util.List; import java.util.Scanner; + import org.json.JSONArray; import org.json.JSONObject; public class SpoonacularAPI { - private final String apiKey = AppConstants.API_KEY; + private static final String API_KEY = AppConstants.API_KEY; + private static final String RESPONSE_METHOD = "GET"; // Define pantry staples private final List pantryStaples = Arrays.asList( @@ -19,36 +21,36 @@ public class SpoonacularAPI { // Fetch recipes based on the user's available ingredients public List getRecipesContaining(List availableIngredients) { - List recipes = new ArrayList<>(); + final List recipes = new ArrayList<>(); try { - String ingredients = String.join(",", availableIngredients); + final String ingredients = String.join(",", availableIngredients); // API call to fetch recipes based on available ingredients - String urlString = "https://api.spoonacular.com/recipes/findByIngredients?ingredients=" + ingredients - + "&number=10&ranking=2&apiKey=" + apiKey; - URL url = new URL(urlString); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - - Scanner scanner = new Scanner(conn.getInputStream()); - StringBuilder jsonResponse = new StringBuilder(); + final String urlString = "https://api.spoonacular.com/recipes/findByIngredients?ingredients=" + ingredients + + "&number=10&ranking=2&apiKey=" + API_KEY; + final URL url = new URL(urlString); + final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(RESPONSE_METHOD); + + final Scanner scanner = new Scanner(conn.getInputStream()); + final StringBuilder jsonResponse = new StringBuilder(); while (scanner.hasNext()) { jsonResponse.append(scanner.nextLine()); } scanner.close(); // Parse the JSON response - JSONArray jsonArray = new JSONArray(jsonResponse.toString()); + final JSONArray jsonArray = new JSONArray(jsonResponse.toString()); for (int i = 0; i < jsonArray.length(); i++) { - JSONObject recipe = jsonArray.getJSONObject(i); + final JSONObject recipe = jsonArray.getJSONObject(i); // Get the missed ingredients for the recipe - JSONArray missedIngredients = recipe.getJSONArray("missedIngredients"); + final JSONArray missedIngredients = recipe.getJSONArray("missedIngredients"); // Count how many ingredients are truly missing int missingCount = 0; for (int j = 0; j < missedIngredients.length(); j++) { - String ingredientName = missedIngredients.getJSONObject(j).getString("name").toLowerCase(); + final String ingredientName = missedIngredients.getJSONObject(j).getString("name").toLowerCase(); if (!availableIngredients.contains(ingredientName)) { missingCount++; } @@ -59,57 +61,59 @@ public List getRecipesContaining(List availableIngredients) { recipes.add(recipe.getString("title")); } } - } catch (Exception e) { - System.err.println("Error fetching recipes: " + e.getMessage()); + } + catch (Exception exception) { + System.err.println("Error fetching recipes: " + exception.getMessage()); } return recipes; } // Fetch missing ingredients for selected recipes public List getMissingIngredients(List selectedRecipes, List availableIngredients) { - List shoppingList = new ArrayList<>(); + final List shoppingList = new ArrayList<>(); try { for (String recipeTitle : selectedRecipes) { - String searchUrl = "https://api.spoonacular.com/recipes/complexSearch?query=" + recipeTitle - + "&apiKey=" + apiKey; - URL searchRequest = new URL(searchUrl); - HttpURLConnection searchConn = (HttpURLConnection) searchRequest.openConnection(); - searchConn.setRequestMethod("GET"); - - Scanner searchScanner = new Scanner(searchConn.getInputStream()); - StringBuilder searchResponse = new StringBuilder(); + final String searchUrl = "https://api.spoonacular.com/recipes/complexSearch?query=" + recipeTitle + + "&apiKey=" + API_KEY; + final URL searchRequest = new URL(searchUrl); + final HttpURLConnection searchConn = (HttpURLConnection) searchRequest.openConnection(); + searchConn.setRequestMethod(RESPONSE_METHOD); + + final Scanner searchScanner = new Scanner(searchConn.getInputStream()); + final StringBuilder searchResponse = new StringBuilder(); while (searchScanner.hasNext()) { searchResponse.append(searchScanner.nextLine()); } searchScanner.close(); - JSONObject searchResult = new JSONObject(searchResponse.toString()); - JSONArray results = searchResult.getJSONArray("results"); + final JSONObject searchResult = new JSONObject(searchResponse.toString()); + final JSONArray results = searchResult.getJSONArray("results"); - if (results.length() > 0) { - JSONObject recipe = results.getJSONObject(0); - int id = recipe.getInt("id"); + if (!results.isEmpty()) { + final JSONObject recipe = results.getJSONObject(0); + final int id = recipe.getInt("id"); // Fetch ingredients for the recipe using its ID - String ingredientUrl = "https://api.spoonacular.com/recipes/" + id + "/information?includeNutrition=false&apiKey=" + apiKey; - URL ingredientRequest = new URL(ingredientUrl); - HttpURLConnection ingredientConn = (HttpURLConnection) ingredientRequest.openConnection(); - ingredientConn.setRequestMethod("GET"); - - Scanner ingredientScanner = new Scanner(ingredientConn.getInputStream()); - StringBuilder ingredientResponse = new StringBuilder(); + final String ingredientUrl = "https://api.spoonacular.com/recipes/" + id + + "/information?includeNutrition=false&apiKey=" + API_KEY; + final URL ingredientRequest = new URL(ingredientUrl); + final HttpURLConnection ingredientConn = (HttpURLConnection) ingredientRequest.openConnection(); + ingredientConn.setRequestMethod(RESPONSE_METHOD); + + final Scanner ingredientScanner = new Scanner(ingredientConn.getInputStream()); + final StringBuilder ingredientResponse = new StringBuilder(); while (ingredientScanner.hasNext()) { ingredientResponse.append(ingredientScanner.nextLine()); } ingredientScanner.close(); - JSONObject ingredientJson = new JSONObject(ingredientResponse.toString()); - JSONArray extendedIngredients = ingredientJson.getJSONArray("extendedIngredients"); + final JSONObject ingredientJson = new JSONObject(ingredientResponse.toString()); + final JSONArray extendedIngredients = ingredientJson.getJSONArray("extendedIngredients"); // Include missing ingredients that are not in the user's list and not pantry staples for (int i = 0; i < extendedIngredients.length(); i++) { - JSONObject ingredient = extendedIngredients.getJSONObject(i); - String ingredientName = ingredient.getString("original").toLowerCase(); + final JSONObject ingredient = extendedIngredients.getJSONObject(i); + final String ingredientName = ingredient.getString("original").toLowerCase(); // Check if the ingredient is not already available and not a pantry staple (substring match) if (!availableIngredients.contains(ingredientName) && !containsPantryStaple(ingredientName)) { @@ -118,8 +122,9 @@ public List getMissingIngredients(List selectedRecipes, List getNutritionDataForRecipe(int recipeId) { - OkHttpClient client = new OkHttpClient(); + final OkHttpClient client = new OkHttpClient(); - String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; - String url = BASE_URL + endpoint + "?apiKey=" + API_KEY; + final String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; + final String url = BASE_URL + endpoint + "?apiKey=" + API_KEY; - Request request = new Request.Builder().url(url).build(); + final Request request = new Request.Builder().url(url).build(); - List nutritionList = new ArrayList<>(); + final List nutritionList = new ArrayList<>(); try (Response response = client.newCall(request).execute()) { if (response.isSuccessful() && response.body() != null) { - String jsonResponse = response.body().string(); - JSONObject jsonObject = new JSONObject(jsonResponse); + final String jsonResponse = response.body().string(); + final JSONObject jsonObject = new JSONObject(jsonResponse); if (jsonObject.has("nutrients")) { - JSONArray nutrients = jsonObject.getJSONArray("nutrients"); + final JSONArray nutrients = jsonObject.getJSONArray("nutrients"); for (int i = 0; i < nutrients.length(); i++) { - JSONObject nutrientJson = nutrients.getJSONObject(i); + final JSONObject nutrientJson = nutrients.getJSONObject(i); - String name = nutrientJson.getString("name"); - double amount = nutrientJson.getDouble("amount"); - String unit = nutrientJson.getString("unit"); - double percentOfDailyNeeds = nutrientJson.optDouble("percentOfDailyNeeds", 0); + final String name = nutrientJson.getString("name"); + final double amount = nutrientJson.getDouble("amount"); + final String unit = nutrientJson.getString("unit"); + final double percentOfDailyNeeds = nutrientJson.optDouble("percentOfDailyNeeds", 0); - Nutrition nutrition = new Nutrition(name, amount, unit, percentOfDailyNeeds); + final Nutrition nutrition = new Nutrition(name, amount, unit, percentOfDailyNeeds); nutritionList.add(nutrition); } } - return nutritionList; - } else { + } + else { System.err.println("Request failed with code: " + response.code()); } - } catch (IOException e) { - e.printStackTrace(); + } + catch (IOException exception) { + exception.printStackTrace(); } return nutritionList; } diff --git a/src/main/java/entity/Ingredient.java b/src/main/java/entity/Ingredient.java index 77b23a79b..9b7b548c7 100644 --- a/src/main/java/entity/Ingredient.java +++ b/src/main/java/entity/Ingredient.java @@ -4,31 +4,17 @@ * Represents an ingredient from our recipe. */ public class Ingredient { - // Attributes based on API documentation - private double amount; - private int id; - private String image; - - // TODO implement the Measures class? - // private Measures measures; - - private String unit; - - private String name; - - // probably don't need these -// private String original; -// private String originalName; -// private String unit; + private final double amount; + private final String unit; + private final String name; public Ingredient(String name, double amount, String unit) { - // TODO parse the original JSONArray representation of ingredients into our format this.name = name; this.amount = amount; this.unit = unit; } - // getters (shouldn't need setters) + // getters (no need for setters) public String getName() { return name; } @@ -41,11 +27,4 @@ public String getUnit() { return unit; } - public int getId() { - return id; - } - - public String getImage() { - return image; - } } diff --git a/src/main/java/entity/Recipe.java b/src/main/java/entity/Recipe.java index a47abbf15..8ae520af5 100644 --- a/src/main/java/entity/Recipe.java +++ b/src/main/java/entity/Recipe.java @@ -1,32 +1,30 @@ package entity; import java.util.List; -import java.util.Objects; /** * This class returns a recipe from the API. */ public class Recipe { - private int id; - private String name; - private String url; - private List ingredients; - private String image; - private String cuisineType; - // should correspond to "cuisines" in API recipes - private String dietaryType; + private final String name; + private final String url; + private final List ingredients; + private final String image; + private final String cuisineType; + private final String dietaryType; - // TODO update constructor to initialize all the attributes public Recipe(String name, String url, List ingredients, String image) { - this.id = id; this.name = name; this.url = url; this.ingredients = ingredients; this.image = image; + this.cuisineType = ""; + this.dietaryType = ""; } // overloading the constructor - public Recipe(String name, String url, List ingredients, String image, String cuisineType, String dietaryType) { + public Recipe(String name, String url, List ingredients, String image, String cuisineType, + String dietaryType) { this.name = name; this.url = url; this.ingredients = ingredients; @@ -36,10 +34,6 @@ public Recipe(String name, String url, List ingredients, String imag } // getters - public int getId() { - return id; - } - public String getName() { return name; } diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java index 6e57a9b59..2b03134c0 100644 --- a/src/main/java/entity/User.java +++ b/src/main/java/entity/User.java @@ -8,9 +8,9 @@ public class User { private String username; private String password; - private List bookmarks; - private List recentlyViewed; - private Map> folders; + private final List bookmarks; + private final List recentlyViewed; + private final Map> folders; public User(String username, String password) { this.username = username; @@ -73,7 +73,9 @@ public List getFolder(String folder) { System.out.println("No such folder: " + folder); return List.of(); } - return folders.get(folder); + else { + return folders.get(folder); + } } /* diff --git a/src/main/java/interface_adapter/RecipeController.java b/src/main/java/interface_adapter/RecipeController.java index 0479d6d05..672cd4a4a 100644 --- a/src/main/java/interface_adapter/RecipeController.java +++ b/src/main/java/interface_adapter/RecipeController.java @@ -1,14 +1,15 @@ package interface_adapter; import use_case.SearchRecipe.SearchRecipeInputBoundary; -import use_case.SearchRecipe.SearchRecipeInputData; +import use_case.search_recipe.SearchRecipeInputData; +import use_case.search_recipe.SearchRecipeUseCase; import java.util.List; public class RecipeController { private final SearchRecipeInputBoundary searchInteractor; - public RecipeController(SearchRecipeInputBoundary searchInteractor) { + public RecipeController(SearchRecipeUseCase searchInteractor) { this.searchInteractor = searchInteractor; } diff --git a/src/main/java/interface_adapter/RecipeListController.java b/src/main/java/interface_adapter/RecipeListController.java index ff1fd6984..5e2ca3be7 100644 --- a/src/main/java/interface_adapter/RecipeListController.java +++ b/src/main/java/interface_adapter/RecipeListController.java @@ -1,8 +1,7 @@ package interface_adapter; -import entity.Ingredient; import entity.Recipe; -import use_case.SearchRecipeListByIngredientUseCase; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.SearchRecipeListByNameUseCase; import java.util.List; diff --git a/src/main/java/interface_adapter/RecipeListState.java b/src/main/java/interface_adapter/RecipeListState.java new file mode 100644 index 000000000..a37ae5698 --- /dev/null +++ b/src/main/java/interface_adapter/RecipeListState.java @@ -0,0 +1,37 @@ +package interface_adapter; + +/** + * The State information representing the recipe list. + */ +public class RecipeListState { + private String username = ""; + + private String password = ""; + private String passwordError; + + public RecipeListState(???) { + username = copy.username; + password = copy.password; + passwordError = copy.passwordError; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setPasswordError(String passwordError) { + this.passwordError = passwordError; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/interface_adapter/RecipeListViewModel.java b/src/main/java/interface_adapter/RecipeListViewModel.java new file mode 100644 index 000000000..54a1da32e --- /dev/null +++ b/src/main/java/interface_adapter/RecipeListViewModel.java @@ -0,0 +1,14 @@ +package interface_adapter; + +/** + * The View Model for the RecipeListView. + */ +public class RecipeListViewModel extends ViewModel { + + public RecipeListViewModel() { + // TODO + super("???"); + setState(new RecipeListState()); + } + +} diff --git a/src/main/java/use_case/SearchRecipePresenter.java b/src/main/java/interface_adapter/SearchRecipePresenter.java similarity index 77% rename from src/main/java/use_case/SearchRecipePresenter.java rename to src/main/java/interface_adapter/SearchRecipePresenter.java index a8fa81131..42f9c110d 100644 --- a/src/main/java/use_case/SearchRecipePresenter.java +++ b/src/main/java/interface_adapter/SearchRecipePresenter.java @@ -1,8 +1,8 @@ package interface_adapter; import entity.Recipe; -import use_case.SearchRecipe.SearchRecipeOutputBoundary; -import use_case.SearchRecipe.SearchRecipeOutputData; +import use_case.search_recipe.SearchRecipeOutputBoundary; +import use_case.search_recipe.SearchRecipeOutputData; import java.util.List; @@ -17,4 +17,5 @@ public void presentRecipes(SearchRecipeOutputData outputData) { public List getRecipes() { return recipes; } + } diff --git a/src/main/java/interface_adapter/bookmark/BookmarkController.java b/src/main/java/interface_adapter/bookmark/BookmarkController.java index 12acec8e0..4b17b78b6 100644 --- a/src/main/java/interface_adapter/bookmark/BookmarkController.java +++ b/src/main/java/interface_adapter/bookmark/BookmarkController.java @@ -1,7 +1,7 @@ package interface_adapter.bookmark; import interface_adapter.RecipeListController; -import use_case.SearchRecipeListByIngredientUseCase; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.SearchRecipeListByNameUseCase; public class BookmarkController extends RecipeListController { diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java new file mode 100644 index 000000000..b93613f28 --- /dev/null +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java @@ -0,0 +1,28 @@ +package interface_adapter.search_recipe_list_by_ingredient.; + +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputData; + +import java.util.List; + +/** + * Controller for the Search Recipe List By Ingredient Use Case. + */ +public class SearchRecipeListByIngredientController { + private final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor; + + public SearchRecipeListByIngredientController( + SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor) { + this.searchRecipeListByIngredientUseCaseInteractor = searchRecipeListByIngredientUseCaseInteractor; + } + + /** + * Executes the Search Recipe List By Ingredient Use Case. + * @param ingredients the ingredients entered by the user + */ + public void execute(List ingredients) { + final SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData = new SearchRecipeListByIngredientInputData(ingredients); + + searchRecipeListByIngredientUseCaseInteractor.execute(searchRecipeListByIngredientInputData); + } +} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java new file mode 100644 index 000000000..2ae7594f8 --- /dev/null +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java @@ -0,0 +1,27 @@ +package interface_adapter.search_recipe_list_by_ingredient; + +import interface_adapter.RecipeListViewModel; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; + +/** + * The Presenter for the Search Recipe List By Ingredients Use Case. + */ +public class SearchRecipeListByIngredientPresenter implements SearchRecipeListByIngredientOutputBoundary { + + private final RecipeListViewModel recipeListViewModel; + + public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewModel) { + this.recipeListViewModel = recipeListViewModel; + } + + @Override + public void prepareSuccessView() { + // TODO + recipeListViewModel.firePropertyChanged("???"); + } + + @Override + public void prepareFailView(String error) { + // note: this use case currently can't fail + } +} diff --git a/src/main/java/use_case/SearchRecipeOutputBoundary.java b/src/main/java/use_case/SearchRecipeOutputBoundary.java deleted file mode 100644 index 47de40b0f..000000000 --- a/src/main/java/use_case/SearchRecipeOutputBoundary.java +++ /dev/null @@ -1,5 +0,0 @@ -package use_case.SearchRecipe; - -public interface SearchRecipeOutputBoundary { - void presentRecipes(SearchRecipeOutputData outputData); -} diff --git a/src/main/java/use_case/SearchRecipeInputBoundary.java b/src/main/java/use_case/search_recipe/SearchRecipeInputBoundary.java similarity index 76% rename from src/main/java/use_case/SearchRecipeInputBoundary.java rename to src/main/java/use_case/search_recipe/SearchRecipeInputBoundary.java index 912f6f74c..3e74b455a 100644 --- a/src/main/java/use_case/SearchRecipeInputBoundary.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeInputBoundary.java @@ -1,4 +1,4 @@ -package use_case.SearchRecipe; +package use_case.search_recipe; public interface SearchRecipeInputBoundary { void searchRecipes(SearchRecipeInputData inputData); diff --git a/src/main/java/use_case/SearchRecipeInputData.java b/src/main/java/use_case/search_recipe/SearchRecipeInputData.java similarity index 90% rename from src/main/java/use_case/SearchRecipeInputData.java rename to src/main/java/use_case/search_recipe/SearchRecipeInputData.java index f3e171e48..548c18ee0 100644 --- a/src/main/java/use_case/SearchRecipeInputData.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeInputData.java @@ -1,4 +1,4 @@ -package use_case.SearchRecipe; +package use_case.search_recipe; import java.util.List; diff --git a/src/main/java/use_case/SearchRecipeInteractor.java b/src/main/java/use_case/search_recipe/SearchRecipeInteractor.java similarity index 84% rename from src/main/java/use_case/SearchRecipeInteractor.java rename to src/main/java/use_case/search_recipe/SearchRecipeInteractor.java index 8b67c7dd1..20158c8d6 100644 --- a/src/main/java/use_case/SearchRecipeInteractor.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeInteractor.java @@ -1,4 +1,4 @@ -package use_case.SearchRecipe; +package use_case.search_recipe; import data_access.RecipeDAO; import entity.Recipe; @@ -20,7 +20,7 @@ public void searchRecipes(SearchRecipeInputData inputData) { List recipes = recipeDAO.getRecipesByIngredients(inputData.getIngredients()); // Prepare output data and pass to presenter - SearchRecipeOutputData outputData = new SearchRecipeOutputData(recipes); + use_case.search_recipe.SearchRecipeOutputData outputData = new SearchRecipeOutputData(recipes); presenter.presentRecipes(outputData); } } diff --git a/src/main/java/use_case/search_recipe/SearchRecipeOutputBoundary.java b/src/main/java/use_case/search_recipe/SearchRecipeOutputBoundary.java new file mode 100644 index 000000000..d5fd14148 --- /dev/null +++ b/src/main/java/use_case/search_recipe/SearchRecipeOutputBoundary.java @@ -0,0 +1,5 @@ +package use_case.search_recipe; + +public interface SearchRecipeOutputBoundary { + void presentRecipes(use_case.search_recipe.SearchRecipeOutputData outputData); +} diff --git a/src/main/java/use_case/SearchRecipeOutputData.java b/src/main/java/use_case/search_recipe/SearchRecipeOutputData.java similarity index 90% rename from src/main/java/use_case/SearchRecipeOutputData.java rename to src/main/java/use_case/search_recipe/SearchRecipeOutputData.java index 7925d4008..7118dfeeb 100644 --- a/src/main/java/use_case/SearchRecipeOutputData.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeOutputData.java @@ -1,4 +1,4 @@ -package use_case.SearchRecipe; +package use_case.search_recipe; import entity.Recipe; diff --git a/src/main/java/use_case/SearchRecipeUseCase.java b/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java similarity index 77% rename from src/main/java/use_case/SearchRecipeUseCase.java rename to src/main/java/use_case/search_recipe/SearchRecipeUseCase.java index 0c24a6cff..cf98c5338 100644 --- a/src/main/java/use_case/SearchRecipeUseCase.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java @@ -1,7 +1,8 @@ -package use_case; +package use_case.search_recipe; import data_access.RecipeDAO; import entity.Recipe; +import interface_adapter.SearchRecipePresenter; import java.util.List; @@ -9,13 +10,13 @@ public class SearchRecipeUseCase implements use_case.SearchRecipe.SearchRecipeIn private final RecipeDAO recipeDAO; private final use_case.SearchRecipe.SearchRecipeOutputBoundary presenter; - public SearchRecipeUseCase(RecipeDAO recipeDAO, use_case.SearchRecipe.SearchRecipeOutputBoundary presenter) { + public SearchRecipeUseCase(RecipeDAO recipeDAO, SearchRecipePresenter presenter) { this.recipeDAO = recipeDAO; this.presenter = presenter; } @Override - public void searchRecipes(use_case.SearchRecipe.SearchRecipeInputData inputData) { + public void searchRecipes(SearchRecipeInputData inputData) { List ingredients = inputData.getIngredients(); // Fetch recipes from DAO diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java new file mode 100644 index 000000000..7152c7100 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java @@ -0,0 +1,4 @@ +package use_case.search_recipe_list_by_ingredient; + +public interface SearchRecipeListByIngredientDataAccessInterface { +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputBoundary.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputBoundary.java new file mode 100644 index 000000000..5dbc7cb96 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputBoundary.java @@ -0,0 +1,14 @@ +package use_case.search_recipe_list_by_ingredient; + +/** + * The Search Recipe List By Ingredient Use Case. + */ +public interface SearchRecipeListByIngredientInputBoundary { + + /** + * Execute the Search Recipe List By Ingredient Use Case. + * @param searchRecipeListByIngredientInputData the input data for this use case + */ + void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData); + +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java new file mode 100644 index 000000000..bc3c69659 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java @@ -0,0 +1,19 @@ +package use_case.search_recipe_list_by_ingredient; + +import java.util.List; + +/** + * The input data for the Change Password Use Case. + */ +public class SearchRecipeListByIngredientInputData { + + private final List ingredients; + + public SearchRecipeListByIngredientInputData(List ingredients) { + this.ingredients = ingredients; + } + + List getIngredients() { + return ingredients; + } +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java new file mode 100644 index 000000000..6d8c6bc77 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java @@ -0,0 +1,34 @@ +package use_case.search_recipe_list_by_ingredient; + +import entity.User; + +public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { + + /** + * The Change Password Interactor. + */ + private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; + private final SearchRecipeListByIngredientOutputBoundary userPresenter; + private final User user; + + public SearchRecipeListByIngredientInteractor( + SearchRecipeListByIngredientDataAccessInterface searchRecipeListByIngredientDataAccessInterface, + SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary) { + this.userDataAccessObject = searchRecipeListByIngredientDataAccessInterface; + this.userPresenter = searchRecipeListByIngredientOutputBoundary; + } + + @Override + public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { + final User user = new User(searchRecipeListByIngredientInputData.getusername(), + searchRecipeListByIngredientInputData.getPassword(), + searchRecipeListByIngredientInputData.get... + ) + userDataAccessObject.metjod(user); + + final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = + new SearchRecipeListByIngredientOutputData(recipes, + false); + userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); + } +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputBoundary.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputBoundary.java new file mode 100644 index 000000000..1b2d4f027 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.search_recipe_list_by_ingredient; + +/** + * The output boundary for the Search Recipe List by Ingredient Use Case. + */ +public interface SearchRecipeListByIngredientOutputBoundary { + /** + * Prepares the success view for the Search Recipe List by Ingredient Use Case. + * @param outputData the output data + */ + void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData); + + /** + * Prepares the failure view for the Search Recipe List by Ingredient Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputData.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputData.java new file mode 100644 index 000000000..83586d997 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientOutputData.java @@ -0,0 +1,28 @@ +package use_case.search_recipe_list_by_ingredient; + +import entity.Recipe; + +import java.util.List; + +/** + * Output Data for the Search Recipe List By Ingredient Use Case. + */ +public class SearchRecipeListByIngredientOutputData { + + private final List recipes; + + private final boolean useCaseFailed; + + public SearchRecipeListByIngredientOutputData(List recipes, boolean useCaseFailed) { + this.recipes = recipes; + this.useCaseFailed = useCaseFailed; + } + + public List getRecipes() { + return recipes; + } + + public boolean isUseCaseFailed() { + return useCaseFailed; + } +} diff --git a/src/main/java/use_case/SearchRecipeListByIngredientUseCase.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java similarity index 96% rename from src/main/java/use_case/SearchRecipeListByIngredientUseCase.java rename to src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java index 00f32a84c..1c23cc0bf 100644 --- a/src/main/java/use_case/SearchRecipeListByIngredientUseCase.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java @@ -1,4 +1,4 @@ -package use_case; +package use_case.search_recipe_list_by_ingredient; import entity.Ingredient; import entity.Recipe; diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index b8209b32c..7377c7caf 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -3,11 +3,12 @@ import java.awt.*; import java.util.List; -import entity.Recipe; -import entity.User; +import javax.swing.*; + import org.jetbrains.annotations.NotNull; -import javax.swing.*; +import entity.Recipe; +import entity.User; public class BookmarkView extends RecipeListView { @@ -24,14 +25,11 @@ public BookmarkView(User user, String folderName) { @NotNull private JComboBox getDropdown(User user) { - // Create an array of dropdown options - // TODO populate the list of options as the user creates folders String[] options = user.getFolders().keySet().toArray(new String[0]); final JComboBox dropdown = new JComboBox<>(options); final JLabel dropdownLabel = new JLabel("Select a folder:"); dropdown.add(dropdownLabel); - // Add an action listener to handle selections dropdown.addActionListener(event -> { final String selectedOption = (String) dropdown.getSelectedItem(); System.out.println(selectedOption + "folder name in bookmark view"); @@ -48,10 +46,6 @@ private JPanel getCreateFolderPanel(User user) { final JButton addFolderButton = new JButton("Create Folder"); addFolderButton.addActionListener(event -> { final String folderName = textField.getText(); - // for testing - System.out.println(folderName); - // not sure if this is necessary - user.addFolder(folderName); userDAO.addFolderToFile(user.getUsername(), folderName); }); createFolderPanel.add(textField); diff --git a/src/main/java/view/FolderView.java b/src/main/java/view/FolderView.java index 003b74b07..12e564146 100644 --- a/src/main/java/view/FolderView.java +++ b/src/main/java/view/FolderView.java @@ -1,4 +1,3 @@ -// Commented out for now - implement if there's time package view; import java.util.List; @@ -6,16 +5,10 @@ import entity.Recipe; import entity.User; -import javax.swing.*; -// import interface_adapter.BookmarkController; - -// implements ActionListener removed for now public class FolderView extends RecipeListView { -// private final List recipes; public FolderView(User user, String folderName) { super(user, folderName); -// this.recipes = getRecipeList(userDAO.findUserByUsername(user.getUsername()), folderName); setTitle(folderName); } @@ -26,8 +19,6 @@ protected List getRecipeList(User user1) { @Override protected List getRecipeList(User user1, String folder) { - System.out.println(user1.getUsername()); - System.out.println(folder + "aaaaa"); return userDAO.getFolderFromFile(user1.getUsername(), folder); } } diff --git a/src/main/java/view/HomePage.java b/src/main/java/view/HomePage.java index f1d90a891..016e8c10c 100644 --- a/src/main/java/view/HomePage.java +++ b/src/main/java/view/HomePage.java @@ -1,24 +1,20 @@ package view; -import data_access.SpoonacularRecipeDAO; -import entity.Ingredient; -import entity.Recipe; -import entity.User; -import interface_adapter.RecipeController; -import interface_adapter.SearchRecipePresenter; -import interface_adapter.filter_recipes.FilterRecipesController; - -import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.List; +import javax.swing.*; + +import data_access.AppConstants; +import data_access.SpoonacularAPI; +import data_access.SpoonacularRecipeDAO; +import entity.User; +import interface_adapter.RecipeController; +import interface_adapter.SearchRecipePresenter; import interface_adapter.ShoppingListController; import use_case.ShoppingListUseCase; -import data_access.SpoonacularAPI; -import use_case.SearchRecipeUseCase; -import use_case.filter_recipes.FilterRecipesInteractor; +import use_case.search_recipe.SearchRecipeUseCase; public class HomePage extends JFrame { private final User user; @@ -26,31 +22,30 @@ public class HomePage extends JFrame { public HomePage(User user) { this.user = user; setTitle("Welcome to the Recipe Finder"); - setSize(400, 300); + // setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); final JLabel welcomeLabel = new JLabel("Welcome to the Recipe Finder!", JLabel.CENTER); - welcomeLabel.setFont(new Font("Arial", Font.BOLD, 18)); + welcomeLabel.setFont(new Font(AppConstants.FONT, Font.BOLD, AppConstants.WELCOME_FONT_SIZE)); add(welcomeLabel, BorderLayout.CENTER); final JButton startButton = new JButton("Start Searching Recipes"); - startButton.setFont(new Font("Arial", Font.PLAIN, 16)); + startButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); startButton.addActionListener(e -> { // Create the presenter - SearchRecipePresenter presenter = new SearchRecipePresenter(); + final SearchRecipePresenter presenter = new SearchRecipePresenter(); // Create the use case interactor - use_case.SearchRecipeUseCase interactor = new use_case.SearchRecipeUseCase(new SpoonacularRecipeDAO(), presenter); + final SearchRecipeUseCase interactor = new SearchRecipeUseCase(new SpoonacularRecipeDAO(), presenter); // Create the controller - RecipeController controller = new RecipeController(interactor); + final RecipeController controller = new RecipeController(interactor); // Open the RecipeView and pass dependencies new RecipeView(controller, presenter, user, new SpoonacularRecipeDAO()); }); // Shopping List Button final JButton shoppingListButton = new JButton("Shopping List"); - shoppingListButton.setFont(new Font("Arial", Font.PLAIN, 16)); - + shoppingListButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); // Action Listener for Shopping List Button shoppingListButton.addActionListener(new ActionListener() { @Override @@ -64,35 +59,21 @@ public void actionPerformed(ActionEvent e) { } }); + // Bookmarks Button final JButton bookmarksButton = new JButton("Bookmarks"); - bookmarksButton.setFont(new Font("Arial", Font.PLAIN, 16)); - bookmarksButton.addActionListener(e -> { -// // Open BookmarkView in a separate window -// // User user = new User("Test_username", "Test_password"); -// Ingredient ingredient1 = new Ingredient("Test_ingredient1"); -// Ingredient ingredient2 = new Ingredient("Test_Ingredient2"); -// String imageURL = "https://img.spoonacular.com/recipes/716429-556x370.jpg"; -// Recipe recipe1 = new Recipe("name1", "url1", java.util.List.of(ingredient1, ingredient2), imageURL); -// Recipe recipe2 = new Recipe("name2", "url2", List.of(ingredient1, ingredient2), imageURL); -// user.addBookmark(recipe1); -// user.addBookmark(recipe2); + bookmarksButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); + bookmarksButton.addActionListener(event -> { new BookmarkView(this.user, null); }); + // Recently Viewed Button final JButton recentlyViewedButton = new JButton("Recently Viewed"); - recentlyViewedButton.setFont(new Font("Arial", Font.PLAIN, 16)); - recentlyViewedButton.addActionListener(e -> { - // User user = new User("Test_username", "Test_password"); -// Ingredient ingredient1 = new Ingredient("Test_ingredient1"); -// Ingredient ingredient2 = new Ingredient("Test_Ingredient2"); -// String imageURL = "https://img.spoonacular.com/recipes/716429-556x370.jpg"; -// Recipe recipe1 = new Recipe("name1", "url1", java.util.List.of(ingredient1, ingredient2), imageURL); -// Recipe recipe2 = new Recipe("name2", "url2", List.of(ingredient1, ingredient2), imageURL); -// user.addRecentlyViewed(recipe1); -// user.addRecentlyViewed(recipe2); + recentlyViewedButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); + recentlyViewedButton.addActionListener(event -> { new RecentlyViewedView(this.user, null); }); + // Putting the buttons together final JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2, 2, 10, 10)); panel.add(startButton); @@ -100,8 +81,7 @@ public void actionPerformed(ActionEvent e) { panel.add(recentlyViewedButton); panel.add(shoppingListButton); add(panel, BorderLayout.SOUTH); -// add(startButton, BorderLayout.SOUTH); -// add(bookmarksButton, BorderLayout.SOUTH); + pack(); setVisible(true); } } diff --git a/src/main/java/view/IndividualRecipeView.java b/src/main/java/view/IndividualRecipeView.java index c5e972231..3331f5166 100644 --- a/src/main/java/view/IndividualRecipeView.java +++ b/src/main/java/view/IndividualRecipeView.java @@ -20,7 +20,6 @@ public class IndividualRecipeView extends JFrame implements ActionListener { private final JButton bookmarkButton; private final JButton urlButton; private JList ingredientsJLIst; - // if we want to display the used and missed ingredients separately... private JList usedIngredientsJList; private final Recipe recipe; private URL imageUrl; private ImageIcon imageIcon; @@ -33,9 +32,18 @@ public IndividualRecipeView(Recipe recipe, User user) { this.user = user; this.userDAO = new UserDAOImpl(); - // Add this recipe to the user's recently viewed list + // Add this recipe to the user's recently viewed list upon the page's opening userDAO.addRecentlyViewedToFile(this.user.getUsername(), this.recipe); + // Set up JFrame properties + setTitle(recipe.getName()); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + // Initialize main panel with vertical BoxLayout + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + setContentPane(mainPanel); + // Initialize ingredient list final DefaultListModel listModel = new DefaultListModel<>(); for (Ingredient ingredient : recipe.getIngredients()) { @@ -44,23 +52,14 @@ public IndividualRecipeView(Recipe recipe, User user) { ingredientsJLIst = new JList<>(listModel); final JScrollPane scrollPane = new JScrollPane(ingredientsJLIst); scrollPane.setPreferredSize(ingredientsJLIst.getPreferredScrollableViewportSize()); + mainPanel.add(scrollPane); // Initialize buttons nutritionButton = new JButton("Nutrition"); bookmarkButton = new JButton("Bookmark"); urlButton = new JButton("Open Recipe in Browser"); - // Set up JFrame properties - setTitle(recipe.getName()); - setSize(800, 300); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - // Main panel with vertical BoxLayout - you need a Layout for each Panel - final JPanel mainPanel = new JPanel(); - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - setContentPane(mainPanel); - - // initializing image + // Initialize image try { // Specify the image URL if (recipe.getImage() == null) { @@ -84,7 +83,7 @@ public IndividualRecipeView(Recipe recipe, User user) { imageLabel.setHorizontalAlignment(SwingConstants.CENTER); imageLabel.setVerticalAlignment(SwingConstants.CENTER); - // Add the label to the frame + // Add the label to the main panel mainPanel.add(imageLabel, BorderLayout.CENTER); } catch (Exception e) { @@ -95,10 +94,7 @@ public IndividualRecipeView(Recipe recipe, User user) { e.printStackTrace(); } - // Add components to main panel - mainPanel.add(scrollPane); - - // Button panel with horizontal BoxLayout + // Initialize button panel with horizontal BoxLayout final JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(nutritionButton); @@ -106,46 +102,38 @@ public IndividualRecipeView(Recipe recipe, User user) { buttonPanel.add(bookmarkButton); buttonPanel.add(Box.createRigidArea(new Dimension(10, 0))); buttonPanel.add(urlButton); - mainPanel.add(buttonPanel); - // Add action listeners + // Add action listeners to buttons nutritionButton.addActionListener(this); bookmarkButton.addActionListener(this); urlButton.addActionListener(this); - // Create an array of dropdown options - // TODO populate the list of options as the user creates folders + // Initialize dropdown menu for adding the recipe to folders String[] options = user.getFolders().keySet().toArray(new String[0]); - // Create a JComboBox with the options final JComboBox dropdown = new JComboBox<>(options); final JLabel dropdownLabel = new JLabel("Add this recipe to a folder"); dropdown.add(dropdownLabel); - // Add an action listener to handle selections dropdown.addActionListener(folderEvent -> { final String selectedOption = (String) dropdown.getSelectedItem(); - // TODO logic of adding a recipe to a folder - // final List folder = userDAO.getFolderFromFile(user.getUsername(), selectedOption); userDAO.addRecipeToFolderInFile(this.user.getUsername(), selectedOption, this.recipe); JOptionPane.showMessageDialog(this, "Recipe added to " + selectedOption + "!"); }); - // Add the dropdown to the frame - add(dropdown); + mainPanel.add(dropdown); // Display the frame + pack(); setVisible(true); } - GetRecipeId getRecipeId = new GetRecipeId(); - @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == nutritionButton) { + GetRecipeId getRecipeId = new GetRecipeId(); int recipeId = getRecipeId.getRecipeIdByName(recipe.getName()); new NutritionView(recipeId); } else if (event.getSource() == bookmarkButton) { - // TODO complete bookmark function if (!user.getBookmarks().contains(recipe)) { userDAO.addBookmarkToFile(user.getUsername(), recipe); JOptionPane.showMessageDialog(this, "Recipe added to bookmarks!"); diff --git a/src/main/java/view/LoginSignupPage.java b/src/main/java/view/LoginSignupPage.java index 67ed514ad..d258253a4 100644 --- a/src/main/java/view/LoginSignupPage.java +++ b/src/main/java/view/LoginSignupPage.java @@ -1,46 +1,45 @@ package view; -import data_access.UserDAO; -import data_access.UserDAOImpl; -import entity.User; - -import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import javax.swing.*; + +import data_access.UserDAO; +import data_access.UserDAOImpl; +import entity.User; + public class LoginSignupPage extends JFrame { - private UserDAO userDAO; + private final UserDAO userDao; // UI components - private JTextField usernameField; - private JPasswordField passwordField; - private JButton loginButton; - private JButton signupButton; + private final JTextField usernameField; + private final JPasswordField passwordField; public LoginSignupPage() { - userDAO = new UserDAOImpl(); // Initialize with JSON-backed DAO + // Initialize with JSON-backed DAO + userDao = new UserDAOImpl(); setTitle("Login or Sign Up"); - setSize(400, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new GridLayout(4, 1, 10, 10)); // Username field - JPanel usernamePanel = new JPanel(); + final JPanel usernamePanel = new JPanel(); usernamePanel.add(new JLabel("Username:")); usernameField = new JTextField(15); usernamePanel.add(usernameField); add(usernamePanel); // Password field - JPanel passwordPanel = new JPanel(); + final JPanel passwordPanel = new JPanel(); passwordPanel.add(new JLabel("Password:")); passwordField = new JPasswordField(15); passwordPanel.add(passwordField); add(passwordPanel); // Login button - loginButton = new JButton("Login"); + final JButton loginButton = new JButton("Login"); loginButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -49,7 +48,7 @@ public void actionPerformed(ActionEvent e) { }); // Signup button - signupButton = new JButton("Sign Up"); + final JButton signupButton = new JButton("Sign Up"); signupButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -63,47 +62,52 @@ public void actionPerformed(ActionEvent e) { buttonPanel.add(signupButton); add(buttonPanel); + // Display the frame + pack(); setVisible(true); } private void handleLogin() { - String username = usernameField.getText(); - String password = new String(passwordField.getPassword()); + final String username = usernameField.getText(); + final String password = new String(passwordField.getPassword()); - if (userDAO.validateUser(username, password)) { + if (userDao.validateUser(username, password)) { JOptionPane.showMessageDialog(this, "Login successful!"); - openHomePage(userDAO.findUserByUsername(username)); + openHomePage(userDao.findUserByUsername(username)); dispose(); - } else { + } + else { JOptionPane.showMessageDialog(this, "Invalid credentials. Please try again."); } } - private void handleSignup() { - String username = usernameField.getText(); - String password = new String(passwordField.getPassword()); + final String username = usernameField.getText(); + final String password = new String(passwordField.getPassword()); // Check for empty username or password if (username.trim().isEmpty() || password.trim().isEmpty()) { JOptionPane.showMessageDialog(this, "Username and password cannot be empty."); - return; } - if (userDAO.findUserByUsername(username) != null) { + if (userDao.findUserByUsername(username) != null) { JOptionPane.showMessageDialog(this, "Username already exists. Please choose another."); - } else { - boolean success = userDAO.addUser(new User(username, password)); + } + else { + final boolean success = userDao.addUser(new User(username, password)); if (success) { JOptionPane.showMessageDialog(this, "Signup successful! You can now log in."); - } else { + } + else { JOptionPane.showMessageDialog(this, "Signup failed. Please try again."); } } } private void openHomePage(User user) { - new HomePage(user); // Open the HomePage - dispose(); // Close the LoginSignupPage + // Open the HomePage + new HomePage(user); + // Close the LoginSignupPage + dispose(); } } diff --git a/src/main/java/view/NutritionView.java b/src/main/java/view/NutritionView.java index 1ebded823..3d04bd251 100644 --- a/src/main/java/view/NutritionView.java +++ b/src/main/java/view/NutritionView.java @@ -1,27 +1,23 @@ package view; +import java.util.List; + +import javax.swing.*; + import data_access.ViewNutritionDataAccessImpl; import entity.Nutrition; import use_case.ViewNutritionDataAccessInterface; -import javax.swing.*; -import java.util.List; - public class NutritionView extends JFrame { - final private int recipeId; - private JList nutritionList; - private ViewNutritionDataAccessInterface dataAccess; public NutritionView(int recipeId) { - this.recipeId = recipeId; - this.nutritionList = new JList<>(); - this.dataAccess = new ViewNutritionDataAccessImpl(); - - List nutritionData = dataAccess.getNutritionDataForRecipe(recipeId); + final JList nutritionList = new JList<>(); + final ViewNutritionDataAccessInterface dataAccess = new ViewNutritionDataAccessImpl(); + final List nutritionData = dataAccess.getNutritionDataForRecipe(recipeId); - DefaultListModel listModel = new DefaultListModel<>(); + final DefaultListModel listModel = new DefaultListModel<>(); for (Nutrition nutrition : nutritionData) { - String displayText = String.format("%s: %.2f %s (%.2f%% of daily needs)", + final String displayText = String.format("%s: %.2f %s (%.2f%% of daily needs)", nutrition.getName(), nutrition.getAmount(), nutrition.getUnit(), @@ -33,8 +29,8 @@ public NutritionView(int recipeId) { add(new JScrollPane(nutritionList)); // setTitle(recipe.getName()); - setSize(800, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + pack(); setVisible(true); } } diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index a87a8f1d4..da08240a4 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -16,14 +16,12 @@ import entity.Recipe; import entity.User; import interface_adapter.RecipeListController; -import use_case.SearchRecipeListByIngredientUseCase; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.SearchRecipeListByNameUseCase; public abstract class RecipeListView extends JFrame implements ActionListener { - // attributes for default view private static User user; protected static UserDAOImpl userDAO; -// protected List recipes; protected final JList recipeList; protected final DefaultListModel listModel; private final RecipeListController controller; @@ -51,23 +49,19 @@ public RecipeListView(User user, String folderName) { new SearchRecipeListByNameUseCase(getRecipeList(userDAO.findUserByUsername(user.getUsername())))); this.spoonacularRecipeDAO = new SpoonacularRecipeDAO(); - setSize(800, 300); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - + // Initialize recipe list to display List recipes = new ArrayList<>(); + // Split into cases: BookmarkView/RecentlyViewedView and FolderView if (folderName == null) { recipes = getRecipeList(userDAO.findUserByUsername(user.getUsername())); } else { recipes = getRecipeList(userDAO.findUserByUsername(user.getUsername()), folderName); } - for (Recipe recipe : recipes) { this.listModel.addElement(recipe); } - this.recipeList.setModel(listModel); - // Wrap the JList in a JScrollPane final JScrollPane scrollPane = new JScrollPane(recipeList); scrollPane.setPreferredSize(recipeList.getPreferredScrollableViewportSize()); @@ -82,6 +76,7 @@ public RecipeListView(User user, String folderName) { // // Add components to the main panel // mainPanel.add(scrollPane); + // Make the recipe list clickable recipeList.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent event) { if (event.getClickCount() == 2) { @@ -98,7 +93,10 @@ public void mouseClicked(MouseEvent event) { } }); - // Display the two search bars + // Initialize and display the two search bars in a panel + final JPanel searchPanel = new JPanel(); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.Y_AXIS)); + // Search by ingredient ingredientSearchField = new JTextField(20); ingredientSearchButton = new JButton("Search recipes by ingredients"); final JPanel ingredientSearchPanel = new JPanel(); @@ -106,8 +104,8 @@ public void mouseClicked(MouseEvent event) { ingredientSearchPanel.add(ingredientSearchField); ingredientSearchPanel.add(ingredientSearchButton); ingredientSearchButton.addActionListener(this); - add(ingredientSearchPanel, BorderLayout.NORTH); - + searchPanel.add(ingredientSearchPanel); + // Search by recipe name recipeSearchField = new JTextField(20); recipeSearchButton = new JButton("Search recipes by name"); final JPanel recipeSearchPanel = new JPanel(); @@ -115,37 +113,17 @@ public void mouseClicked(MouseEvent event) { recipeSearchPanel.add(recipeSearchField); recipeSearchPanel.add(recipeSearchButton); recipeSearchButton.addActionListener(this); - add(recipeSearchPanel, BorderLayout.CENTER); + searchPanel.add(recipeSearchPanel); - // Clear search + // Clear search button clearSearchButton = new JButton("Clear Search"); clearSearchButton.addActionListener(this); - add(clearSearchButton, BorderLayout.WEST); + searchPanel.add(clearSearchButton, BorderLayout.EAST); - // TODO Search function - in progress -// ingredientInput = new JTextField(20); -// searchButton = new JButton("Find Recipes by Ingredients"); -// recipeListByIngredient = new JList<>(); -// -// nameInput = new JTextField(20); -// searchButton = new JButton("Find Recipes by Name"); -// recipeListByName = new JList<>(); -// -// searchButton.addActionListener(new ActionListener() { -// @Override -// public void actionPerformed(ActionEvent e) { -// String ingredientsText = ingredientInput.getText(); -// java.util.List ingredients = Arrays.asList(ingredientsText.split(",")); -// List recipes = controller.getRecipes(ingredients); -// -// String[] recipeNames = recipes.stream() -// .map(recipe -> recipe.getName() + " - " + recipe.getUrl()) -// .toArray(String[]::new); -// recipeList.setListData(recipeNames); -// } -// }); - - // search filters + // Add the search panel to the frame + add(searchPanel, BorderLayout.NORTH); + + // Search filters final JPanel filterPanel = new JPanel(new FlowLayout()); dietComboBox = new JComboBox<>(); cuisineComboBox = new JComboBox<>(); @@ -175,6 +153,8 @@ public void mouseClicked(MouseEvent event) { // } // }); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + pack(); setVisible(true); } diff --git a/src/main/java/view/RecipeView.java b/src/main/java/view/RecipeView.java index 00d7c9a95..fee1cc95b 100644 --- a/src/main/java/view/RecipeView.java +++ b/src/main/java/view/RecipeView.java @@ -1,13 +1,11 @@ package view; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.List; -import java.awt.*; import javax.swing.*; import entity.Recipe; @@ -17,18 +15,18 @@ import use_case.filter_recipes.FilterRecipesDataAccessInterface; public class RecipeView extends JFrame { - private JTextField ingredientInput; - private JButton searchButton; - // private JList recipeList; - commented out for now to make the double click function work - private JList recipeList; + private static final int TEXTFIELD_COLUMNS = 20; + + private final JTextField ingredientInput; + private final JList recipeList; private final DefaultListModel listModel; private final RecipeController controller; private final SearchRecipePresenter presenter; private final User user; - private FilterRecipesDataAccessInterface frDataAccessInterface; - private JComboBox dietComboBox; - private JComboBox cuisineComboBox; + private final FilterRecipesDataAccessInterface frDataAccessInterface; + private final JComboBox dietComboBox; + private final JComboBox cuisineComboBox; private final String defaultFilter; public RecipeView(RecipeController controller, SearchRecipePresenter presenter, User user, @@ -40,11 +38,10 @@ public RecipeView(RecipeController controller, SearchRecipePresenter presenter, this.defaultFilter = "Any"; setTitle("Recipe Generator"); - setSize(800, 400); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - ingredientInput = new JTextField(20); - searchButton = new JButton("Find Recipes"); + ingredientInput = new JTextField(TEXTFIELD_COLUMNS); + final JButton searchButton = new JButton("Find Recipes"); recipeList = new JList<>(); listModel = new DefaultListModel<>(); recipeList.setModel(listModel); @@ -94,12 +91,13 @@ public void mouseClicked(MouseEvent event) { dietComboBox.addActionListener(event -> applyFilters()); cuisineComboBox.addActionListener(event -> applyFilters()); + pack(); setVisible(true); } private void handleSearch() { - String ingredientsText = ingredientInput.getText(); - List ingredients = Arrays.asList(ingredientsText.split(",")); + final String ingredientsText = ingredientInput.getText(); + final List ingredients = Arrays.asList(ingredientsText.split(",")); listModel.clear(); // Delegate search to the controller when default filters applied if (defaultFilter.equals(dietComboBox.getSelectedItem()) diff --git a/src/main/java/view/ShoppingListGUI.java b/src/main/java/view/ShoppingListGUI.java index 220c6e939..029faa8d8 100644 --- a/src/main/java/view/ShoppingListGUI.java +++ b/src/main/java/view/ShoppingListGUI.java @@ -1,17 +1,15 @@ package view; -import entity.Recipe; -import interface_adapter.ShoppingListController; - -import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.List; +import javax.swing.*; + +import interface_adapter.ShoppingListController; + public class ShoppingListGUI { private final ShoppingListController controller; @@ -21,27 +19,26 @@ public ShoppingListGUI(ShoppingListController controller) { public void run() { // Main Frame - JFrame frame = new JFrame("Shopping List Generator"); + final JFrame frame = new JFrame("Shopping List Generator"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(600, 500); // Panel Layout - JPanel panel = new JPanel(); + final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); // Top Input Section - JPanel inputPanel = new JPanel(new FlowLayout()); - JLabel ingredientLabel = new JLabel("Enter Ingredients (comma-separated):"); - JTextField ingredientField = new JTextField(20); - JButton searchButton = new JButton("Search Recipes"); + final JPanel inputPanel = new JPanel(new FlowLayout()); + final JLabel ingredientLabel = new JLabel("Enter Ingredients (comma-separated):"); + final JTextField ingredientField = new JTextField(20); + final JButton searchButton = new JButton("Search Recipes"); inputPanel.add(ingredientLabel); inputPanel.add(ingredientField); inputPanel.add(searchButton); // Center Recipe List Section - DefaultListModel recipeListModel = new DefaultListModel<>(); - JList recipeList = new JList<>(recipeListModel); - JScrollPane recipeScrollPane = new JScrollPane(recipeList); + final DefaultListModel recipeListModel = new DefaultListModel<>(); + final JList recipeList = new JList<>(recipeListModel); + final JScrollPane recipeScrollPane = new JScrollPane(recipeList); recipeScrollPane.setBorder(BorderFactory.createTitledBorder("Select Recipes")); // TODO Make the recipe list clickable - will be complicated... @@ -66,10 +63,10 @@ public void run() { // }); // Bottom Generate Section - JButton generateButton = new JButton("Generate Shopping List"); - JTextArea shoppingListArea = new JTextArea(10, 50); + final JButton generateButton = new JButton("Generate Shopping List"); + final JTextArea shoppingListArea = new JTextArea(10, 50); shoppingListArea.setEditable(false); - JScrollPane shoppingListScrollPane = new JScrollPane(shoppingListArea); + final JScrollPane shoppingListScrollPane = new JScrollPane(shoppingListArea); shoppingListScrollPane.setBorder(BorderFactory.createTitledBorder("Shopping List")); // Add components to panel @@ -88,17 +85,17 @@ public void actionPerformed(ActionEvent e) { String ingredientInput = ingredientField.getText().trim(); if (ingredientInput.isEmpty()) { JOptionPane.showMessageDialog(frame, "Please enter ingredients!", "Error", JOptionPane.ERROR_MESSAGE); - return; } // Parse the user-provided ingredients - List availableIngredients = Arrays.asList(ingredientInput.split(",")); + final List availableIngredients = Arrays.asList(ingredientInput.split(",")); // Fetch recipes - List recipes = controller.getRecipesContaining(availableIngredients); + final List recipes = controller.getRecipesContaining(availableIngredients); if (recipes.isEmpty()) { JOptionPane.showMessageDialog(frame, "No recipes found!", "Info", JOptionPane.INFORMATION_MESSAGE); - } else { + } + else { recipeListModel.clear(); for (String recipe : recipes) { recipeListModel.addElement(recipe); @@ -111,27 +108,26 @@ public void actionPerformed(ActionEvent e) { generateButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - List selectedRecipes = recipeList.getSelectedValuesList(); - String ingredientInput = ingredientField.getText().trim(); + final List selectedRecipes = recipeList.getSelectedValuesList(); + final String ingredientInput = ingredientField.getText().trim(); if (ingredientInput.isEmpty()) { JOptionPane.showMessageDialog(frame, "Please enter ingredients!", "Error", JOptionPane.ERROR_MESSAGE); - return; } List availableIngredients = Arrays.asList(ingredientInput.split(",")); if (selectedRecipes.isEmpty()) { JOptionPane.showMessageDialog(frame, "Please select at least one recipe!", "Error", JOptionPane.ERROR_MESSAGE); - return; } // Generate the shopping list - String shoppingList = controller.generateShoppingList(selectedRecipes, availableIngredients); + final String shoppingList = controller.generateShoppingList(selectedRecipes, availableIngredients); shoppingListArea.setText(shoppingList); } }); // Show Frame + frame.pack(); frame.setVisible(true); } } diff --git a/src/test/java/NutritionViewTest.java b/src/test/java/use_case/nutrition/NutritionViewTest.java similarity index 86% rename from src/test/java/NutritionViewTest.java rename to src/test/java/use_case/nutrition/NutritionViewTest.java index 80938a2c8..6aa1a09e4 100644 --- a/src/test/java/NutritionViewTest.java +++ b/src/test/java/use_case/nutrition/NutritionViewTest.java @@ -1,3 +1,5 @@ +package use_case.nutrition; + import view.NutritionView; public class NutritionViewTest { diff --git a/src/test/java/ViewNutritionDataAccessTest.java b/src/test/java/use_case/nutrition/ViewNutritionDataAccessTest.java similarity index 96% rename from src/test/java/ViewNutritionDataAccessTest.java rename to src/test/java/use_case/nutrition/ViewNutritionDataAccessTest.java index 7f893aea6..776c5aef0 100644 --- a/src/test/java/ViewNutritionDataAccessTest.java +++ b/src/test/java/use_case/nutrition/ViewNutritionDataAccessTest.java @@ -1,3 +1,5 @@ +package use_case.nutrition; + import data_access.ViewNutritionDataAccessImpl; import entity.Nutrition; diff --git a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java new file mode 100644 index 000000000..980d84b32 --- /dev/null +++ b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java @@ -0,0 +1,96 @@ +package use_case.search_recipe_list_by_ingredient; + +import data_access.InMemoryUserDataAccessObject; +import entity.User; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class SearchRecipeListByIngredientInteractorTest { + private static final List ingredients = List.of("chicken", "mushroom", "tomato"); + + @Test + void successTest() { + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData(ingredients); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + + // For the success test, we need to add Paul to the data access repository before we log in. + User user = factory.create("Paul", "password"); + userRepository.save(user); + + + // This creates a successPresenter that tests whether the test case is as we expect. + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { + assertEquals("expected", ??user.getUsername()??); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void noMatchingResultsTest() { + LoginInputData inputData = new LoginInputData("Paul", "wrong"); + LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + + // For this failure test, we need to add Paul to the data access repository before we log in, and + // the passwords should not match. + UserFactory factory = new CommonUserFactory(); + User user = factory.create("Paul", "password"); + userRepository.save(user); + + // This creates a presenter that tests whether the test case is as we expect. + LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { + @Override + public void prepareSuccessView(LoginOutputData user) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String error) { + assertEquals("Incorrect password for \"Paul\".", error); + } + }; + + LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); + interactor.execute(inputData); + } + + @Test + void emptyQueryTest() { + LoginInputData inputData = new LoginInputData("Paul", "password"); + LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + + // Add Paul to the repo so that when we check later they already exist + + // This creates a presenter that tests whether the test case is as we expect. + LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { + @Override + public void prepareSuccessView(LoginOutputData user) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String error) { + assertEquals("Paul: Account does not exist.", error); + } + }; + + LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); + interactor.execute(inputData); + } +} \ No newline at end of file From 4af468f6c0188873e4dfdb2c338387ca97cb89ff Mon Sep 17 00:00:00 2001 From: cindyzhang Date: Mon, 2 Dec 2024 01:10:39 -0500 Subject: [PATCH 134/148] adjusted some views --- README.md | 13 +- .../interface_adapter/RecipeController.java | 2 +- .../interface_adapter/RecipeListState.java | 74 +++---- .../RecipeListViewModel.java | 28 +-- .../bookmark/BookmarkController.java | 52 ++--- .../bookmark/BookmarkPresenter.java | 28 +-- ...earchRecipeListByIngredientController.java | 56 ++--- ...SearchRecipeListByIngredientPresenter.java | 55 ++--- .../search_recipe/SearchRecipeUseCase.java | 6 +- ...peListByIngredientDataAccessInterface.java | 13 ++ ...earchRecipeListByIngredientInteractor.java | 68 +++---- src/main/java/view/BookmarkView.java | 9 +- src/main/java/view/IndividualRecipeView.java | 20 +- src/main/java/view/RecipeListView.java | 2 +- ...hRecipeListByIngredientInteractorTest.java | 192 +++++++++--------- 15 files changed, 319 insertions(+), 299 deletions(-) diff --git a/README.md b/README.md index e106ae546..10220b875 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Shuxin (Kate) Zhou * Member 3: Another feature provides detailed nutritional information for each recipe. Users can click a button to access data such as calorie counts, macronutrients (carbs, proteins, fats), and other key nutrients. This feature supports those who are tracking their nutrition or following specific dietary goals. It is important for the system to present this information clearly, accounting for potential variations in portion sizes and ingredient substitutions. -* Member 4: The system will also allow users to bookmark recipes for future reference. This feature enables users to save recipes they find interesting or useful and organize them into categories like "Dinner" or "Favorites." Additionally, a "Recently Viewed" section can help users retrieve recipes they forgot to bookmark. The ability to sync bookmarks across devices ensures easy access whether users are on a phone or computer. +* Member 4: The system will also allow users to bookmark recipes for future reference. This feature enables users to save recipes they find interesting or useful and organize them into categories like "Dinner" or "Favorites." Additionally, a "Recently Viewed" section can help users retrieve recipes they forgot to bookmark. * Member 5: Finally, the program offers a shopping list feature for adjacent recipes. When users are missing one or two ingredients for a recipe, the system will generate a shopping list with only the required items. This function can help users decide what to buy if they want to try new meals without doing extensive shopping. Export options, such as saving the list as a PDF or sharing it with other apps, enhance usability. The system will also need to distinguish between essential ingredients and common pantry staples to keep the lists concise. @@ -44,7 +44,7 @@ Shuxin (Kate) Zhou - ^ doesn't happen all the time tho - allow the user to choose how many recipes they want to see? Page flipping feature? - show random recipes feature? -- **allow users to create custom folders???** +- ~~**allow users to create custom folders???**~~ - allow users to press enter instead of using the search button? - ~~*add clickable url to individual recipe view*~~ - allow users to select ingredients from individual recipe view and add them to their shopping list? @@ -55,11 +55,12 @@ Shuxin (Kate) Zhou - display no recipes found in search view ~~- add clear recently viewed button~~ - modify how the close button works - so that you don't terminate the program when you close one window +- ~~auto adjust window size~~ - **add the fire property changed thingy so that the lists update in real time** - allow users to delete a bookmark ### folder view ### -- [ ] add create folder user interface - either create a new button on home page or in BookmarkView -- [ ] add dropdown menu of folders in IndividualRecipeView - name it "add recipe to xxx folder" - or make it a checklist instead? -- [ ] add a save folder to json file thingy in UserDAOImpl -- make the whole thing a tag and filter system instead??? This would then entail changing the Recipe class +- [X] add create folder user interface - either create a new button on home page or in BookmarkView +- [X] add dropdown menu of folders in IndividualRecipeView - name it "add recipe to xxx folder" - or make it a checklist instead? +- [X] add a save folder to json file thingy in UserDAOImpl +- ~~make the whole thing a tag and filter system instead??? This would then entail changing the Recipe class~~ diff --git a/src/main/java/interface_adapter/RecipeController.java b/src/main/java/interface_adapter/RecipeController.java index 672cd4a4a..63d1d62b8 100644 --- a/src/main/java/interface_adapter/RecipeController.java +++ b/src/main/java/interface_adapter/RecipeController.java @@ -1,6 +1,6 @@ package interface_adapter; -import use_case.SearchRecipe.SearchRecipeInputBoundary; +import use_case.search_recipe.SearchRecipeInputBoundary; import use_case.search_recipe.SearchRecipeInputData; import use_case.search_recipe.SearchRecipeUseCase; diff --git a/src/main/java/interface_adapter/RecipeListState.java b/src/main/java/interface_adapter/RecipeListState.java index a37ae5698..02e8c0f9a 100644 --- a/src/main/java/interface_adapter/RecipeListState.java +++ b/src/main/java/interface_adapter/RecipeListState.java @@ -1,37 +1,37 @@ -package interface_adapter; - -/** - * The State information representing the recipe list. - */ -public class RecipeListState { - private String username = ""; - - private String password = ""; - private String passwordError; - - public RecipeListState(???) { - username = copy.username; - password = copy.password; - passwordError = copy.passwordError; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; - } - - public String getPassword() { - return password; - } -} +//package interface_adapter; +// +///** +// * The State information representing the recipe list. +// */ +//public class RecipeListState { +// private String username = ""; +// +// private String password = ""; +// private String passwordError; +// +// public RecipeListState(???) { +// username = copy.username; +// password = copy.password; +// passwordError = copy.passwordError; +// } +// +// public String getUsername() { +// return username; +// } +// +// public void setUsername(String username) { +// this.username = username; +// } +// +// public void setPassword(String password) { +// this.password = password; +// } +// +// public void setPasswordError(String passwordError) { +// this.passwordError = passwordError; +// } +// +// public String getPassword() { +// return password; +// } +//} diff --git a/src/main/java/interface_adapter/RecipeListViewModel.java b/src/main/java/interface_adapter/RecipeListViewModel.java index 54a1da32e..121499319 100644 --- a/src/main/java/interface_adapter/RecipeListViewModel.java +++ b/src/main/java/interface_adapter/RecipeListViewModel.java @@ -1,14 +1,14 @@ -package interface_adapter; - -/** - * The View Model for the RecipeListView. - */ -public class RecipeListViewModel extends ViewModel { - - public RecipeListViewModel() { - // TODO - super("???"); - setState(new RecipeListState()); - } - -} +//package interface_adapter; +// +///** +// * The View Model for the RecipeListView. +// */ +//public class RecipeListViewModel extends ViewModel { +// +// public RecipeListViewModel() { +// // TODO +// super("???"); +// setState(new RecipeListState()); +// } +// +//} diff --git a/src/main/java/interface_adapter/bookmark/BookmarkController.java b/src/main/java/interface_adapter/bookmark/BookmarkController.java index 4b17b78b6..7833f6f38 100644 --- a/src/main/java/interface_adapter/bookmark/BookmarkController.java +++ b/src/main/java/interface_adapter/bookmark/BookmarkController.java @@ -1,31 +1,31 @@ -package interface_adapter.bookmark; - -import interface_adapter.RecipeListController; -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -import use_case.SearchRecipeListByNameUseCase; - -public class BookmarkController extends RecipeListController { - private final BookmarkInputBoundary bookmarkUseCaseInteractor; - - public BookmarkController(SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase, - SearchRecipeListByNameUseCase searchRecipeListByNameUseCase, - BookmarkInputBoundary bookmarkUseCaseInteractor) { - super(searchRecipeListByIngredientUseCase, searchRecipeListByNameUseCase); - this.bookmarkUseCaseInteractor = bookmarkUseCaseInteractor; - } - - public void execute(...) { - final BookmarkInputData bookmarkInputData = new BookmarkInputData(...); - - bookmarkUseCaseInteractor.execute(bookmarkInputData); - } - -// public addBookmark(Recipe recipe) { +//package interface_adapter.bookmark; // +//import interface_adapter.RecipeListController; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; +//import use_case.SearchRecipeListByNameUseCase; +// +//public class BookmarkController extends RecipeListController { +// private final BookmarkInputBoundary bookmarkUseCaseInteractor; +// +// public BookmarkController(SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase, +// SearchRecipeListByNameUseCase searchRecipeListByNameUseCase, +// BookmarkInputBoundary bookmarkUseCaseInteractor) { +// super(searchRecipeListByIngredientUseCase, searchRecipeListByNameUseCase); +// this.bookmarkUseCaseInteractor = bookmarkUseCaseInteractor; // } // -// public void execute(Recipe recipe) { -// final BookmarkInputData bookmarkInputData = new BookmarkInputData(recipe); +// public void execute(...) { +// final BookmarkInputData bookmarkInputData = new BookmarkInputData(...); // // bookmarkUseCaseInteractor.execute(bookmarkInputData); -} +// } +// +//// public addBookmark(Recipe recipe) { +//// +//// } +//// +//// public void execute(Recipe recipe) { +//// final BookmarkInputData bookmarkInputData = new BookmarkInputData(recipe); +//// +//// bookmarkUseCaseInteractor.execute(bookmarkInputData); +//} diff --git a/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java b/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java index 154bd9054..f8e7da30e 100644 --- a/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java +++ b/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java @@ -1,14 +1,14 @@ -package interface_adapter.bookmark; - -public class BookmarkPresenter implements BookmarkOutputBoundary { - private final BookmarkViewModel bookmarkViewModel; - - public BookmarkPresenter(BookmarkViewModel bookmarkViewModel) { - this.bookmarkViewModel = bookmarkViewModel; - } - - @Override - public void prepareSuccessView(BookmarkOutputData outputData) { - bookmarkViewModel.firePrepertyChanged("property name..."); - } -} +//package interface_adapter.bookmark; +// +//public class BookmarkPresenter implements BookmarkOutputBoundary { +// private final BookmarkViewModel bookmarkViewModel; +// +// public BookmarkPresenter(BookmarkViewModel bookmarkViewModel) { +// this.bookmarkViewModel = bookmarkViewModel; +// } +// +// @Override +// public void prepareSuccessView(BookmarkOutputData outputData) { +// bookmarkViewModel.firePrepertyChanged("property name..."); +// } +//} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java index b93613f28..b2c11ca3a 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java @@ -1,28 +1,28 @@ -package interface_adapter.search_recipe_list_by_ingredient.; - -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputData; - -import java.util.List; - -/** - * Controller for the Search Recipe List By Ingredient Use Case. - */ -public class SearchRecipeListByIngredientController { - private final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor; - - public SearchRecipeListByIngredientController( - SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor) { - this.searchRecipeListByIngredientUseCaseInteractor = searchRecipeListByIngredientUseCaseInteractor; - } - - /** - * Executes the Search Recipe List By Ingredient Use Case. - * @param ingredients the ingredients entered by the user - */ - public void execute(List ingredients) { - final SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData = new SearchRecipeListByIngredientInputData(ingredients); - - searchRecipeListByIngredientUseCaseInteractor.execute(searchRecipeListByIngredientInputData); - } -} +//package interface_adapter.search_recipe_list_by_ingredient.; +// +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputData; +// +//import java.util.List; +// +///** +// * Controller for the Search Recipe List By Ingredient Use Case. +// */ +//public class SearchRecipeListByIngredientController { +// private final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor; +// +// public SearchRecipeListByIngredientController( +// SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor) { +// this.searchRecipeListByIngredientUseCaseInteractor = searchRecipeListByIngredientUseCaseInteractor; +// } +// +// /** +// * Executes the Search Recipe List By Ingredient Use Case. +// * @param ingredients the ingredients entered by the user +// */ +// public void execute(List ingredients) { +// final SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData = new SearchRecipeListByIngredientInputData(ingredients); +// +// searchRecipeListByIngredientUseCaseInteractor.execute(searchRecipeListByIngredientInputData); +// } +//} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java index 2ae7594f8..fcb9b83ac 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java @@ -1,27 +1,28 @@ -package interface_adapter.search_recipe_list_by_ingredient; - -import interface_adapter.RecipeListViewModel; -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; - -/** - * The Presenter for the Search Recipe List By Ingredients Use Case. - */ -public class SearchRecipeListByIngredientPresenter implements SearchRecipeListByIngredientOutputBoundary { - - private final RecipeListViewModel recipeListViewModel; - - public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewModel) { - this.recipeListViewModel = recipeListViewModel; - } - - @Override - public void prepareSuccessView() { - // TODO - recipeListViewModel.firePropertyChanged("???"); - } - - @Override - public void prepareFailView(String error) { - // note: this use case currently can't fail - } -} +//package interface_adapter.search_recipe_list_by_ingredient; +// +//import interface_adapter.RecipeListViewModel; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputData; +// +///** +// * The Presenter for the Search Recipe List By Ingredients Use Case. +// */ +//public class SearchRecipeListByIngredientPresenter implements SearchRecipeListByIngredientOutputBoundary { +// +// private final RecipeListViewModel recipeListViewModel; +// +// public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewModel) { +// this.recipeListViewModel = recipeListViewModel; +// } +// +// @Override +// public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { +// // TODO +// recipeListViewModel.firePropertyChanged("???"); +// } +// +// @Override +// public void prepareFailView(String error) { +// // note: this use case currently can't fail +// } +//} diff --git a/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java b/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java index cf98c5338..3b2f52029 100644 --- a/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java +++ b/src/main/java/use_case/search_recipe/SearchRecipeUseCase.java @@ -6,9 +6,9 @@ import java.util.List; -public class SearchRecipeUseCase implements use_case.SearchRecipe.SearchRecipeInputBoundary { +public class SearchRecipeUseCase implements use_case.search_recipe.SearchRecipeInputBoundary { private final RecipeDAO recipeDAO; - private final use_case.SearchRecipe.SearchRecipeOutputBoundary presenter; + private final use_case.search_recipe.SearchRecipeOutputBoundary presenter; public SearchRecipeUseCase(RecipeDAO recipeDAO, SearchRecipePresenter presenter) { this.recipeDAO = recipeDAO; @@ -23,7 +23,7 @@ public void searchRecipes(SearchRecipeInputData inputData) { List recipes = recipeDAO.getRecipesByIngredients(ingredients); // Pass results to presenter - use_case.SearchRecipe.SearchRecipeOutputData outputData = new use_case.SearchRecipe.SearchRecipeOutputData(recipes); + use_case.search_recipe.SearchRecipeOutputData outputData = new use_case.search_recipe.SearchRecipeOutputData(recipes); presenter.presentRecipes(outputData); } } diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java index 7152c7100..2fd2a6c94 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java @@ -1,4 +1,17 @@ package use_case.search_recipe_list_by_ingredient; +import entity.User; + +import java.util.List; + +/** + * The interface of the DAO for the Change Password Use Case. + */ public interface SearchRecipeListByIngredientDataAccessInterface { + + /** + * Searches recipes by ingredient. + * @param ingredients the ingredients entered. + */ + void searchRecipeListByIngredient(List ingredients); } diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java index 6d8c6bc77..9ea20defa 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java @@ -1,34 +1,34 @@ -package use_case.search_recipe_list_by_ingredient; - -import entity.User; - -public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { - - /** - * The Change Password Interactor. - */ - private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; - private final SearchRecipeListByIngredientOutputBoundary userPresenter; - private final User user; - - public SearchRecipeListByIngredientInteractor( - SearchRecipeListByIngredientDataAccessInterface searchRecipeListByIngredientDataAccessInterface, - SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary) { - this.userDataAccessObject = searchRecipeListByIngredientDataAccessInterface; - this.userPresenter = searchRecipeListByIngredientOutputBoundary; - } - - @Override - public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { - final User user = new User(searchRecipeListByIngredientInputData.getusername(), - searchRecipeListByIngredientInputData.getPassword(), - searchRecipeListByIngredientInputData.get... - ) - userDataAccessObject.metjod(user); - - final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = - new SearchRecipeListByIngredientOutputData(recipes, - false); - userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); - } -} +//package use_case.search_recipe_list_by_ingredient; +// +//import entity.User; +// +//public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { +// +// /** +// * The Change Password Interactor. +// */ +// private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; +// private final SearchRecipeListByIngredientOutputBoundary userPresenter; +// private final User user; +// +// public SearchRecipeListByIngredientInteractor( +// SearchRecipeListByIngredientDataAccessInterface searchRecipeListByIngredientDataAccessInterface, +// SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary) { +// this.userDataAccessObject = searchRecipeListByIngredientDataAccessInterface; +// this.userPresenter = searchRecipeListByIngredientOutputBoundary; +// } +// +// @Override +// public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { +// final User user = new User(searchRecipeListByIngredientInputData.getusername(), +// searchRecipeListByIngredientInputData.getPassword(), +// searchRecipeListByIngredientInputData.get... +// ) +// userDataAccessObject.metjod(user); +// +// final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = +// new SearchRecipeListByIngredientOutputData(recipes, +// false); +// userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); +// } +//} diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index 7377c7caf..282c31535 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -16,11 +16,16 @@ public BookmarkView(User user, String folderName) { super(user, folderName); setTitle(user.getUsername() + "'s Bookmarks"); + final JPanel folderPanel = new JPanel(); + folderPanel.setLayout(new BoxLayout(folderPanel, BoxLayout.Y_AXIS)); + final JComboBox dropdown = getDropdown(user); - add(dropdown, BorderLayout.EAST); + folderPanel.add(dropdown); final JPanel createFolderPanel = getCreateFolderPanel(user); - add(createFolderPanel, BorderLayout.WEST); + folderPanel.add(createFolderPanel); + + add(folderPanel); } @NotNull diff --git a/src/main/java/view/IndividualRecipeView.java b/src/main/java/view/IndividualRecipeView.java index 3331f5166..4eba2afbe 100644 --- a/src/main/java/view/IndividualRecipeView.java +++ b/src/main/java/view/IndividualRecipeView.java @@ -44,16 +44,6 @@ public IndividualRecipeView(Recipe recipe, User user) { mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); setContentPane(mainPanel); - // Initialize ingredient list - final DefaultListModel listModel = new DefaultListModel<>(); - for (Ingredient ingredient : recipe.getIngredients()) { - listModel.addElement(ingredient.getName() + ", Amount: " + ingredient.getAmount() + " " + ingredient.getUnit()); - } - ingredientsJLIst = new JList<>(listModel); - final JScrollPane scrollPane = new JScrollPane(ingredientsJLIst); - scrollPane.setPreferredSize(ingredientsJLIst.getPreferredScrollableViewportSize()); - mainPanel.add(scrollPane); - // Initialize buttons nutritionButton = new JButton("Nutrition"); bookmarkButton = new JButton("Bookmark"); @@ -94,6 +84,16 @@ public IndividualRecipeView(Recipe recipe, User user) { e.printStackTrace(); } + // Initialize ingredient list + final DefaultListModel listModel = new DefaultListModel<>(); + for (Ingredient ingredient : recipe.getIngredients()) { + listModel.addElement(ingredient.getName() + ", Amount: " + ingredient.getAmount() + " " + ingredient.getUnit()); + } + ingredientsJLIst = new JList<>(listModel); + final JScrollPane scrollPane = new JScrollPane(ingredientsJLIst); + scrollPane.setPreferredSize(ingredientsJLIst.getPreferredScrollableViewportSize()); + mainPanel.add(scrollPane); + // Initialize button panel with horizontal BoxLayout final JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index da08240a4..3db7f88a4 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -118,7 +118,7 @@ public void mouseClicked(MouseEvent event) { // Clear search button clearSearchButton = new JButton("Clear Search"); clearSearchButton.addActionListener(this); - searchPanel.add(clearSearchButton, BorderLayout.EAST); + add(clearSearchButton, BorderLayout.WEST); // Add the search panel to the frame add(searchPanel, BorderLayout.NORTH); diff --git a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java index 980d84b32..b222e7b07 100644 --- a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java +++ b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java @@ -1,96 +1,96 @@ -package use_case.search_recipe_list_by_ingredient; - -import data_access.InMemoryUserDataAccessObject; -import entity.User; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class SearchRecipeListByIngredientInteractorTest { - private static final List ingredients = List.of("chicken", "mushroom", "tomato"); - - @Test - void successTest() { - SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData(ingredients); - SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For the success test, we need to add Paul to the data access repository before we log in. - User user = factory.create("Paul", "password"); - userRepository.save(user); - - - // This creates a successPresenter that tests whether the test case is as we expect. - SearchRecipeListByIngredientOutputBoundary successPresenter = - new SearchRecipeListByIngredientOutputBoundary() { - @Override - public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { - assertEquals("expected", ??user.getUsername()??); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - }; - - SearchRecipeListByIngredientInputBoundary interactor = - new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); - interactor.execute(inputData); - } - - @Test - void noMatchingResultsTest() { - LoginInputData inputData = new LoginInputData("Paul", "wrong"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For this failure test, we need to add Paul to the data access repository before we log in, and - // the passwords should not match. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Incorrect password for \"Paul\".", error); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); - } - - @Test - void emptyQueryTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // Add Paul to the repo so that when we check later they already exist - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Paul: Account does not exist.", error); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); - } -} \ No newline at end of file +//package use_case.search_recipe_list_by_ingredient; +// +//import data_access.InMemoryUserDataAccessObject; +//import entity.User; +//import org.junit.jupiter.api.Test; +// +//import java.util.List; +// +//import static org.junit.jupiter.api.Assertions.*; +// +//class SearchRecipeListByIngredientInteractorTest { +// private static final List ingredients = List.of("chicken", "mushroom", "tomato"); +// +// @Test +// void successTest() { +// SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData(ingredients); +// SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); +// +// // For the success test, we need to add Paul to the data access repository before we log in. +// User user = factory.create("Paul", "password"); +// userRepository.save(user); +// +// +// // This creates a successPresenter that tests whether the test case is as we expect. +// SearchRecipeListByIngredientOutputBoundary successPresenter = +// new SearchRecipeListByIngredientOutputBoundary() { +// @Override +// public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { +// assertEquals("expected", ??user.getUsername()??); +// } +// +// @Override +// public void prepareFailView(String error) { +// fail("Use case failure is unexpected."); +// } +// }; +// +// SearchRecipeListByIngredientInputBoundary interactor = +// new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); +// interactor.execute(inputData); +// } +// +// @Test +// void noMatchingResultsTest() { +// LoginInputData inputData = new LoginInputData("Paul", "wrong"); +// LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); +// +// // For this failure test, we need to add Paul to the data access repository before we log in, and +// // the passwords should not match. +// UserFactory factory = new CommonUserFactory(); +// User user = factory.create("Paul", "password"); +// userRepository.save(user); +// +// // This creates a presenter that tests whether the test case is as we expect. +// LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { +// @Override +// public void prepareSuccessView(LoginOutputData user) { +// // this should never be reached since the test case should fail +// fail("Use case success is unexpected."); +// } +// +// @Override +// public void prepareFailView(String error) { +// assertEquals("Incorrect password for \"Paul\".", error); +// } +// }; +// +// LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); +// interactor.execute(inputData); +// } +// +// @Test +// void emptyQueryTest() { +// LoginInputData inputData = new LoginInputData("Paul", "password"); +// LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); +// +// // Add Paul to the repo so that when we check later they already exist +// +// // This creates a presenter that tests whether the test case is as we expect. +// LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { +// @Override +// public void prepareSuccessView(LoginOutputData user) { +// // this should never be reached since the test case should fail +// fail("Use case success is unexpected."); +// } +// +// @Override +// public void prepareFailView(String error) { +// assertEquals("Paul: Account does not exist.", error); +// } +// }; +// +// LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); +// interactor.execute(inputData); +// } +//} \ No newline at end of file From 778d2712cbe1a94637af2c82d3e03e49418c76fc Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 2 Dec 2024 06:54:40 -0500 Subject: [PATCH 135/148] (refactor) about NutritionInformation usecase part --- .../data_access/NutritionInformationDAO.java | 38 +++++++++++ src/main/java/data_access/RecipeDAOImpl.java | 66 ------------------- .../{GetRecipeId.java => RecipeIdDAO.java} | 5 +- .../ViewNutritionDataAccessImpl.java | 60 ----------------- .../NutritionInformationPresenter.java | 14 ++++ .../NutritionInformationAccessInterface.java | 5 ++ .../NutritionInformationInputBoundary.java | 9 +++ .../NutritionInformationInputData.java | 13 ++++ .../NutritionInformationInteractor.java | 60 +++++++++++++++++ .../NutritionInformationOutputBoundary.java | 9 +++ .../NutritionInformationOutputData.java | 16 +++++ src/main/java/view/IndividualRecipeView.java | 9 ++- .../java/view/NutritionInformationView.java | 52 +++++++++++++++ src/main/java/view/NutritionView.java | 40 ----------- src/test/java/NutritionViewTest.java | 7 -- .../java/ViewNutritionDataAccessTest.java | 27 -------- users.json | 37 +++++++++++ 17 files changed, 259 insertions(+), 208 deletions(-) create mode 100644 src/main/java/data_access/NutritionInformationDAO.java delete mode 100644 src/main/java/data_access/RecipeDAOImpl.java rename src/main/java/data_access/{GetRecipeId.java => RecipeIdDAO.java} (97%) delete mode 100644 src/main/java/data_access/ViewNutritionDataAccessImpl.java create mode 100644 src/main/java/interface_adapter/nutrition_information/NutritionInformationPresenter.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationAccessInterface.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationInputBoundary.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationInputData.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationOutputBoundary.java create mode 100644 src/main/java/use_case/nutrition_information/NutritionInformationOutputData.java create mode 100644 src/main/java/view/NutritionInformationView.java delete mode 100644 src/main/java/view/NutritionView.java delete mode 100644 src/test/java/NutritionViewTest.java delete mode 100644 src/test/java/ViewNutritionDataAccessTest.java create mode 100644 users.json diff --git a/src/main/java/data_access/NutritionInformationDAO.java b/src/main/java/data_access/NutritionInformationDAO.java new file mode 100644 index 000000000..552fc082d --- /dev/null +++ b/src/main/java/data_access/NutritionInformationDAO.java @@ -0,0 +1,38 @@ +package data_access; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import use_case.nutrition_information.NutritionInformationAccessInterface; + +import java.io.IOException; + +public class NutritionInformationDAO implements NutritionInformationAccessInterface { + private static final String BASE_URL = "https://api.spoonacular.com"; + private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; + + @Override + public String fetchNutritionInformation(int recipeId) { + OkHttpClient client = new OkHttpClient(); + + String url = buildUrl(recipeId); + + Request request = new Request.Builder().url(url).build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + return response.body().string(); + } else { + throw new IOException("Failed to fetch data: HTTP code " + response.code()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String buildUrl(int recipeId) { + String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; + return BASE_URL + endpoint + "?apiKey=" + API_KEY; + } +} + diff --git a/src/main/java/data_access/RecipeDAOImpl.java b/src/main/java/data_access/RecipeDAOImpl.java deleted file mode 100644 index d75f1e494..000000000 --- a/src/main/java/data_access/RecipeDAOImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -// Commented out because it's not used to avoid errors -//package data_access; -// -//import entity.Ingredient; -//import entity.Recipe; -// -//import java.util.ArrayList; -//import java.util.List; -// -//public class RecipeDAOImpl implements RecipeDAO { -// private Ingredient pasta; -// private Ingredient tomatoSauce; -// private Ingredient lettuce; -// private Ingredient tomato; -// private Ingredient cucumber; -// private Ingredient onion; -// private Ingredient garlic; -// private String imageURL; -// -// @Override -// public List getRecipesByIngredients(List ingredients) { -// List recipes = new ArrayList<>(); -// this.pasta = new Ingredient("pasta"); -// this.tomatoSauce = new Ingredient("tomato sauce"); -// this.lettuce = new Ingredient("lettuce"); -// this.tomato = new Ingredient("tomato"); -// this.cucumber = new Ingredient("cucumber"); -// this.onion = new Ingredient("onion"); -// this.garlic = new Ingredient("garlic"); -// this.imageURL = "https://img.spoonacular.com/recipes/716429-556x370.jpg"; -// recipes.add(new Recipe("Pasta", "https://example.com/pasta", List.of(pasta, tomatoSauce), imageURL)); -// recipes.add(new Recipe("Salad", "https://example.com/salad", List.of(lettuce, tomato, cucumber), imageURL)); -// recipes.add(new Recipe("Tomato Soup", "https://example.com/tomato_soup", List.of(tomato, onion, garlic), imageURL)); -// return recipes; -// } -// -// @Override -// public List getAllRecipes() { -// // Mock data - replace this with actual data retrieval from a database or API -// // Note: adding dietaryType and cuisineType to recipe constructor so need to add here too -// List recipes = new ArrayList<>(); -// recipes.add(new Recipe("Pasta", "https://example.com/pasta", List.of(pasta, tomato), imageURL)); -// // fried rice example w/ cuisine and diet added -// recipes.add(new Recipe("Fried Rice", "rice.com", List.of(onion, garlic), -// "chinese", "vegetarian", imageURL)); -// recipes.add(new Recipe("Salad", "https://example.com/salad", List.of(lettuce, tomato, cucumber), imageURL)); -// recipes.add(new Recipe("Tomato Soup", "https://example.com/tomato_soup", List.of(tomato, onion, garlic), imageURL)); -// return recipes; -// } -// -// @Override -// public List getAvailableDiets() { -// final List diets = new ArrayList<>(); -// diets.add("Vegetarian"); -// diets.add("Vegan"); -// return diets; -// } -// -// @Override -// public List getAvailableCuisines() { -// final List cuisines = new ArrayList<>(); -// cuisines.add("Chinese"); -// cuisines.add("Italian"); -// return cuisines; -// } -//} diff --git a/src/main/java/data_access/GetRecipeId.java b/src/main/java/data_access/RecipeIdDAO.java similarity index 97% rename from src/main/java/data_access/GetRecipeId.java rename to src/main/java/data_access/RecipeIdDAO.java index f07a621bc..803d8f295 100644 --- a/src/main/java/data_access/GetRecipeId.java +++ b/src/main/java/data_access/RecipeIdDAO.java @@ -7,14 +7,13 @@ import org.json.JSONArray; import org.json.JSONObject; -public class GetRecipeId { +public class RecipeIdDAO { - private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; + private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; // private static final String BASE_URL = "https://api.spoonacular.com/recipes/complexSearch"; public static int getRecipeIdByName(String recipeName) { try { - String apiUrl = BASE_URL + "?query=" + recipeName.replace(" ", "+") + "&apiKey=" + API_KEY; URL url = new URL(apiUrl); diff --git a/src/main/java/data_access/ViewNutritionDataAccessImpl.java b/src/main/java/data_access/ViewNutritionDataAccessImpl.java deleted file mode 100644 index 4c1afe481..000000000 --- a/src/main/java/data_access/ViewNutritionDataAccessImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package data_access; - -import entity.Nutrition; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import org.json.JSONArray; -import org.json.JSONObject; -import use_case.ViewNutritionDataAccessInterface; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class ViewNutritionDataAccessImpl implements ViewNutritionDataAccessInterface { - private static final String BASE_URL = "https://api.spoonacular.com"; - private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; - - @Override - public List getNutritionDataForRecipe(int recipeId) { - OkHttpClient client = new OkHttpClient(); - - String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; - String url = BASE_URL + endpoint + "?apiKey=" + API_KEY; - - Request request = new Request.Builder().url(url).build(); - - List nutritionList = new ArrayList<>(); - - try (Response response = client.newCall(request).execute()) { - if (response.isSuccessful() && response.body() != null) { - - String jsonResponse = response.body().string(); - JSONObject jsonObject = new JSONObject(jsonResponse); - - if (jsonObject.has("nutrients")) { - JSONArray nutrients = jsonObject.getJSONArray("nutrients"); - for (int i = 0; i < nutrients.length(); i++) { - JSONObject nutrientJson = nutrients.getJSONObject(i); - - String name = nutrientJson.getString("name"); - double amount = nutrientJson.getDouble("amount"); - String unit = nutrientJson.getString("unit"); - double percentOfDailyNeeds = nutrientJson.optDouble("percentOfDailyNeeds", 0); - - Nutrition nutrition = new Nutrition(name, amount, unit, percentOfDailyNeeds); - nutritionList.add(nutrition); - } - } - return nutritionList; - } else { - System.err.println("Request failed with code: " + response.code()); - } - - } catch (IOException e) { - e.printStackTrace(); - } - return nutritionList; - } -} diff --git a/src/main/java/interface_adapter/nutrition_information/NutritionInformationPresenter.java b/src/main/java/interface_adapter/nutrition_information/NutritionInformationPresenter.java new file mode 100644 index 000000000..97510612a --- /dev/null +++ b/src/main/java/interface_adapter/nutrition_information/NutritionInformationPresenter.java @@ -0,0 +1,14 @@ +package interface_adapter.nutrition_information; + +import entity.Nutrition; +import use_case.nutrition_information.NutritionInformationOutputBoundary; +import use_case.nutrition_information.NutritionInformationOutputData; + +import java.util.List; + +public class NutritionInformationPresenter implements NutritionInformationOutputBoundary { + @Override + public List presentNutritionInformation(NutritionInformationOutputData outputData) { + return outputData.getNutritionList(); + } +} diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationAccessInterface.java b/src/main/java/use_case/nutrition_information/NutritionInformationAccessInterface.java new file mode 100644 index 000000000..955df4d9a --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationAccessInterface.java @@ -0,0 +1,5 @@ +package use_case.nutrition_information; + +public interface NutritionInformationAccessInterface { + String fetchNutritionInformation(int recipeId); +} diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationInputBoundary.java b/src/main/java/use_case/nutrition_information/NutritionInformationInputBoundary.java new file mode 100644 index 000000000..3d71484e5 --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationInputBoundary.java @@ -0,0 +1,9 @@ +package use_case.nutrition_information; + +import entity.Nutrition; + +import java.util.List; + +public interface NutritionInformationInputBoundary { + List NutritionInformation(NutritionInformationInputData inputData); +} diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationInputData.java b/src/main/java/use_case/nutrition_information/NutritionInformationInputData.java new file mode 100644 index 000000000..fb2798278 --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationInputData.java @@ -0,0 +1,13 @@ +package use_case.nutrition_information; + +public class NutritionInformationInputData { + private final int recipeId; + + public NutritionInformationInputData(int recipeId) { + this.recipeId = recipeId; + } + + public int getRecipeId() { + return recipeId; + } +} \ No newline at end of file diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java b/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java new file mode 100644 index 000000000..fb3d7c89e --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java @@ -0,0 +1,60 @@ +package use_case.nutrition_information; + +import entity.Nutrition; + +import data_access.NutritionInformationDAO; +import interface_adapter.nutrition_information.NutritionInformationPresenter; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class NutritionInformationInteractor implements NutritionInformationInputBoundary { + private final NutritionInformationDAO dataFetcher; + private final NutritionInformationPresenter outputPresenter; + + public NutritionInformationInteractor(NutritionInformationDAO dataFetcher + ,NutritionInformationPresenter outputPresenter) { + this.dataFetcher = dataFetcher; + this.outputPresenter = outputPresenter; + } + + @Override + public List NutritionInformation(NutritionInformationInputData inputData) { + // Step 1: 从数据访问层获取原始 JSON 数据 + String jsonData = dataFetcher.fetchNutritionInformation(inputData.getRecipeId()); + + // Step 2: 解析 JSON 数据 + List nutritionList = parseNutritionData(jsonData); + + // Step 3: 封装结果为 OutputData + NutritionInformationOutputData outputData = new NutritionInformationOutputData(nutritionList); + + // Step 4: 调用 OutputBoundary 返回结果 + return outputPresenter.presentNutritionInformation(outputData); + } + + private List parseNutritionData(String jsonData) { + List nutritionList = new ArrayList<>(); + + JSONObject jsonObject = new JSONObject(jsonData); + if (jsonObject.has("nutrients")) { + JSONArray nutrients = jsonObject.getJSONArray("nutrients"); + for (int i = 0; i < nutrients.length(); i++) { + JSONObject nutrientJson = nutrients.getJSONObject(i); + + String name = nutrientJson.getString("name"); + double amount = nutrientJson.getDouble("amount"); + String unit = nutrientJson.getString("unit"); + double percentOfDailyNeeds = nutrientJson.optDouble("percentOfDailyNeeds", 0); + + Nutrition nutrition = new Nutrition(name, amount, unit, percentOfDailyNeeds); + nutritionList.add(nutrition); + } + } + return nutritionList; + } +} + + diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationOutputBoundary.java b/src/main/java/use_case/nutrition_information/NutritionInformationOutputBoundary.java new file mode 100644 index 000000000..8b5145787 --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationOutputBoundary.java @@ -0,0 +1,9 @@ +package use_case.nutrition_information; + +import entity.Nutrition; + +import java.util.List; + +public interface NutritionInformationOutputBoundary { + List presentNutritionInformation(NutritionInformationOutputData outputData); +} diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationOutputData.java b/src/main/java/use_case/nutrition_information/NutritionInformationOutputData.java new file mode 100644 index 000000000..4498e7dd4 --- /dev/null +++ b/src/main/java/use_case/nutrition_information/NutritionInformationOutputData.java @@ -0,0 +1,16 @@ +package use_case.nutrition_information; + +import java.util.List; +import entity.Nutrition; + +public class NutritionInformationOutputData { + private final List nutritionList; + + public NutritionInformationOutputData(List nutritionList) { + this.nutritionList = nutritionList; + } + + public List getNutritionList() { + return nutritionList; + } +} diff --git a/src/main/java/view/IndividualRecipeView.java b/src/main/java/view/IndividualRecipeView.java index 8bbbf2be3..d4334de88 100644 --- a/src/main/java/view/IndividualRecipeView.java +++ b/src/main/java/view/IndividualRecipeView.java @@ -9,7 +9,7 @@ import java.net.URISyntaxException; import java.net.URL; -import data_access.GetRecipeId; +import data_access.RecipeIdDAO; import data_access.UserDAOImpl; import entity.*; @@ -116,13 +116,12 @@ public IndividualRecipeView(Recipe recipe, User user) { setVisible(true); } - GetRecipeId getRecipeId = new GetRecipeId(); - @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == nutritionButton) { - int recipeId = getRecipeId.getRecipeIdByName(recipe.getName()); - new NutritionView(recipeId); + RecipeIdDAO getRecipeIdDAO = new RecipeIdDAO(); + int recipeId = getRecipeIdDAO.getRecipeIdByName(recipe.getName()); + new NutritionInformationView(recipeId); } else if (event.getSource() == bookmarkButton) { // TODO complete bookmark function diff --git a/src/main/java/view/NutritionInformationView.java b/src/main/java/view/NutritionInformationView.java new file mode 100644 index 000000000..967a10606 --- /dev/null +++ b/src/main/java/view/NutritionInformationView.java @@ -0,0 +1,52 @@ +package view; + +import data_access.NutritionInformationDAO; +import entity.Nutrition; +import interface_adapter.nutrition_information.NutritionInformationPresenter; +import use_case.nutrition_information.NutritionInformationInputData; +import use_case.nutrition_information.NutritionInformationInteractor; + +import javax.swing.*; +import java.util.List; + +public class NutritionInformationView extends JFrame { + private final int recipeId; + private final NutritionInformationInteractor interactor; + private final JList nutritionList; + + public NutritionInformationView(int recipeId) { + this.recipeId = recipeId; + + nutritionList = new JList<>(); + setTitle("Nutrition Information"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(800, 300); + setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + add(new JScrollPane(nutritionList)); + + NutritionInformationPresenter outputPresenter = new NutritionInformationPresenter(); + NutritionInformationDAO dataFetcher = new NutritionInformationDAO(); + interactor = new NutritionInformationInteractor(dataFetcher, outputPresenter); + + loadNutritionData(); + + setVisible(true); + } + + private void loadNutritionData() { + NutritionInformationInputData inputData = new NutritionInformationInputData(recipeId); + + List nutritionData = interactor.NutritionInformation(inputData); + + DefaultListModel listModel = new DefaultListModel<>(); + for (Nutrition nutrition : nutritionData) { + String displayText = String.format("%s: %.2f %s (%.2f%% of daily needs)", + nutrition.getName(), + nutrition.getAmount(), + nutrition.getUnit(), + nutrition.getPercentDailyNeeds()); + listModel.addElement(displayText); + } + nutritionList.setModel(listModel); + } +} diff --git a/src/main/java/view/NutritionView.java b/src/main/java/view/NutritionView.java deleted file mode 100644 index 1ebded823..000000000 --- a/src/main/java/view/NutritionView.java +++ /dev/null @@ -1,40 +0,0 @@ -package view; - -import data_access.ViewNutritionDataAccessImpl; -import entity.Nutrition; -import use_case.ViewNutritionDataAccessInterface; - -import javax.swing.*; -import java.util.List; - -public class NutritionView extends JFrame { - final private int recipeId; - private JList nutritionList; - private ViewNutritionDataAccessInterface dataAccess; - - public NutritionView(int recipeId) { - this.recipeId = recipeId; - this.nutritionList = new JList<>(); - this.dataAccess = new ViewNutritionDataAccessImpl(); - - List nutritionData = dataAccess.getNutritionDataForRecipe(recipeId); - - DefaultListModel listModel = new DefaultListModel<>(); - for (Nutrition nutrition : nutritionData) { - String displayText = String.format("%s: %.2f %s (%.2f%% of daily needs)", - nutrition.getName(), - nutrition.getAmount(), - nutrition.getUnit(), - nutrition.getPercentDailyNeeds()); - listModel.addElement(displayText); - } - - nutritionList.setModel(listModel); - - add(new JScrollPane(nutritionList)); - // setTitle(recipe.getName()); - setSize(800, 300); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setVisible(true); - } -} diff --git a/src/test/java/NutritionViewTest.java b/src/test/java/NutritionViewTest.java deleted file mode 100644 index 80938a2c8..000000000 --- a/src/test/java/NutritionViewTest.java +++ /dev/null @@ -1,7 +0,0 @@ -import view.NutritionView; - -public class NutritionViewTest { - public static void main(String[] args) { - NutritionView nutritionView = new NutritionView(1003464); - } -} diff --git a/src/test/java/ViewNutritionDataAccessTest.java b/src/test/java/ViewNutritionDataAccessTest.java deleted file mode 100644 index 7f893aea6..000000000 --- a/src/test/java/ViewNutritionDataAccessTest.java +++ /dev/null @@ -1,27 +0,0 @@ -import data_access.ViewNutritionDataAccessImpl; -import entity.Nutrition; - -import java.util.List; - -public class ViewNutritionDataAccessTest { - public static void main(String[] args) { - // 创建一个 ViewNutritionDataAccessImpl 对象 - ViewNutritionDataAccessImpl dataAccess = new ViewNutritionDataAccessImpl(); - - // 传入测试的 recipeId - int recipeId = 1003464; - - // 调用方法获取营养数据 - List nutritionList = dataAccess.getNutritionDataForRecipe(recipeId); - - // 输出结果 - if (!nutritionList.isEmpty()) { - System.out.println("Nutrition data for recipe ID " + recipeId + ":"); - for (Nutrition nutrition : nutritionList) { - System.out.println(nutrition); - } - } else { - System.out.println("No nutrition data found for recipe ID " + recipeId + "."); - } - } -} diff --git a/users.json b/users.json new file mode 100644 index 000000000..71e6f3304 --- /dev/null +++ b/users.json @@ -0,0 +1,37 @@ +[{ + "bookmarks": [], + "recentlyViewed": [{ + "image": "https://img.spoonacular.com/recipes/648368-312x231.jpg", + "name": "Jalapeno Queso With Goat Cheese", + "ingredients": [ + { + "unit": "oz", + "amount": 8, + "name": "canned tomatoes", + "id": 0 + }, + { + "unit": "oz", + "amount": 5.3, + "name": "goat cheese", + "id": 0 + }, + { + "unit": "", + "amount": 1, + "name": "ea. jalapeno pepper", + "id": 0 + }, + { + "unit": "teaspoons", + "amount": 2, + "name": "hot sauce", + "id": 0 + } + ], + "id": 0, + "url": "https://spoonacular.com/jalapeno-queso-with-goat-cheese-648368" + }], + "password": "hhcgoodluck", + "username": "hhc" +}] \ No newline at end of file From 99124ae747e07d1a90bdffd3a095ffecbf24327b Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 2 Dec 2024 09:34:43 -0500 Subject: [PATCH 136/148] =?UTF-8?q?=EF=BC=88Refactor=20And=20Code=20style)?= =?UTF-8?q?=20for=20the=20Nutrition=20Information?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_access/NutritionInformationDAO.java | 28 ++++++++++++++++ src/main/java/data_access/RecipeIdDAO.java | 19 +++++++++++ .../NutritionInformationInteractor.java | 32 ++++++++++++++++--- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/main/java/data_access/NutritionInformationDAO.java b/src/main/java/data_access/NutritionInformationDAO.java index 552fc082d..7ae7d1405 100644 --- a/src/main/java/data_access/NutritionInformationDAO.java +++ b/src/main/java/data_access/NutritionInformationDAO.java @@ -7,18 +7,37 @@ import java.io.IOException; +/** + * Data Access Object (DAO) implementation for fetching nutrition information. + * This class communicates with the Spoonacular API to retrieve nutrition data + * for a specific recipe based on its ID. + */ public class NutritionInformationDAO implements NutritionInformationAccessInterface { + + // Base URL for the Spoonacular API private static final String BASE_URL = "https://api.spoonacular.com"; + + // API key for authenticating requests to the Spoonacular API private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; + /** + * Fetches nutrition information for a specific recipe from the API. + * @param recipeId The ID of the recipe for which nutrition information is required. + * @return A JSON string containing the nutrition information. + * @throws RuntimeException If there is an error during the API request. + */ @Override public String fetchNutritionInformation(int recipeId) { + // Step 1: Initialize OkHttpClient for making HTTP requests OkHttpClient client = new OkHttpClient(); + // Step 2: Build the URL for the API request String url = buildUrl(recipeId); + // Step 3: Create a new HTTP request object Request request = new Request.Builder().url(url).build(); + // Step 4: Execute the HTTP request and handle the response try (Response response = client.newCall(request).execute()) { if (response.isSuccessful() && response.body() != null) { return response.body().string(); @@ -30,8 +49,17 @@ public String fetchNutritionInformation(int recipeId) { } } + /** + * Builds the complete URL for the Spoonacular API request. + * @param recipeId The ID of the recipe for which the URL is generated. + * @return A fully formed URL string for the API request. + */ private String buildUrl(int recipeId) { + + // Construct the API endpoint specific to the recipe String endpoint = "/recipes/" + recipeId + "/nutritionWidget.json"; + + // Combine the base URL, endpoint, and API key to form the full URL return BASE_URL + endpoint + "?apiKey=" + API_KEY; } } diff --git a/src/main/java/data_access/RecipeIdDAO.java b/src/main/java/data_access/RecipeIdDAO.java index 803d8f295..772653850 100644 --- a/src/main/java/data_access/RecipeIdDAO.java +++ b/src/main/java/data_access/RecipeIdDAO.java @@ -7,25 +7,42 @@ import org.json.JSONArray; import org.json.JSONObject; +/** + * Data Access Object (DAO) for retrieving recipe IDs by recipe name. + * This class communicates with the Spoonacular API's "complexSearch" endpoint + * to find recipe IDs based on a given recipe name. + */ public class RecipeIdDAO { + // API key for authenticating requests to the Spoonacular API private static final String API_KEY = "5fcf2eef76af4e6893959ceefae0a087"; // + + // Base URL for the Spoonacular "complexSearch" endpoint private static final String BASE_URL = "https://api.spoonacular.com/recipes/complexSearch"; + /** + * Fetches the recipe ID for a given recipe name by querying the Spoonacular API. + * @param recipeName The name of the recipe to search for. + * @return The ID of the first recipe returned by the API, or -1 if no recipe is found or an error occurs. + */ public static int getRecipeIdByName(String recipeName) { try { + // Construct the full API URL with the recipe name and API key String apiUrl = BASE_URL + "?query=" + recipeName.replace(" ", "+") + "&apiKey=" + API_KEY; + // Create a URL object and open an HTTP connection URL url = new URL(apiUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); + // Check the API response code int responseCode = connection.getResponseCode(); if (responseCode != 200) { System.out.println("Error: API returned response code " + responseCode); return -1; } + // Read the response from the API BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder response = new StringBuilder(); String line; @@ -34,6 +51,7 @@ public static int getRecipeIdByName(String recipeName) { } reader.close(); + // Parse the JSON response JSONObject jsonResponse = new JSONObject(response.toString()); JSONArray results = jsonResponse.getJSONArray("results"); @@ -42,6 +60,7 @@ public static int getRecipeIdByName(String recipeName) { return -1; } + // Get the ID of the first recipe in the results JSONObject firstResult = results.getJSONObject(0); int recipeId = firstResult.getInt("id"); System.out.println("Recipe ID for " + recipeName + ": " + recipeId); diff --git a/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java b/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java index fb3d7c89e..0c2a7c5ac 100644 --- a/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java +++ b/src/main/java/use_case/nutrition_information/NutritionInformationInteractor.java @@ -10,31 +10,55 @@ import java.util.ArrayList; import java.util.List; +/** + * Interactor for handling nutrition information use case. + * This class acts as the intermediary between the data access layer and the presenter, + * orchestrating the logic to fetch and process nutrition information for a given recipe. + */ public class NutritionInformationInteractor implements NutritionInformationInputBoundary { + // DAO to fetch nutrition data from the data source API private final NutritionInformationDAO dataFetcher; + + // Presenter to format and deliver the output data private final NutritionInformationPresenter outputPresenter; + /** + * Constructor for the NutritionInformationInteractor. + * @param dataFetcher DAO responsible for fetching nutrition data. + * @param outputPresenter Presenter responsible for formatting the output data. + */ public NutritionInformationInteractor(NutritionInformationDAO dataFetcher ,NutritionInformationPresenter outputPresenter) { this.dataFetcher = dataFetcher; this.outputPresenter = outputPresenter; } + /** + * Fetches and processes nutrition information for a given recipe ID. + * @param inputData Input data containing the recipe ID. + * @return List of Nutrition objects representing the processed nutrition data. + */ @Override public List NutritionInformation(NutritionInformationInputData inputData) { - // Step 1: 从数据访问层获取原始 JSON 数据 + + // Step 1: Fetch raw JSON nutrition data using the DAO String jsonData = dataFetcher.fetchNutritionInformation(inputData.getRecipeId()); - // Step 2: 解析 JSON 数据 + // Step 2: Parse the raw JSON data into a list of Nutrition objects List nutritionList = parseNutritionData(jsonData); - // Step 3: 封装结果为 OutputData + // Step 3: Wrap the parsed data into the output data object NutritionInformationOutputData outputData = new NutritionInformationOutputData(nutritionList); - // Step 4: 调用 OutputBoundary 返回结果 + // Step 4: Use the presenter to format and deliver the result return outputPresenter.presentNutritionInformation(outputData); } + /** + * Helper Function:Parses raw JSON data into a list of Nutrition objects. + * @param jsonData Raw JSON string containing nutrition information. + * @return List of Nutrition objects parsed from the JSON data. + */ private List parseNutritionData(String jsonData) { List nutritionList = new ArrayList<>(); From 3fe6f90d81b8638f18ec0a3f34bba6ab622b2442 Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Mon, 2 Dec 2024 13:20:06 -0500 Subject: [PATCH 137/148] restored folders feature --- src/main/java/data_access/UserDAOImpl.java | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/data_access/UserDAOImpl.java b/src/main/java/data_access/UserDAOImpl.java index dc3311922..89bca3e53 100644 --- a/src/main/java/data_access/UserDAOImpl.java +++ b/src/main/java/data_access/UserDAOImpl.java @@ -127,16 +127,22 @@ private Map loadUsersFromFile() { } // reconstructing folders as a map of folderNames and lists of recipes - JSONObject foldersJson = userObject.getJSONObject("folders"); - Map> foldersMap = new HashMap<>(); + JSONObject foldersJson = new JSONObject(); + if (userObject.has("folders")) { + foldersJson = userObject.getJSONObject("folders"); + } + else { + foldersJson.put("folders", new JSONArray()); + } + final Map> foldersMap = new HashMap<>(); for (String folderName : foldersJson.keySet()) { - JSONArray recipesArray = foldersJson.getJSONArray(folderName); - List recipes = new ArrayList<>(); + final JSONArray recipesArray = foldersJson.getJSONArray(folderName); + final List recipes = new ArrayList<>(); for (int t = 0; t < recipesArray.length(); t++) { - JSONObject recipeJson = recipesArray.getJSONObject(t); - JSONArray ingredientsJson = recipeJson.getJSONArray("ingredients"); - List ingredients = new ArrayList<>(); + final JSONObject recipeJson = recipesArray.getJSONObject(t); + final JSONArray ingredientsJson = recipeJson.getJSONArray("ingredients"); + final List ingredients = new ArrayList<>(); for (int s = 0; s < ingredientsJson.length(); s++) { JSONObject ingredientJson = ingredientsJson.getJSONObject(s); @@ -147,7 +153,7 @@ private Map loadUsersFromFile() { ingredients.add(ingredient); } - Recipe recipe = new Recipe( + final Recipe recipe = new Recipe( recipeJson.getString("name"), recipeJson.getString("url"), ingredients, @@ -163,14 +169,14 @@ private Map loadUsersFromFile() { users.put(username, new User(username, password, recipeBookmarks, recipeRecentlyViewed, foldersMap)); System.out.println("Loaded user: " + username); // Debugging output } - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); saveUsersToFile(); } return users; } - // Save users to JSON file using org.json private void saveUsersToFile() { JSONArray usersArray = new JSONArray(); From 007c37897132d93e50c8fc50813322c15aeabea8 Mon Sep 17 00:00:00 2001 From: William Wu Date: Mon, 2 Dec 2024 13:21:32 -0500 Subject: [PATCH 138/148] adjusted number of recipes returned from search to 20 --- src/main/java/data_access/SpoonacularRecipeDAO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/data_access/SpoonacularRecipeDAO.java b/src/main/java/data_access/SpoonacularRecipeDAO.java index b383f5765..9f4f1a3ad 100644 --- a/src/main/java/data_access/SpoonacularRecipeDAO.java +++ b/src/main/java/data_access/SpoonacularRecipeDAO.java @@ -47,7 +47,7 @@ public List getRecipesByIngredients(List ingredients) { // Construct the URL with the ingredients query String endpoint = "/recipes/findByIngredients"; - String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&ingredients=" + ingredientsQuery + "&number=5"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&ingredients=" + ingredientsQuery + "&number=20"; // Build the request Request request = new Request.Builder() From b526d7ba34e78ed4f1530a1f22baca8250f9c6f1 Mon Sep 17 00:00:00 2001 From: Hongcheng Huo Date: Mon, 2 Dec 2024 13:29:31 -0500 Subject: [PATCH 139/148] add the test file for NutritionInformation --- .../NutritionInformationInteractorTset.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/test/java/nutrition_information_test/NutritionInformationInteractorTset.java diff --git a/src/test/java/nutrition_information_test/NutritionInformationInteractorTset.java b/src/test/java/nutrition_information_test/NutritionInformationInteractorTset.java new file mode 100644 index 000000000..8c4ce8ef0 --- /dev/null +++ b/src/test/java/nutrition_information_test/NutritionInformationInteractorTset.java @@ -0,0 +1,11 @@ +package nutrition_information_test; + +import org.junit.jupiter.api.Test; +import use_case.nutrition_information.NutritionInformationInputData; +import use_case.nutrition_information.NutritionInformationInteractor; + +public class NutritionInformationInteractorTset { + @Test + void testNutritionInformation() { + } +} From 027b8b92058f5a17815beaff892cae740507579e Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 2 Dec 2024 13:55:25 -0500 Subject: [PATCH 140/148] deleted old class --- .../filter_recipes/FilterRecipesUseCase.java | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 src/main/java/use_case/filter_recipes/FilterRecipesUseCase.java diff --git a/src/main/java/use_case/filter_recipes/FilterRecipesUseCase.java b/src/main/java/use_case/filter_recipes/FilterRecipesUseCase.java deleted file mode 100644 index c8a89045c..000000000 --- a/src/main/java/use_case/filter_recipes/FilterRecipesUseCase.java +++ /dev/null @@ -1,37 +0,0 @@ -package use_case.filter_recipes; - -import java.util.ArrayList; -import java.util.List; - -import data_access.RecipeDAO; -import entity.Recipe; - -/** - * Use case layer for filtering recipes. - */ -public class FilterRecipesUseCase { - // to be implemented - // database from api? - private final RecipeDAO recipeDao; - - public FilterRecipesUseCase(RecipeDAO recipeDao) { - this.recipeDao = recipeDao; - } - - /** - * Get the recipes according to the dietary preference. - * @param diet user's diet choice - * @return a list of recipes that satisfy diet choice - */ - public List filterByDiet(String diet) { - final List recipes = recipeDao.getAllRecipes(); - final List filteredRecipes = new ArrayList<>(); - for (Recipe recipe : recipes) { - if (recipe.getDietaryType().equals(diet)) { - filteredRecipes.add(recipe); - } - } - return filteredRecipes; - } - -} From ea980f3d663e81f629059b1e04fbf47bb0d35900 Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Mon, 2 Dec 2024 18:17:44 -0500 Subject: [PATCH 141/148] implemented search recipe list by ingredient interactor test --- ...emorySearchRecipeListDataAccessObject.java | 57 ++++ .../data_access/SpoonacularRecipeDAO.java | 5 +- src/main/java/data_access/UserDAOImpl.java | 63 ++++- .../interface_adapter/RecipeListState.java | 68 ++--- .../RecipeListViewModel.java | 28 +- ...earchRecipeListByIngredientController.java | 60 +++-- ...SearchRecipeListByIngredientPresenter.java | 56 ++-- ...peListByIngredientDataAccessInterface.java | 8 +- ...SearchRecipeListByIngredientInputData.java | 18 +- ...earchRecipeListByIngredientInteractor.java | 69 ++--- src/main/java/view/BookmarkView.java | 7 +- src/main/java/view/FolderView.java | 5 +- src/main/java/view/HomePage.java | 5 +- src/main/java/view/IndividualRecipeView.java | 73 ++--- src/main/java/view/RecentlyViewedView.java | 5 +- src/main/java/view/RecipeListView.java | 39 ++- ...hRecipeListByIngredientInteractorTest.java | 252 +++++++++++------- 17 files changed, 520 insertions(+), 298 deletions(-) create mode 100644 src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java diff --git a/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java b/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java new file mode 100644 index 000000000..c5fde7c93 --- /dev/null +++ b/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java @@ -0,0 +1,57 @@ +package data_access; + +import entity.Ingredient; +import entity.Recipe; +import entity.User; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientDataAccessInterface; + +import java.util.*; + +public class InMemorySearchRecipeListDataAccessObject implements SearchRecipeListByIngredientDataAccessInterface { + private final Map users = new HashMap<>(); + + private String currentUsername; + + @Override + public List searchRecipeListByIngredient(List ingredients, User user, String folder) { + final User userFromFile = users.get(user.getUsername()); + List recipeList = new ArrayList<>(); + final List results = new ArrayList<>(); + if ("bookmarks".equals(folder)) { + recipeList.addAll(userFromFile.getBookmarks()); + } + else if ("recentlyViewed".equals(folder)) { + recipeList.addAll(userFromFile.getRecentlyViewed()); + } + else { + recipeList.addAll(userFromFile.getFolder(folder)); + } + for (Recipe recipe : recipeList) { + boolean match = false; + final List recipeIngredients = recipe.getIngredients(); + final List recipeIngredientsString = new ArrayList<>(); + for (Ingredient ingredient : recipeIngredients) { + final String[] words = ingredient.getName().split(" "); + recipeIngredientsString.addAll(Arrays.asList(words)); + } + for (String recipeIngredient : recipeIngredientsString) { + for (String enteredIngredient : ingredients) { + if (recipeIngredient.equalsIgnoreCase(enteredIngredient)) { + match = true; + } + } + + } + if (match) { + results.add(recipe); + } + } + return results; + } + + @Override + public boolean addUser(User user) { + users.put(user.getUsername(), user); + return true; + } +} diff --git a/src/main/java/data_access/SpoonacularRecipeDAO.java b/src/main/java/data_access/SpoonacularRecipeDAO.java index 9f4f1a3ad..194df4fbd 100644 --- a/src/main/java/data_access/SpoonacularRecipeDAO.java +++ b/src/main/java/data_access/SpoonacularRecipeDAO.java @@ -15,7 +15,7 @@ import java.util.stream.Collectors; public class SpoonacularRecipeDAO implements RecipeDAO, FilterRecipesDataAccessInterface { - private static final String API_KEY = "38143f425a9f4698bd2e63550825a4ce"; // Replace with your Spoonacular API key + private static final String API_KEY = AppConstants.API_KEY; private static final String BASE_URL = "https://api.spoonacular.com"; private final OkHttpClient client; @@ -47,7 +47,8 @@ public List getRecipesByIngredients(List ingredients) { // Construct the URL with the ingredients query String endpoint = "/recipes/findByIngredients"; - String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&ingredients=" + ingredientsQuery + "&number=20"; + String url = BASE_URL + endpoint + "?apiKey=" + API_KEY + "&ingredients=" + ingredientsQuery + "&number=" + + AppConstants.NUMBER_OF_RESULTS; // Build the request Request request = new Request.Builder() diff --git a/src/main/java/data_access/UserDAOImpl.java b/src/main/java/data_access/UserDAOImpl.java index 89bca3e53..b53d58fbd 100644 --- a/src/main/java/data_access/UserDAOImpl.java +++ b/src/main/java/data_access/UserDAOImpl.java @@ -6,16 +6,14 @@ import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONTokener; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientDataAccessInterface; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -public class UserDAOImpl implements UserDAO { +public class UserDAOImpl implements UserDAO, SearchRecipeListByIngredientDataAccessInterface { private static final String FILE_PATH = "users.json"; private Map usersDatabase; @@ -134,15 +132,15 @@ private Map loadUsersFromFile() { else { foldersJson.put("folders", new JSONArray()); } - final Map> foldersMap = new HashMap<>(); + Map> foldersMap = new HashMap<>(); for (String folderName : foldersJson.keySet()) { - final JSONArray recipesArray = foldersJson.getJSONArray(folderName); - final List recipes = new ArrayList<>(); + JSONArray recipesArray = foldersJson.getJSONArray(folderName); + List recipes = new ArrayList<>(); for (int t = 0; t < recipesArray.length(); t++) { - final JSONObject recipeJson = recipesArray.getJSONObject(t); - final JSONArray ingredientsJson = recipeJson.getJSONArray("ingredients"); - final List ingredients = new ArrayList<>(); + JSONObject recipeJson = recipesArray.getJSONObject(t); + JSONArray ingredientsJson = recipeJson.getJSONArray("ingredients"); + List ingredients = new ArrayList<>(); for (int s = 0; s < ingredientsJson.length(); s++) { JSONObject ingredientJson = ingredientsJson.getJSONObject(s); @@ -153,7 +151,7 @@ private Map loadUsersFromFile() { ingredients.add(ingredient); } - final Recipe recipe = new Recipe( + Recipe recipe = new Recipe( recipeJson.getString("name"), recipeJson.getString("url"), ingredients, @@ -169,8 +167,7 @@ private Map loadUsersFromFile() { users.put(username, new User(username, password, recipeBookmarks, recipeRecentlyViewed, foldersMap)); System.out.println("Loaded user: " + username); // Debugging output } - } - catch (IOException e) { + } catch (IOException e) { e.printStackTrace(); saveUsersToFile(); } @@ -276,4 +273,42 @@ public List getFolderFromFile(String username, String folder) { System.out.println("Loaded folder: " + user.getFolder(folder)); return user.getFolder(folder); } + + @Override + public List searchRecipeListByIngredient(List ingredients, User user, String folder) { + final Map users = loadUsersFromFile(); + final User userFromFile = users.get(user.getUsername()); + List recipeList = new ArrayList<>(); + final List results = new ArrayList<>(); + if ("bookmarks".equals(folder)) { + recipeList.addAll(userFromFile.getBookmarks()); + } + else if ("recentlyViewed".equals(folder)) { + recipeList.addAll(userFromFile.getRecentlyViewed()); + } + else { + recipeList.addAll(userFromFile.getFolder(folder)); + } + for (Recipe recipe : recipeList) { + boolean match = false; + final List recipeIngredients = recipe.getIngredients(); + final List recipeIngredientsString = new ArrayList<>(); + for (Ingredient ingredient : recipeIngredients) { + final String[] words = ingredient.getName().split(" "); + recipeIngredientsString.addAll(Arrays.asList(words)); + } + for (String recipeIngredient : recipeIngredientsString) { + for (String enteredIngredient : ingredients) { + if (recipeIngredient.equalsIgnoreCase(enteredIngredient)) { + match = true; + } + } + + } + if (match) { + results.add(recipe); + } + } + return results; + } } diff --git a/src/main/java/interface_adapter/RecipeListState.java b/src/main/java/interface_adapter/RecipeListState.java index 02e8c0f9a..fbf9a36b5 100644 --- a/src/main/java/interface_adapter/RecipeListState.java +++ b/src/main/java/interface_adapter/RecipeListState.java @@ -1,37 +1,37 @@ -//package interface_adapter; -// -///** -// * The State information representing the recipe list. -// */ -//public class RecipeListState { -// private String username = ""; -// -// private String password = ""; -// private String passwordError; -// -// public RecipeListState(???) { +package interface_adapter; + +/** + * The State information representing the recipe list. + */ +public class RecipeListState { + private String username = ""; + + private String password = ""; + private String passwordError; + + public RecipeListState() { // username = copy.username; // password = copy.password; // passwordError = copy.passwordError; -// } -// -// public String getUsername() { -// return username; -// } -// -// public void setUsername(String username) { -// this.username = username; -// } -// -// public void setPassword(String password) { -// this.password = password; -// } -// -// public void setPasswordError(String passwordError) { -// this.passwordError = passwordError; -// } -// -// public String getPassword() { -// return password; -// } -//} + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setPasswordError(String passwordError) { + this.passwordError = passwordError; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/interface_adapter/RecipeListViewModel.java b/src/main/java/interface_adapter/RecipeListViewModel.java index 121499319..54a1da32e 100644 --- a/src/main/java/interface_adapter/RecipeListViewModel.java +++ b/src/main/java/interface_adapter/RecipeListViewModel.java @@ -1,14 +1,14 @@ -//package interface_adapter; -// -///** -// * The View Model for the RecipeListView. -// */ -//public class RecipeListViewModel extends ViewModel { -// -// public RecipeListViewModel() { -// // TODO -// super("???"); -// setState(new RecipeListState()); -// } -// -//} +package interface_adapter; + +/** + * The View Model for the RecipeListView. + */ +public class RecipeListViewModel extends ViewModel { + + public RecipeListViewModel() { + // TODO + super("???"); + setState(new RecipeListState()); + } + +} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java index b2c11ca3a..42f3a4000 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientController.java @@ -1,28 +1,32 @@ -//package interface_adapter.search_recipe_list_by_ingredient.; -// -//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; -//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputData; -// -//import java.util.List; -// -///** -// * Controller for the Search Recipe List By Ingredient Use Case. -// */ -//public class SearchRecipeListByIngredientController { -// private final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor; -// -// public SearchRecipeListByIngredientController( -// SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor) { -// this.searchRecipeListByIngredientUseCaseInteractor = searchRecipeListByIngredientUseCaseInteractor; -// } -// -// /** -// * Executes the Search Recipe List By Ingredient Use Case. -// * @param ingredients the ingredients entered by the user -// */ -// public void execute(List ingredients) { -// final SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData = new SearchRecipeListByIngredientInputData(ingredients); -// -// searchRecipeListByIngredientUseCaseInteractor.execute(searchRecipeListByIngredientInputData); -// } -//} +package interface_adapter.search_recipe_list_by_ingredient; + +import entity.User; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputData; + +import java.util.List; + +/** + * Controller for the Search Recipe List By Ingredient Use Case. + */ +public class SearchRecipeListByIngredientController { + private final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor; + + public SearchRecipeListByIngredientController( + SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientUseCaseInteractor) { + this.searchRecipeListByIngredientUseCaseInteractor = searchRecipeListByIngredientUseCaseInteractor; + } + + /** + * Executes the Search Recipe List By Ingredient Use Case. + * @param ingredients the ingredients entered by the user. + * @param user the user. + * @param folder the folder to search in. + */ + public void execute(List ingredients, User user, String folder) { + final SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData = + new SearchRecipeListByIngredientInputData(ingredients, user, folder); + + searchRecipeListByIngredientUseCaseInteractor.execute(searchRecipeListByIngredientInputData); + } +} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java index fcb9b83ac..079d67497 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java @@ -1,28 +1,28 @@ -//package interface_adapter.search_recipe_list_by_ingredient; -// -//import interface_adapter.RecipeListViewModel; -//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; -//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputData; -// -///** -// * The Presenter for the Search Recipe List By Ingredients Use Case. -// */ -//public class SearchRecipeListByIngredientPresenter implements SearchRecipeListByIngredientOutputBoundary { -// -// private final RecipeListViewModel recipeListViewModel; -// -// public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewModel) { -// this.recipeListViewModel = recipeListViewModel; -// } -// -// @Override -// public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { -// // TODO -// recipeListViewModel.firePropertyChanged("???"); -// } -// -// @Override -// public void prepareFailView(String error) { -// // note: this use case currently can't fail -// } -//} +package interface_adapter.search_recipe_list_by_ingredient; + +import interface_adapter.RecipeListViewModel; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputData; + +/** + * The Presenter for the Search Recipe List By Ingredients Use Case. + */ +public class SearchRecipeListByIngredientPresenter implements SearchRecipeListByIngredientOutputBoundary { + + private final RecipeListViewModel recipeListViewModel; + + public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewModel) { + this.recipeListViewModel = recipeListViewModel; + } + + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { + // TODO + recipeListViewModel.firePropertyChanged("???"); + } + + @Override + public void prepareFailView(String error) { + // note: this use case currently can't fail + } +} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java index 2fd2a6c94..c7fd09faa 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java @@ -1,5 +1,6 @@ package use_case.search_recipe_list_by_ingredient; +import entity.Recipe; import entity.User; import java.util.List; @@ -12,6 +13,11 @@ public interface SearchRecipeListByIngredientDataAccessInterface { /** * Searches recipes by ingredient. * @param ingredients the ingredients entered. + * @param user the user. + * @param folder the folder to search in. + * @return search results. */ - void searchRecipeListByIngredient(List ingredients); + List searchRecipeListByIngredient(List ingredients, User user, String folder); + + boolean addUser(User user); } diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java index bc3c69659..b58fb42ac 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java @@ -1,5 +1,7 @@ package use_case.search_recipe_list_by_ingredient; +import entity.User; + import java.util.List; /** @@ -8,12 +10,24 @@ public class SearchRecipeListByIngredientInputData { private final List ingredients; + private final User user; + private final String folder; - public SearchRecipeListByIngredientInputData(List ingredients) { + public SearchRecipeListByIngredientInputData(List ingredients, User user, String folder) { this.ingredients = ingredients; + this.user = user; + this.folder = folder; } - List getIngredients() { + public List getIngredients() { return ingredients; } + + public User getUser() { + return user; + } + + public String getFolder() { + return folder; + } } diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java index 9ea20defa..3ce283038 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java @@ -1,34 +1,35 @@ -//package use_case.search_recipe_list_by_ingredient; -// -//import entity.User; -// -//public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { -// -// /** -// * The Change Password Interactor. -// */ -// private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; -// private final SearchRecipeListByIngredientOutputBoundary userPresenter; -// private final User user; -// -// public SearchRecipeListByIngredientInteractor( -// SearchRecipeListByIngredientDataAccessInterface searchRecipeListByIngredientDataAccessInterface, -// SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary) { -// this.userDataAccessObject = searchRecipeListByIngredientDataAccessInterface; -// this.userPresenter = searchRecipeListByIngredientOutputBoundary; -// } -// -// @Override -// public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { -// final User user = new User(searchRecipeListByIngredientInputData.getusername(), -// searchRecipeListByIngredientInputData.getPassword(), -// searchRecipeListByIngredientInputData.get... -// ) -// userDataAccessObject.metjod(user); -// -// final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = -// new SearchRecipeListByIngredientOutputData(recipes, -// false); -// userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); -// } -//} +package use_case.search_recipe_list_by_ingredient; + +import entity.Recipe; +import entity.User; + +import java.util.List; + +public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { + + /** + * The Change Password Interactor. + */ + private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; + private final SearchRecipeListByIngredientOutputBoundary userPresenter; + + public SearchRecipeListByIngredientInteractor( + SearchRecipeListByIngredientDataAccessInterface searchRecipeListByIngredientDataAccessInterface, + SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary) { + this.userDataAccessObject = searchRecipeListByIngredientDataAccessInterface; + this.userPresenter = searchRecipeListByIngredientOutputBoundary; + } + + @Override + public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { + final List ingredients = searchRecipeListByIngredientInputData.getIngredients(); + final User user = searchRecipeListByIngredientInputData.getUser(); + final String folder = searchRecipeListByIngredientInputData.getFolder(); + final List recipes = userDataAccessObject.searchRecipeListByIngredient(ingredients, user, folder); + + final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = + new SearchRecipeListByIngredientOutputData(recipes, + false); + userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); + } +} diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index 282c31535..9878c36c3 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -5,6 +5,7 @@ import javax.swing.*; +import interface_adapter.RecipeListViewModel; import org.jetbrains.annotations.NotNull; import entity.Recipe; @@ -12,8 +13,8 @@ public class BookmarkView extends RecipeListView { - public BookmarkView(User user, String folderName) { - super(user, folderName); + public BookmarkView(User user, String folderName, RecipeListViewModel recipeListViewModel) { + super(user, folderName, recipeListViewModel); setTitle(user.getUsername() + "'s Bookmarks"); final JPanel folderPanel = new JPanel(); @@ -38,7 +39,7 @@ private JComboBox getDropdown(User user) { dropdown.addActionListener(event -> { final String selectedOption = (String) dropdown.getSelectedItem(); System.out.println(selectedOption + "folder name in bookmark view"); - new FolderView(user, selectedOption); + new FolderView(user, selectedOption, new RecipeListViewModel()); }); return dropdown; } diff --git a/src/main/java/view/FolderView.java b/src/main/java/view/FolderView.java index 12e564146..d04bc7283 100644 --- a/src/main/java/view/FolderView.java +++ b/src/main/java/view/FolderView.java @@ -4,11 +4,12 @@ import entity.Recipe; import entity.User; +import interface_adapter.RecipeListViewModel; public class FolderView extends RecipeListView { - public FolderView(User user, String folderName) { - super(user, folderName); + public FolderView(User user, String folderName, RecipeListViewModel recipeListViewModel) { + super(user, folderName, recipeListViewModel); setTitle(folderName); } diff --git a/src/main/java/view/HomePage.java b/src/main/java/view/HomePage.java index 016e8c10c..21d4c9298 100644 --- a/src/main/java/view/HomePage.java +++ b/src/main/java/view/HomePage.java @@ -11,6 +11,7 @@ import data_access.SpoonacularRecipeDAO; import entity.User; import interface_adapter.RecipeController; +import interface_adapter.RecipeListViewModel; import interface_adapter.SearchRecipePresenter; import interface_adapter.ShoppingListController; import use_case.ShoppingListUseCase; @@ -63,14 +64,14 @@ public void actionPerformed(ActionEvent e) { final JButton bookmarksButton = new JButton("Bookmarks"); bookmarksButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); bookmarksButton.addActionListener(event -> { - new BookmarkView(this.user, null); + new BookmarkView(this.user, null, new RecipeListViewModel()); }); // Recently Viewed Button final JButton recentlyViewedButton = new JButton("Recently Viewed"); recentlyViewedButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); recentlyViewedButton.addActionListener(event -> { - new RecentlyViewedView(this.user, null); + new RecentlyViewedView(this.user, null, new RecipeListViewModel()); }); // Putting the buttons together diff --git a/src/main/java/view/IndividualRecipeView.java b/src/main/java/view/IndividualRecipeView.java index d4334de88..a96548e4a 100644 --- a/src/main/java/view/IndividualRecipeView.java +++ b/src/main/java/view/IndividualRecipeView.java @@ -8,8 +8,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; -import data_access.RecipeIdDAO; import data_access.UserDAOImpl; import entity.*; @@ -18,7 +19,6 @@ public class IndividualRecipeView extends JFrame implements ActionListener { private final JButton bookmarkButton; private final JButton urlButton; private JList ingredientsJLIst; - // if we want to display the used and missed ingredients separately... private JList usedIngredientsJList; private final Recipe recipe; private URL imageUrl; private ImageIcon imageIcon; @@ -31,34 +31,24 @@ public IndividualRecipeView(Recipe recipe, User user) { this.user = user; this.userDAO = new UserDAOImpl(); - // Add this recipe to the user's recently viewed list + // Add this recipe to the user's recently viewed list upon the page's opening userDAO.addRecentlyViewedToFile(this.user.getUsername(), this.recipe); - // Initialize ingredient list - final DefaultListModel listModel = new DefaultListModel<>(); - for (Ingredient ingredient : recipe.getIngredients()) { - listModel.addElement(ingredient.getName() + ", Amount: " + ingredient.getAmount() + " " + ingredient.getUnit()); - } - ingredientsJLIst = new JList<>(listModel); - final JScrollPane scrollPane = new JScrollPane(ingredientsJLIst); - scrollPane.setPreferredSize(ingredientsJLIst.getPreferredScrollableViewportSize()); - - // Initialize buttons - nutritionButton = new JButton("Nutrition"); - bookmarkButton = new JButton("Bookmark"); - urlButton = new JButton("Open Recipe in Browser"); - // Set up JFrame properties setTitle(recipe.getName()); - setSize(800, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - // Main panel with vertical BoxLayout - you need a Layout for each Panel + // Initialize main panel with vertical BoxLayout final JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); setContentPane(mainPanel); - // initializing image + // Initialize buttons + nutritionButton = new JButton("Nutrition"); + bookmarkButton = new JButton("Bookmark"); + urlButton = new JButton("Open Recipe in Browser"); + + // Initialize image try { // Specify the image URL if (recipe.getImage() == null) { @@ -82,7 +72,7 @@ public IndividualRecipeView(Recipe recipe, User user) { imageLabel.setHorizontalAlignment(SwingConstants.CENTER); imageLabel.setVerticalAlignment(SwingConstants.CENTER); - // Add the label to the frame + // Add the label to the main panel mainPanel.add(imageLabel, BorderLayout.CENTER); } catch (Exception e) { @@ -93,10 +83,17 @@ public IndividualRecipeView(Recipe recipe, User user) { e.printStackTrace(); } - // Add components to main panel + // Initialize ingredient list + final DefaultListModel listModel = new DefaultListModel<>(); + for (Ingredient ingredient : recipe.getIngredients()) { + listModel.addElement(ingredient.getName() + ", Amount: " + ingredient.getAmount() + " " + ingredient.getUnit()); + } + ingredientsJLIst = new JList<>(listModel); + final JScrollPane scrollPane = new JScrollPane(ingredientsJLIst); + scrollPane.setPreferredSize(ingredientsJLIst.getPreferredScrollableViewportSize()); mainPanel.add(scrollPane); - // Button panel with horizontal BoxLayout + // Initialize button panel with horizontal BoxLayout final JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(nutritionButton); @@ -104,29 +101,39 @@ public IndividualRecipeView(Recipe recipe, User user) { buttonPanel.add(bookmarkButton); buttonPanel.add(Box.createRigidArea(new Dimension(10, 0))); buttonPanel.add(urlButton); - mainPanel.add(buttonPanel); - // Add action listeners + // Add action listeners to buttons nutritionButton.addActionListener(this); bookmarkButton.addActionListener(this); urlButton.addActionListener(this); + // Initialize dropdown menu for adding the recipe to folders + String[] options = user.getFolders().keySet().toArray(new String[0]); + final JComboBox dropdown = new JComboBox<>(options); + final JLabel dropdownLabel = new JLabel("Add this recipe to a folder"); + dropdown.add(dropdownLabel); + dropdown.addActionListener(folderEvent -> { + final String selectedOption = (String) dropdown.getSelectedItem(); + userDAO.addRecipeToFolderInFile(this.user.getUsername(), selectedOption, this.recipe); + JOptionPane.showMessageDialog(this, "Recipe added to " + selectedOption + "!"); + }); + mainPanel.add(dropdown); + // Display the frame + pack(); setVisible(true); } @Override public void actionPerformed(ActionEvent event) { - if (event.getSource() == nutritionButton) { - RecipeIdDAO getRecipeIdDAO = new RecipeIdDAO(); - int recipeId = getRecipeIdDAO.getRecipeIdByName(recipe.getName()); - new NutritionInformationView(recipeId); - } - else if (event.getSource() == bookmarkButton) { - // TODO complete bookmark function +// if (event.getSource() == nutritionButton) { +// RecipeIdDAO getRecipeIdDAO = new RecipeIdDAO(); +// int recipeId = getRecipeIdDAO.getRecipeIdByName(recipe.getName()); +// new NutritionInformationView(recipeId); +// } + if (event.getSource() == bookmarkButton) { if (!user.getBookmarks().contains(recipe)) { - // user.addBookmark(recipe); userDAO.addBookmarkToFile(user.getUsername(), recipe); JOptionPane.showMessageDialog(this, "Recipe added to bookmarks!"); } else { diff --git a/src/main/java/view/RecentlyViewedView.java b/src/main/java/view/RecentlyViewedView.java index 71c0f6e78..8c2681dd4 100644 --- a/src/main/java/view/RecentlyViewedView.java +++ b/src/main/java/view/RecentlyViewedView.java @@ -6,11 +6,12 @@ import entity.Recipe; import entity.User; +import interface_adapter.RecipeListViewModel; public class RecentlyViewedView extends RecipeListView { - public RecentlyViewedView(User user, String folderName) { - super(user, folderName); + public RecentlyViewedView(User user, String folderName, RecipeListViewModel recipeListViewModel) { + super(user, folderName, recipeListViewModel); setTitle(user.getUsername() + "'s Recently Viewed"); final JButton clearButton = new JButton("Clear Recently Viewed"); clearButton.addActionListener(event -> { diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index 3db7f88a4..d5ce86adf 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -5,6 +5,8 @@ import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -16,10 +18,13 @@ import entity.Recipe; import entity.User; import interface_adapter.RecipeListController; +import interface_adapter.RecipeListState; +import interface_adapter.RecipeListViewModel; +import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.SearchRecipeListByNameUseCase; -public abstract class RecipeListView extends JFrame implements ActionListener { +public abstract class RecipeListView extends JFrame implements ActionListener, PropertyChangeListener { private static User user; protected static UserDAOImpl userDAO; protected final JList recipeList; @@ -36,11 +41,17 @@ public abstract class RecipeListView extends JFrame implements ActionListener { private JComboBox cuisineComboBox; private SpoonacularRecipeDAO spoonacularRecipeDAO; + private final String folderName; + + private final RecipeListViewModel recipeListViewModel; + private SearchRecipeListByIngredientController searchRecipeListByIngredientController; + /* Generates the default view of a list of recipes associated with a User. */ - public RecipeListView(User user, String folderName) { + public RecipeListView(User user, String folderName, RecipeListViewModel recipeListViewModel) { this.user = user; + this.folderName = folderName; this.userDAO = new UserDAOImpl(); this.recipeList = new JList<>(); this.listModel = new DefaultListModel<>(); @@ -49,6 +60,9 @@ public RecipeListView(User user, String folderName) { new SearchRecipeListByNameUseCase(getRecipeList(userDAO.findUserByUsername(user.getUsername())))); this.spoonacularRecipeDAO = new SpoonacularRecipeDAO(); + this.recipeListViewModel = recipeListViewModel; + this.recipeListViewModel.addPropertyChangeListener(this); + // Initialize recipe list to display List recipes = new ArrayList<>(); // Split into cases: BookmarkView/RecentlyViewedView and FolderView @@ -133,7 +147,7 @@ public void mouseClicked(MouseEvent event) { filterPanel.add(cuisineComboBox); add(filterPanel, BorderLayout.EAST); - + dietComboBox.addActionListener(e -> applyFilters()); cuisineComboBox.addActionListener(e -> applyFilters()); @@ -168,6 +182,12 @@ public void actionPerformed(ActionEvent event) { ingredientSearchListModel.addElement(recipe); } recipeList.setModel(ingredientSearchListModel); + + // additions... + + final RecipeListState currentState = recipeListViewModel.getState(); + + this.searchRecipeListByIngredientController.execute(ingredients, user, folderName); } if (event.getSource() == recipeSearchButton) { final String userInput = recipeSearchField.getText(); @@ -224,4 +244,17 @@ private void applyFilters() { protected abstract List getRecipeList(User user1); protected abstract List getRecipeList(User user1, String folderName); + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (event.getPropertyName().equals("state")) { + final RecipeListState state = (RecipeListState) event.getNewValue(); + // TODO recipeList.setModel(ingredientSearchListModel); + } + } + + public void setSearchRecipeListByIngredientController( + SearchRecipeListByIngredientController SearchRecipeListByIngredientController) { + this.searchRecipeListByIngredientController = SearchRecipeListByIngredientController; + } } diff --git a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java index b222e7b07..678d5a427 100644 --- a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java +++ b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java @@ -1,96 +1,156 @@ -//package use_case.search_recipe_list_by_ingredient; -// -//import data_access.InMemoryUserDataAccessObject; -//import entity.User; -//import org.junit.jupiter.api.Test; -// -//import java.util.List; -// -//import static org.junit.jupiter.api.Assertions.*; -// -//class SearchRecipeListByIngredientInteractorTest { -// private static final List ingredients = List.of("chicken", "mushroom", "tomato"); -// -// @Test -// void successTest() { -// SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData(ingredients); -// SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); -// -// // For the success test, we need to add Paul to the data access repository before we log in. -// User user = factory.create("Paul", "password"); -// userRepository.save(user); -// -// -// // This creates a successPresenter that tests whether the test case is as we expect. -// SearchRecipeListByIngredientOutputBoundary successPresenter = -// new SearchRecipeListByIngredientOutputBoundary() { -// @Override -// public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { -// assertEquals("expected", ??user.getUsername()??); -// } -// -// @Override -// public void prepareFailView(String error) { -// fail("Use case failure is unexpected."); -// } -// }; -// -// SearchRecipeListByIngredientInputBoundary interactor = -// new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); -// interactor.execute(inputData); -// } -// -// @Test -// void noMatchingResultsTest() { -// LoginInputData inputData = new LoginInputData("Paul", "wrong"); -// LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); -// -// // For this failure test, we need to add Paul to the data access repository before we log in, and -// // the passwords should not match. -// UserFactory factory = new CommonUserFactory(); -// User user = factory.create("Paul", "password"); -// userRepository.save(user); -// -// // This creates a presenter that tests whether the test case is as we expect. -// LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { -// @Override -// public void prepareSuccessView(LoginOutputData user) { -// // this should never be reached since the test case should fail -// fail("Use case success is unexpected."); -// } -// -// @Override -// public void prepareFailView(String error) { -// assertEquals("Incorrect password for \"Paul\".", error); -// } -// }; -// -// LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); -// interactor.execute(inputData); -// } -// -// @Test -// void emptyQueryTest() { -// LoginInputData inputData = new LoginInputData("Paul", "password"); -// LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); -// -// // Add Paul to the repo so that when we check later they already exist -// -// // This creates a presenter that tests whether the test case is as we expect. -// LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { -// @Override -// public void prepareSuccessView(LoginOutputData user) { -// // this should never be reached since the test case should fail -// fail("Use case success is unexpected."); -// } -// -// @Override -// public void prepareFailView(String error) { -// assertEquals("Paul: Account does not exist.", error); -// } -// }; -// -// LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); -// interactor.execute(inputData); -// } -//} \ No newline at end of file +package use_case.search_recipe_list_by_ingredient; + +import data_access.InMemorySearchRecipeListDataAccessObject; +import entity.Ingredient; +import entity.Recipe; +import entity.User; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class SearchRecipeListByIngredientInteractorTest { + private static final Recipe recipe1 = new Recipe( + "recipe1", + "url", + List.of( + new Ingredient("chicken", 1.0, "unit"), + new Ingredient("mushroom", 1.0, "unit")), + "image"); + private static final Recipe recipe2 = new Recipe( + "recipe2", + "url", + List.of( + new Ingredient("tomato", 1.0, "unit"), + new Ingredient("onion", 1.0, "unit")), + "image"); + private static final Recipe recipe3 = new Recipe( + "recipe3", + "url", + List.of( + new Ingredient("lettuce", 1.0, "unit"), + new Ingredient("pork", 1.0, "unit")), + "image"); + private static final List bookmarks = List.of(recipe1, recipe2, recipe3); + private static final List recentlyViewed = List.of(recipe1, recipe2, recipe3); + + @Test + void successTest() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final List ingredients = List.of("chicken", "mushroom", "tomato"); + + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData( + ingredients, user, "bookmarks"); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void successTestForFolder() { + final Map> folders = new HashMap<>(); + folders.put("dinner", bookmarks); + folders.put("lunch", List.of()); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final List ingredients = List.of("chicken", "mushroom", "tomato"); + + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData( + ingredients, user, "dinner"); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void emptyQueryTest() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData( + List.of(), user, "bookmarks"); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData searchResults) { + assertEquals(List.of(), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void noMatchingIngredientTest() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final List ingredients = List.of("triangle", "tree"); + + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData( + ingredients, user, "bookmarks"); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData searchResults) { + assertEquals(List.of(), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } +} \ No newline at end of file From 6f2af2e6a5ce1c2a22c369c4083f55f92ac88b5e Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Mon, 2 Dec 2024 18:47:11 -0500 Subject: [PATCH 142/148] created DAI, input/output boundaries, input/output data and interactor for search recipe by name; implemented corresponding test --- ...emorySearchRecipeListDataAccessObject.java | 31 ++- src/main/java/data_access/UserDAOImpl.java | 32 ++- .../RecipeListController.java | 2 +- .../bookmark/BookmarkController.java | 2 +- ...chRecipeListByNameDataAccessInterface.java | 23 +++ .../SearchRecipeListByNameInputBoundary.java | 14 ++ .../SearchRecipeListByNameInputData.java | 31 +++ .../SearchRecipeListByNameInteractor.java | 35 ++++ .../SearchRecipeListByNameOutputBoundary.java | 18 ++ .../SearchRecipeListByNameOutputData.java | 28 +++ .../SearchRecipeListByNameUseCase.java | 4 +- src/main/java/view/RecipeListView.java | 2 +- ...hRecipeListByIngredientInteractorTest.java | 31 ++- .../SearchRecipeListByNameInteractorTest.java | 187 ++++++++++++++++++ 14 files changed, 431 insertions(+), 9 deletions(-) create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameDataAccessInterface.java create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputBoundary.java create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputData.java create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputBoundary.java create mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputData.java rename src/main/java/use_case/{ => search_recipe_list_by_name}/SearchRecipeListByNameUseCase.java (91%) create mode 100644 src/test/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractorTest.java diff --git a/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java b/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java index c5fde7c93..aaa69d932 100644 --- a/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java +++ b/src/main/java/data_access/InMemorySearchRecipeListDataAccessObject.java @@ -4,10 +4,12 @@ import entity.Recipe; import entity.User; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientDataAccessInterface; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameDataAccessInterface; import java.util.*; -public class InMemorySearchRecipeListDataAccessObject implements SearchRecipeListByIngredientDataAccessInterface { +public class InMemorySearchRecipeListDataAccessObject implements SearchRecipeListByIngredientDataAccessInterface, + SearchRecipeListByNameDataAccessInterface { private final Map users = new HashMap<>(); private String currentUsername; @@ -49,6 +51,33 @@ else if ("recentlyViewed".equals(folder)) { return results; } + @Override + public List searchRecipeListByName(String recipeName, User user, String folder) { + final User userFromFile = users.get(user.getUsername()); + final List results = new ArrayList<>(); + + List recipeList = new ArrayList<>(); + if ("bookmarks".equals(folder)) { + recipeList.addAll(userFromFile.getBookmarks()); + } + else if ("recentlyViewed".equals(folder)) { + recipeList.addAll(userFromFile.getRecentlyViewed()); + } + else { + recipeList.addAll(userFromFile.getFolder(folder)); + } + + for (Recipe recipe : recipeList) { + final String[] words = recipe.getName().split(" "); + for (String word : words) { + if (word.equalsIgnoreCase(recipeName)) { + results.add(recipe); + } + } + } + return results; + } + @Override public boolean addUser(User user) { users.put(user.getUsername(), user); diff --git a/src/main/java/data_access/UserDAOImpl.java b/src/main/java/data_access/UserDAOImpl.java index b53d58fbd..cdc062803 100644 --- a/src/main/java/data_access/UserDAOImpl.java +++ b/src/main/java/data_access/UserDAOImpl.java @@ -7,13 +7,15 @@ import org.json.JSONObject; import org.json.JSONTokener; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientDataAccessInterface; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameDataAccessInterface; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.*; -public class UserDAOImpl implements UserDAO, SearchRecipeListByIngredientDataAccessInterface { +public class UserDAOImpl implements UserDAO, SearchRecipeListByIngredientDataAccessInterface, + SearchRecipeListByNameDataAccessInterface { private static final String FILE_PATH = "users.json"; private Map usersDatabase; @@ -311,4 +313,32 @@ else if ("recentlyViewed".equals(folder)) { } return results; } + + @Override + public List searchRecipeListByName(String recipeName, User user, String folder) { + final Map users = loadUsersFromFile(); + final User userFromFile = users.get(user.getUsername()); + final List results = new ArrayList<>(); + + List recipeList = new ArrayList<>(); + if ("bookmarks".equals(folder)) { + recipeList.addAll(userFromFile.getBookmarks()); + } + else if ("recentlyViewed".equals(folder)) { + recipeList.addAll(userFromFile.getRecentlyViewed()); + } + else { + recipeList.addAll(userFromFile.getFolder(folder)); + } + + for (Recipe recipe : recipeList) { + final String[] words = recipe.getName().split(" "); + for (String word : words) { + if (word.equalsIgnoreCase(recipeName)) { + results.add(recipe); + } + } + } + return results; + } } diff --git a/src/main/java/interface_adapter/RecipeListController.java b/src/main/java/interface_adapter/RecipeListController.java index 5e2ca3be7..8426bf001 100644 --- a/src/main/java/interface_adapter/RecipeListController.java +++ b/src/main/java/interface_adapter/RecipeListController.java @@ -2,7 +2,7 @@ import entity.Recipe; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -import use_case.SearchRecipeListByNameUseCase; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; import java.util.List; diff --git a/src/main/java/interface_adapter/bookmark/BookmarkController.java b/src/main/java/interface_adapter/bookmark/BookmarkController.java index 7833f6f38..fabb96e58 100644 --- a/src/main/java/interface_adapter/bookmark/BookmarkController.java +++ b/src/main/java/interface_adapter/bookmark/BookmarkController.java @@ -2,7 +2,7 @@ // //import interface_adapter.RecipeListController; //import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -//import use_case.SearchRecipeListByNameUseCase; +//import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; // //public class BookmarkController extends RecipeListController { // private final BookmarkInputBoundary bookmarkUseCaseInteractor; diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameDataAccessInterface.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameDataAccessInterface.java new file mode 100644 index 000000000..8608d3f16 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameDataAccessInterface.java @@ -0,0 +1,23 @@ +package use_case.search_recipe_list_by_name; + +import entity.Recipe; +import entity.User; + +import java.util.List; + +/** + * The interface of the DAO for the Search Recipe List By Name Use Case. + */ +public interface SearchRecipeListByNameDataAccessInterface { + + /** + * Searches recipes by name. + * @param recipeName the recipe name entered. + * @param user the user. + * @param folder the folder to search in. + * @return search results. + */ + List searchRecipeListByName(String recipeName, User user, String folder); + + boolean addUser(User user); +} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputBoundary.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputBoundary.java new file mode 100644 index 000000000..89ac8e5d0 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputBoundary.java @@ -0,0 +1,14 @@ +package use_case.search_recipe_list_by_name; + +/** + * The Search Recipe List By Name Use Case. + */ +public interface SearchRecipeListByNameInputBoundary { + + /** + * Execute the Search Recipe List By Name Use Case. + * @param searchRecipeListByNameInputData the input data for this use case + */ + void execute(SearchRecipeListByNameInputData searchRecipeListByNameInputData); + +} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputData.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputData.java new file mode 100644 index 000000000..170f4a23d --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInputData.java @@ -0,0 +1,31 @@ +package use_case.search_recipe_list_by_name; + +import entity.User; + +/** + * The input data for the Search Recipe List By Name Use Case. + */ +public class SearchRecipeListByNameInputData { + + private final String recipeName; + private final User user; + private final String folder; + + public SearchRecipeListByNameInputData(String recipeName, User user, String folder) { + this.recipeName = recipeName; + this.user = user; + this.folder = folder; + } + + public String getRecipeName() { + return recipeName; + } + + public User getUser() { + return user; + } + + public String getFolder() { + return folder; + } +} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java new file mode 100644 index 000000000..d98610be4 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java @@ -0,0 +1,35 @@ +package use_case.search_recipe_list_by_name; + +import entity.Recipe; +import entity.User; + +import java.util.List; + +public class SearchRecipeListByNameInteractor implements SearchRecipeListByNameInputBoundary { + + /** + * The Search Recipe List By Name Interactor. + */ + private final SearchRecipeListByNameDataAccessInterface userDataAccessObject; + private final SearchRecipeListByNameOutputBoundary userPresenter; + + public SearchRecipeListByNameInteractor( + SearchRecipeListByNameDataAccessInterface searchRecipeListByNameDataAccessInterface, + SearchRecipeListByNameOutputBoundary searchRecipeListByNameOutputBoundary) { + this.userDataAccessObject = searchRecipeListByNameDataAccessInterface; + this.userPresenter = searchRecipeListByNameOutputBoundary; + } + + @Override + public void execute(SearchRecipeListByNameInputData searchRecipeListByNameInputData) { + String recipeName = searchRecipeListByNameInputData.getRecipeName(); + final User user = searchRecipeListByNameInputData.getUser(); + final String folder = searchRecipeListByNameInputData.getFolder(); + final List recipes = userDataAccessObject.searchRecipeListByName(recipeName, user, folder); + + final SearchRecipeListByNameOutputData searchRecipeListByNameOutputData = + new SearchRecipeListByNameOutputData(recipes, + false); + userPresenter.prepareSuccessView(searchRecipeListByNameOutputData); + } +} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputBoundary.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputBoundary.java new file mode 100644 index 000000000..f448ad38d --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.search_recipe_list_by_name; + +/** + * The output boundary for the Search Recipe List by Name Use Case. + */ +public interface SearchRecipeListByNameOutputBoundary { + /** + * Prepares the success view for the Search Recipe List by Name Use Case. + * @param outputData the output data + */ + void prepareSuccessView(SearchRecipeListByNameOutputData outputData); + + /** + * Prepares the failure view for the Search Recipe List by Name Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputData.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputData.java new file mode 100644 index 000000000..1b8593247 --- /dev/null +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameOutputData.java @@ -0,0 +1,28 @@ +package use_case.search_recipe_list_by_name; + +import entity.Recipe; + +import java.util.List; + +/** + * Output Data for the Search Recipe List By Name Use Case. + */ +public class SearchRecipeListByNameOutputData { + + private final List recipes; + + private final boolean useCaseFailed; + + public SearchRecipeListByNameOutputData(List recipes, boolean useCaseFailed) { + this.recipes = recipes; + this.useCaseFailed = useCaseFailed; + } + + public List getRecipes() { + return recipes; + } + + public boolean isUseCaseFailed() { + return useCaseFailed; + } +} diff --git a/src/main/java/use_case/SearchRecipeListByNameUseCase.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java similarity index 91% rename from src/main/java/use_case/SearchRecipeListByNameUseCase.java rename to src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java index d07a0bd10..aca808a2e 100644 --- a/src/main/java/use_case/SearchRecipeListByNameUseCase.java +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java @@ -1,9 +1,7 @@ -package use_case; +package use_case.search_recipe_list_by_name; -import entity.Ingredient; import entity.Recipe; -import javax.swing.*; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index d5ce86adf..92dfa057c 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -22,7 +22,7 @@ import interface_adapter.RecipeListViewModel; import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -import use_case.SearchRecipeListByNameUseCase; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; public abstract class RecipeListView extends JFrame implements ActionListener, PropertyChangeListener { private static User user; diff --git a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java index 678d5a427..dc4c84fb1 100644 --- a/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java +++ b/src/test/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractorTest.java @@ -38,7 +38,7 @@ class SearchRecipeListByIngredientInteractorTest { private static final List recentlyViewed = List.of(recipe1, recipe2, recipe3); @Test - void successTest() { + void successTestForBookmarks() { final Map> folders = new HashMap<>(); final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); final List ingredients = List.of("chicken", "mushroom", "tomato"); @@ -66,6 +66,35 @@ public void prepareFailView(String error) { interactor.execute(inputData); } + @Test + void successTestForRecentlyViewed() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final List ingredients = List.of("chicken", "mushroom", "tomato"); + + SearchRecipeListByIngredientInputData inputData = new SearchRecipeListByIngredientInputData( + ingredients, user, "recentlyViewed"); + SearchRecipeListByIngredientDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByIngredientOutputBoundary successPresenter = + new SearchRecipeListByIngredientOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByIngredientOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByIngredientInputBoundary interactor = + new SearchRecipeListByIngredientInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + @Test void successTestForFolder() { final Map> folders = new HashMap<>(); diff --git a/src/test/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractorTest.java b/src/test/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractorTest.java new file mode 100644 index 000000000..96e00eb50 --- /dev/null +++ b/src/test/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractorTest.java @@ -0,0 +1,187 @@ +package use_case.search_recipe_list_by_name; + +import data_access.InMemorySearchRecipeListDataAccessObject; +import entity.Ingredient; +import entity.Recipe; +import entity.User; +import org.junit.jupiter.api.Test; +import use_case.search_recipe_list_by_ingredient.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class SearchRecipeListByNameInteractorTest { + private static final Recipe recipe1 = new Recipe( + "recipe1 roasted in oven", + "url", + List.of( + new Ingredient("chicken", 1.0, "unit"), + new Ingredient("mushroom", 1.0, "unit")), + "image"); + private static final Recipe recipe2 = new Recipe( + "recipe2 roasted on grill", + "url", + List.of( + new Ingredient("tomato", 1.0, "unit"), + new Ingredient("onion", 1.0, "unit")), + "image"); + private static final Recipe recipe3 = new Recipe( + "recipe3 microwaved", + "url", + List.of( + new Ingredient("lettuce", 1.0, "unit"), + new Ingredient("pork", 1.0, "unit")), + "image"); + private static final List bookmarks = List.of(recipe1, recipe2, recipe3); + private static final List recentlyViewed = List.of(recipe1, recipe2, recipe3); + + @Test + void successTestForBookmarks() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final String recipeName = "roasted"; + + SearchRecipeListByNameInputData inputData = new SearchRecipeListByNameInputData( + recipeName, user, "bookmarks"); + SearchRecipeListByNameDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByNameOutputBoundary successPresenter = + new SearchRecipeListByNameOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByNameInputBoundary interactor = + new SearchRecipeListByNameInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void successTestForRecentlyViewed() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final String recipeName = "roasted"; + + SearchRecipeListByNameInputData inputData = new SearchRecipeListByNameInputData( + recipeName, user, "recentlyViewed"); + SearchRecipeListByNameDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByNameOutputBoundary successPresenter = + new SearchRecipeListByNameOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByNameInputBoundary interactor = + new SearchRecipeListByNameInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void successTestForFolder() { + final Map> folders = new HashMap<>(); + folders.put("dinner", bookmarks); + folders.put("lunch", List.of()); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final String recipeName = "roasted"; + + SearchRecipeListByNameInputData inputData = new SearchRecipeListByNameInputData( + recipeName, user, "dinner"); + SearchRecipeListByNameDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByNameOutputBoundary successPresenter = + new SearchRecipeListByNameOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData searchResults) { + assertEquals(List.of(recipe1, recipe2), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByNameInputBoundary interactor = + new SearchRecipeListByNameInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void emptyQueryTest() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + + SearchRecipeListByNameInputData inputData = new SearchRecipeListByNameInputData( + "", user, "bookmarks"); + SearchRecipeListByNameDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByNameOutputBoundary successPresenter = + new SearchRecipeListByNameOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData searchResults) { + assertEquals(List.of(), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByNameInputBoundary interactor = + new SearchRecipeListByNameInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } + + @Test + void noMatchingNameTest() { + final Map> folders = new HashMap<>(); + final User user = new User("cindy", "123", bookmarks, recentlyViewed, folders); + final String recipeName = "steamed"; + + SearchRecipeListByNameInputData inputData = new SearchRecipeListByNameInputData( + recipeName, user, "bookmarks"); + SearchRecipeListByNameDataAccessInterface userRepository = new InMemorySearchRecipeListDataAccessObject(); + userRepository.addUser(user); + + SearchRecipeListByNameOutputBoundary successPresenter = + new SearchRecipeListByNameOutputBoundary() { + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData searchResults) { + assertEquals(List.of(), searchResults.getRecipes()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchRecipeListByNameInputBoundary interactor = + new SearchRecipeListByNameInteractor(userRepository, successPresenter); + interactor.execute(inputData); + } +} \ No newline at end of file From 1e7907e94b7e08c304b20e5cdaf5f5cbc14afd88 Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Mon, 2 Dec 2024 19:22:25 -0500 Subject: [PATCH 143/148] added search recipe by name controller to recipe list view; still need to figure out fire property changed, recipe list state and recipe list view model. Might add appbuilder class --- .../interface_adapter/RecipeListState.java | 34 +++++------- .../SearchRecipeListByNameController.java | 32 +++++++++++ .../SearchRecipeListByNamePresenter.java | 28 ++++++++++ .../java/use_case/SearchBookmarkUseCase.java | 22 -------- ...peListByIngredientDataAccessInterface.java | 2 +- ...SearchRecipeListByIngredientInputData.java | 2 +- ...earchRecipeListByIngredientInteractor.java | 2 +- src/main/java/view/RecipeListView.java | 55 ++++++++++--------- 8 files changed, 107 insertions(+), 70 deletions(-) create mode 100644 src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNameController.java create mode 100644 src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java delete mode 100644 src/main/java/use_case/SearchBookmarkUseCase.java diff --git a/src/main/java/interface_adapter/RecipeListState.java b/src/main/java/interface_adapter/RecipeListState.java index fbf9a36b5..65cd126c3 100644 --- a/src/main/java/interface_adapter/RecipeListState.java +++ b/src/main/java/interface_adapter/RecipeListState.java @@ -1,37 +1,33 @@ package interface_adapter; +import entity.User; + /** * The State information representing the recipe list. */ public class RecipeListState { - private String username = ""; - - private String password = ""; - private String passwordError; + private User user; + private String folder; public RecipeListState() { -// username = copy.username; -// password = copy.password; -// passwordError = copy.passwordError; - } - - public String getUsername() { - return username; + // TODO change this + this.user = null; + this.folder = null; } - public void setUsername(String username) { - this.username = username; + public User getUser() { + return this.user; } - public void setPassword(String password) { - this.password = password; + public String getFolder() { + return this.folder; } - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; + public void setUser(User user) { + this.user = user; } - public String getPassword() { - return password; + public void setFolder(String folder) { + this.folder = folder; } } diff --git a/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNameController.java b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNameController.java new file mode 100644 index 000000000..44502a065 --- /dev/null +++ b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNameController.java @@ -0,0 +1,32 @@ +package interface_adapter.search_recipe_list_by_name; + +import entity.User; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameInputBoundary; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameInputData; + +import java.util.List; + +/** + * Controller for the Search Recipe List By Name Use Case. + */ +public class SearchRecipeListByNameController { + private final SearchRecipeListByNameInputBoundary searchRecipeListByNameUseCaseInteractor; + + public SearchRecipeListByNameController( + SearchRecipeListByNameInputBoundary searchRecipeListByNameUseCaseInteractor) { + this.searchRecipeListByNameUseCaseInteractor = searchRecipeListByNameUseCaseInteractor; + } + + /** + * Executes the Search Recipe List By Name Use Case. + * @param recipeName the recipe name entered by the user. + * @param user the user. + * @param folder the folder to search in. + */ + public void execute(String recipeName, User user, String folder) { + final SearchRecipeListByNameInputData searchRecipeListByNameInputData = + new SearchRecipeListByNameInputData(recipeName, user, folder); + + searchRecipeListByNameUseCaseInteractor.execute(searchRecipeListByNameInputData); + } +} diff --git a/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java new file mode 100644 index 000000000..658a467f1 --- /dev/null +++ b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java @@ -0,0 +1,28 @@ +package interface_adapter.search_recipe_list_by_name; + +import interface_adapter.RecipeListViewModel; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputBoundary; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputData; + +/** + * The Presenter for the Search Recipe List By Name Use Case. + */ +public class SearchRecipeListByNamePresenter implements SearchRecipeListByNameOutputBoundary { + + private final RecipeListViewModel recipeListViewModel; + + public SearchRecipeListByNamePresenter(RecipeListViewModel recipeListViewModel) { + this.recipeListViewModel = recipeListViewModel; + } + + @Override + public void prepareSuccessView(SearchRecipeListByNameOutputData outputData) { + // TODO + recipeListViewModel.firePropertyChanged("???"); + } + + @Override + public void prepareFailView(String error) { + // note: this use case currently can't fail + } +} diff --git a/src/main/java/use_case/SearchBookmarkUseCase.java b/src/main/java/use_case/SearchBookmarkUseCase.java deleted file mode 100644 index 91f94d2bf..000000000 --- a/src/main/java/use_case/SearchBookmarkUseCase.java +++ /dev/null @@ -1,22 +0,0 @@ -/* -In progress; commented out everything for now so it doesn't crash the program. - */ - -//package use_case; -// -//import data_access.RecipeDAO; -//import entity.Recipe; -// -//import java.util.List; -// -//public class SearchBookmarkUseCase { -// private final RecipeDAO recipeDAO; -// -// public SearchRecipeUseCase(RecipeDAO recipeDAO) { -// this.recipeDAO = recipeDAO; -// } -// -// public List searchRecipes(List ingredients) { -// return recipeDAO.getRecipesByIngredients(ingredients); -// } -//} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java index c7fd09faa..edd14b326 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientDataAccessInterface.java @@ -6,7 +6,7 @@ import java.util.List; /** - * The interface of the DAO for the Change Password Use Case. + * The interface of the DAO for the Search Recipe List By Ingredient Use Case. */ public interface SearchRecipeListByIngredientDataAccessInterface { diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java index b58fb42ac..35d1a0abe 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInputData.java @@ -5,7 +5,7 @@ import java.util.List; /** - * The input data for the Change Password Use Case. + * The input data for the Search Recipe List By Ingredient Use Case. */ public class SearchRecipeListByIngredientInputData { diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java index 3ce283038..62a90adbd 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java @@ -8,7 +8,7 @@ public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { /** - * The Change Password Interactor. + * The Search Recipe List By Ingredient Interactor. */ private final SearchRecipeListByIngredientDataAccessInterface userDataAccessObject; private final SearchRecipeListByIngredientOutputBoundary userPresenter; diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index 92dfa057c..18d5e2317 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -21,6 +21,7 @@ import interface_adapter.RecipeListState; import interface_adapter.RecipeListViewModel; import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; +import interface_adapter.search_recipe_list_by_name.SearchRecipeListByNameController; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; @@ -45,6 +46,7 @@ public abstract class RecipeListView extends JFrame implements ActionListener, P private final RecipeListViewModel recipeListViewModel; private SearchRecipeListByIngredientController searchRecipeListByIngredientController; + private SearchRecipeListByNameController searchRecipeListByNameController; /* Generates the default view of a list of recipes associated with a User. @@ -81,15 +83,6 @@ public RecipeListView(User user, String folderName, RecipeListViewModel recipeLi scrollPane.setPreferredSize(recipeList.getPreferredScrollableViewportSize()); add(scrollPane, BorderLayout.SOUTH); - // TODO fix the layout -// // Main panel with vertical BoxLayout -// final JPanel mainPanel = new JPanel(); -// mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); -// setContentPane(mainPanel); -// -// // Add components to the main panel -// mainPanel.add(scrollPane); - // Make the recipe list clickable recipeList.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent event) { @@ -175,28 +168,31 @@ public void mouseClicked(MouseEvent event) { public void actionPerformed(ActionEvent event) { if (event.getSource() == ingredientSearchButton) { final String userInput = ingredientSearchField.getText(); - List ingredients = Arrays.asList(userInput.split(",")); - List recipes = controller.getRecipesByIngredients(ingredients); - final DefaultListModel ingredientSearchListModel = new DefaultListModel<>(); - for (Recipe recipe : recipes) { - ingredientSearchListModel.addElement(recipe); - } - recipeList.setModel(ingredientSearchListModel); - - // additions... + final List ingredients = Arrays.asList(userInput.split(",")); + // TODO double check then delete +// List recipes = controller.getRecipesByIngredients(ingredients); +// final DefaultListModel ingredientSearchListModel = new DefaultListModel<>(); +// for (Recipe recipe : recipes) { +// ingredientSearchListModel.addElement(recipe); +// } +// recipeList.setModel(ingredientSearchListModel); final RecipeListState currentState = recipeListViewModel.getState(); - - this.searchRecipeListByIngredientController.execute(ingredients, user, folderName); + this.searchRecipeListByIngredientController.execute( + ingredients, currentState.getUser(), currentState.getFolder()); } if (event.getSource() == recipeSearchButton) { final String userInput = recipeSearchField.getText(); - List recipes = controller.getRecipesByName(userInput); - final DefaultListModel recipeSearchListModel = new DefaultListModel<>(); - for (Recipe recipe : recipes) { - recipeSearchListModel.addElement(recipe); - } - recipeList.setModel(recipeSearchListModel); + // TODO double check then delete +// final List recipes = controller.getRecipesByName(userInput); +// final DefaultListModel recipeSearchListModel = new DefaultListModel<>(); +// for (Recipe recipe : recipes) { +// recipeSearchListModel.addElement(recipe); +// } +// recipeList.setModel(recipeSearchListModel); + final RecipeListState currentState = recipeListViewModel.getState(); + this.searchRecipeListByNameController.execute( + userInput, currentState.getUser(), currentState.getFolder()); } if (event.getSource() == clearSearchButton) { recipeList.setModel(listModel); @@ -251,10 +247,17 @@ public void propertyChange(PropertyChangeEvent event) { final RecipeListState state = (RecipeListState) event.getNewValue(); // TODO recipeList.setModel(ingredientSearchListModel); } + // TODO add other cases } + // TODO only need these if there's an appbuilder public void setSearchRecipeListByIngredientController( SearchRecipeListByIngredientController SearchRecipeListByIngredientController) { this.searchRecipeListByIngredientController = SearchRecipeListByIngredientController; } + + public void setSearchRecipeListByNameController( + SearchRecipeListByNameController SearchRecipeListByNameController) { + this.searchRecipeListByNameController = SearchRecipeListByNameController; + } } From d787da4bc26f49e526a533e5747a4901ed7a5b71 Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Mon, 2 Dec 2024 22:43:03 -0500 Subject: [PATCH 144/148] got the search recipe list functions working using controllers, presenters, etc. Commented out AppBuilder for now and instantiated the controllers in the view instead. --- src/main/java/app/AppBuilder.java | 170 ++++++++++++++++++ src/main/java/app/Main.java | 2 +- .../interface_adapter/RecipeListState.java | 13 ++ .../RecipeListViewModel.java | 2 +- ...SearchRecipeListByIngredientPresenter.java | 5 +- .../SearchRecipeListByNamePresenter.java | 5 +- src/main/java/view/BookmarkView.java | 4 +- src/main/java/view/HomePage.java | 4 +- src/main/java/view/RecentlyViewedView.java | 2 +- src/main/java/view/RecipeListView.java | 54 +++++- 10 files changed, 246 insertions(+), 15 deletions(-) create mode 100644 src/main/java/app/AppBuilder.java diff --git a/src/main/java/app/AppBuilder.java b/src/main/java/app/AppBuilder.java new file mode 100644 index 000000000..7c54509ce --- /dev/null +++ b/src/main/java/app/AppBuilder.java @@ -0,0 +1,170 @@ +//package app; +// +//import data_access.UserDAOImpl; +//import interface_adapter.RecipeListViewModel; +//import interface_adapter.ViewManagerModel; +//import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; +//import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientPresenter; +//import interface_adapter.search_recipe_list_by_name.SearchRecipeListByNameController; +//import interface_adapter.search_recipe_list_by_name.SearchRecipeListByNamePresenter; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInteractor; +//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; +//import use_case.search_recipe_list_by_name.SearchRecipeListByNameInputBoundary; +//import use_case.search_recipe_list_by_name.SearchRecipeListByNameInteractor; +//import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputBoundary; +//import view.*; +// +//import java.awt.CardLayout; +// +//import javax.swing.JFrame; +//import javax.swing.JPanel; +//import javax.swing.WindowConstants; +// +///** +// * The AppBuilder class is responsible for putting together the pieces of +// * our CA architecture; piece by piece. +// *

+// * This is done by adding each View and then adding related Use Cases. +// */ +//public class AppBuilder { +// private final JPanel cardPanel = new JPanel(); +// private final CardLayout cardLayout = new CardLayout(); +// private final ViewManagerModel viewManagerModel = new ViewManagerModel(); +// private final ViewManager viewManager = new ViewManager(cardPanel, cardLayout, viewManagerModel); +// +// private final UserDAOImpl userDataAccessObject = new UserDAOImpl(); +// +// private BookmarkView bookmarkView; +// private FolderView folderView; +// private HomePage homepage; +// private IndividualRecipeView individualRecipeView; +// private LoginSignupPage loginSignupPage; +// private NutritionInformationView nutritionInformationView; +// private RecentlyViewedView recentlyViewedView; +// private RecipeListView recipeListView; +// private RecipeView recipeView; +// private ShoppingListGUI shoppingListGUI; +// private RecipeListViewModel recipeListViewModel; +// +// public AppBuilder() { +// cardPanel.setLayout(cardLayout); +// } +// +// /** +// * Adds the Bookmark View to the application. +// * @return this builder +// */ +// public AppBuilder addBookmarkView() { +// recipeListViewModel = new RecipeListViewModel(); +// // TODO figure out how to remove user +// bookmarkView = new BookmarkView(user, "bookmarks", recipeListViewModel); +// cardPanel.add(bookmarkView); +// return this; +// } +// +//// /** +//// * Adds the Login View to the application. +//// * @return this builder +//// */ +//// public AppBuilder addLoginView() { +//// loginViewModel = new LoginViewModel(); +//// loginView = new LoginView(loginViewModel); +//// cardPanel.add(loginView, loginView.getViewName()); +//// return this; +//// } +//// +//// /** +//// * Adds the LoggedIn View to the application. +//// * @return this builder +//// */ +//// public AppBuilder addLoggedInView() { +//// loggedInViewModel = new LoggedInViewModel(); +//// loggedInView = new LoggedInView(loggedInViewModel); +//// cardPanel.add(loggedInView, loggedInView.getViewName()); +//// return this; +//// } +// +// /** +// * Adds the Signup Use Case to the application. +// * @return this builder +// */ +// public AppBuilder addSearchRecipeListByIngredientUseCase() { +// final SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary = +// new SearchRecipeListByIngredientPresenter(recipeListViewModel); +// final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientInteractor = +// new SearchRecipeListByIngredientInteractor( +// userDataAccessObject, searchRecipeListByIngredientOutputBoundary); +// +// final SearchRecipeListByIngredientController controller = +// new SearchRecipeListByIngredientController(searchRecipeListByIngredientInteractor); +// recipeListView.setSearchRecipeListByIngredientController(controller); +// return this; +// } +// +// /** +// * Adds the Login Use Case to the application. +// * @return this builder +// */ +// public AppBuilder addSearchRecipeListByNameUseCase() { +// final SearchRecipeListByNameOutputBoundary searchRecipeListByNameOutputBoundary = +// new SearchRecipeListByNamePresenter(recipeListViewModel); +// final SearchRecipeListByNameInputBoundary searchRecipeListByNameInteractor = +// new SearchRecipeListByNameInteractor( +// userDataAccessObject, searchRecipeListByNameOutputBoundary); +// +// final SearchRecipeListByNameController controller = +// new SearchRecipeListByNameController(searchRecipeListByNameInteractor); +// recipeListView.setSearchRecipeListByNameController(controller); +// return this; +// } +// +//// /** +//// * Adds the Change Password Use Case to the application. +//// * @return this builder +//// */ +//// public AppBuilder addChangePasswordUseCase() { +//// final ChangePasswordOutputBoundary changePasswordOutputBoundary = +//// new ChangePasswordPresenter(loggedInViewModel); +//// +//// final ChangePasswordInputBoundary changePasswordInteractor = +//// new ChangePasswordInteractor(userDataAccessObject, changePasswordOutputBoundary, userFactory); +//// +//// final ChangePasswordController changePasswordController = +//// new ChangePasswordController(changePasswordInteractor); +//// loggedInView.setChangePasswordController(changePasswordController); +//// return this; +//// } +//// +//// /** +//// * Adds the Logout Use Case to the application. +//// * @return this builder +//// */ +//// public AppBuilder addLogoutUseCase() { +//// final LogoutOutputBoundary logoutOutputBoundary = new LogoutPresenter(viewManagerModel, +//// loggedInViewModel, loginViewModel); +//// +//// final LogoutInputBoundary logoutInteractor = +//// new LogoutInteractor(userDataAccessObject, logoutOutputBoundary); +//// +//// final LogoutController logoutController = new LogoutController(logoutInteractor); +//// loggedInView.setLogoutController(logoutController); +//// return this; +//// } +// +// /** +// * Creates the JFrame for the application and initially sets the SignupView to be displayed. +// * @return the application +// */ +// public JFrame build() { +// final JFrame application = new JFrame("Recipe Generator"); +// application.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); +// +// application.add(cardPanel); +// +// viewManagerModel.setState(homepage.getName()); +// viewManagerModel.firePropertyChanged(); +// +// return application; +// } +//} diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index 4664cbf16..9e09ffc77 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -4,7 +4,7 @@ import view.LoginSignupPage; /** - * This is where the application starts. .. + * This is where the application starts. */ public class Main { diff --git a/src/main/java/interface_adapter/RecipeListState.java b/src/main/java/interface_adapter/RecipeListState.java index 65cd126c3..df3cbbf00 100644 --- a/src/main/java/interface_adapter/RecipeListState.java +++ b/src/main/java/interface_adapter/RecipeListState.java @@ -1,18 +1,23 @@ package interface_adapter; +import entity.Recipe; import entity.User; +import java.util.List; + /** * The State information representing the recipe list. */ public class RecipeListState { private User user; private String folder; + private List recipeList; public RecipeListState() { // TODO change this this.user = null; this.folder = null; + this.recipeList = null; } public User getUser() { @@ -23,6 +28,10 @@ public String getFolder() { return this.folder; } + public List getRecipeList() { + return this.recipeList; + } + public void setUser(User user) { this.user = user; } @@ -30,4 +39,8 @@ public void setUser(User user) { public void setFolder(String folder) { this.folder = folder; } + + public void setRecipeList(List recipeList) { + this.recipeList = recipeList; + } } diff --git a/src/main/java/interface_adapter/RecipeListViewModel.java b/src/main/java/interface_adapter/RecipeListViewModel.java index 54a1da32e..6e44df30f 100644 --- a/src/main/java/interface_adapter/RecipeListViewModel.java +++ b/src/main/java/interface_adapter/RecipeListViewModel.java @@ -7,7 +7,7 @@ public class RecipeListViewModel extends ViewModel { public RecipeListViewModel() { // TODO - super("???"); + super("bookmarks"); setState(new RecipeListState()); } diff --git a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java index 079d67497..d01709cef 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_ingredient/SearchRecipeListByIngredientPresenter.java @@ -1,5 +1,6 @@ package interface_adapter.search_recipe_list_by_ingredient; +import interface_adapter.RecipeListState; import interface_adapter.RecipeListViewModel; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputData; @@ -18,7 +19,9 @@ public SearchRecipeListByIngredientPresenter(RecipeListViewModel recipeListViewM @Override public void prepareSuccessView(SearchRecipeListByIngredientOutputData outputData) { // TODO - recipeListViewModel.firePropertyChanged("???"); + final RecipeListState recipeListState = recipeListViewModel.getState(); + recipeListState.setRecipeList(outputData.getRecipes()); + recipeListViewModel.firePropertyChanged("recipes"); } @Override diff --git a/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java index 658a467f1..b252735e4 100644 --- a/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java +++ b/src/main/java/interface_adapter/search_recipe_list_by_name/SearchRecipeListByNamePresenter.java @@ -1,5 +1,6 @@ package interface_adapter.search_recipe_list_by_name; +import interface_adapter.RecipeListState; import interface_adapter.RecipeListViewModel; import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputBoundary; import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputData; @@ -18,7 +19,9 @@ public SearchRecipeListByNamePresenter(RecipeListViewModel recipeListViewModel) @Override public void prepareSuccessView(SearchRecipeListByNameOutputData outputData) { // TODO - recipeListViewModel.firePropertyChanged("???"); + final RecipeListState recipeListState = recipeListViewModel.getState(); + recipeListState.setRecipeList(outputData.getRecipes()); + recipeListViewModel.firePropertyChanged("recipes"); } @Override diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index 9878c36c3..23e288ce5 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -27,6 +27,7 @@ public BookmarkView(User user, String folderName, RecipeListViewModel recipeList folderPanel.add(createFolderPanel); add(folderPanel); + pack(); } @NotNull @@ -53,6 +54,7 @@ private JPanel getCreateFolderPanel(User user) { addFolderButton.addActionListener(event -> { final String folderName = textField.getText(); userDAO.addFolderToFile(user.getUsername(), folderName); + JOptionPane.showMessageDialog(null, "Folder created successfully"); }); createFolderPanel.add(textField); createFolderPanel.add(addFolderButton); @@ -66,6 +68,6 @@ protected List getRecipeList(User user1) { @Override protected List getRecipeList(User user1, String folderName) { - return List.of(); + return userDAO.getRecentlyViewedFromFile(user1.getUsername()); } } diff --git a/src/main/java/view/HomePage.java b/src/main/java/view/HomePage.java index 21d4c9298..e81b728f3 100644 --- a/src/main/java/view/HomePage.java +++ b/src/main/java/view/HomePage.java @@ -64,14 +64,14 @@ public void actionPerformed(ActionEvent e) { final JButton bookmarksButton = new JButton("Bookmarks"); bookmarksButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); bookmarksButton.addActionListener(event -> { - new BookmarkView(this.user, null, new RecipeListViewModel()); + new BookmarkView(this.user, "bookmarks", new RecipeListViewModel()); }); // Recently Viewed Button final JButton recentlyViewedButton = new JButton("Recently Viewed"); recentlyViewedButton.setFont(new Font(AppConstants.FONT, Font.PLAIN, AppConstants.BUTTON_FONT_SIZE)); recentlyViewedButton.addActionListener(event -> { - new RecentlyViewedView(this.user, null, new RecipeListViewModel()); + new RecentlyViewedView(this.user, "recentlyViewed", new RecipeListViewModel()); }); // Putting the buttons together diff --git a/src/main/java/view/RecentlyViewedView.java b/src/main/java/view/RecentlyViewedView.java index 8c2681dd4..7a03516a2 100644 --- a/src/main/java/view/RecentlyViewedView.java +++ b/src/main/java/view/RecentlyViewedView.java @@ -28,6 +28,6 @@ protected List getRecipeList(User user1) { @Override protected List getRecipeList(User user1, String folderName) { - return List.of(); + return userDAO.getRecentlyViewedFromFile(user1.getUsername()); } } diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index 18d5e2317..a67710d5f 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -21,16 +21,24 @@ import interface_adapter.RecipeListState; import interface_adapter.RecipeListViewModel; import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; +import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientPresenter; import interface_adapter.search_recipe_list_by_name.SearchRecipeListByNameController; +import interface_adapter.search_recipe_list_by_name.SearchRecipeListByNamePresenter; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInteractor; +import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameInputBoundary; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameInteractor; +import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputBoundary; import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; public abstract class RecipeListView extends JFrame implements ActionListener, PropertyChangeListener { - private static User user; protected static UserDAOImpl userDAO; protected final JList recipeList; protected final DefaultListModel listModel; private final RecipeListController controller; + private final User user; private JTextField ingredientSearchField; private JButton ingredientSearchButton; @@ -65,10 +73,26 @@ public RecipeListView(User user, String folderName, RecipeListViewModel recipeLi this.recipeListViewModel = recipeListViewModel; this.recipeListViewModel.addPropertyChangeListener(this); + final SearchRecipeListByIngredientOutputBoundary searchRecipeListByIngredientOutputBoundary = + new SearchRecipeListByIngredientPresenter(recipeListViewModel); + final SearchRecipeListByIngredientInputBoundary searchRecipeListByIngredientInteractor = + new SearchRecipeListByIngredientInteractor( + userDAO, searchRecipeListByIngredientOutputBoundary); + this.searchRecipeListByIngredientController = + new SearchRecipeListByIngredientController(searchRecipeListByIngredientInteractor); + + final SearchRecipeListByNameOutputBoundary searchRecipeListByNameOutputBoundary = + new SearchRecipeListByNamePresenter(recipeListViewModel); + final SearchRecipeListByNameInputBoundary searchRecipeListByNameInteractor = + new SearchRecipeListByNameInteractor( + userDAO, searchRecipeListByNameOutputBoundary); + this.searchRecipeListByNameController = + new SearchRecipeListByNameController(searchRecipeListByNameInteractor); + // Initialize recipe list to display List recipes = new ArrayList<>(); // Split into cases: BookmarkView/RecentlyViewedView and FolderView - if (folderName == null) { + if (folderName == "bookmarks" || folderName == "recentlyViewed") { recipes = getRecipeList(userDAO.findUserByUsername(user.getUsername())); } else { @@ -178,21 +202,37 @@ public void actionPerformed(ActionEvent event) { // recipeList.setModel(ingredientSearchListModel); final RecipeListState currentState = recipeListViewModel.getState(); + currentState.setFolder(folderName); + currentState.setUser(user); this.searchRecipeListByIngredientController.execute( ingredients, currentState.getUser(), currentState.getFolder()); + recipeListViewModel.setState(currentState); + final DefaultListModel recipeSearchListModel = new DefaultListModel<>(); + for (Recipe recipe : currentState.getRecipeList()) { + recipeSearchListModel.addElement(recipe); + } + recipeList.setModel(recipeSearchListModel); } if (event.getSource() == recipeSearchButton) { final String userInput = recipeSearchField.getText(); // TODO double check then delete // final List recipes = controller.getRecipesByName(userInput); -// final DefaultListModel recipeSearchListModel = new DefaultListModel<>(); -// for (Recipe recipe : recipes) { -// recipeSearchListModel.addElement(recipe); -// } -// recipeList.setModel(recipeSearchListModel); + final RecipeListState currentState = recipeListViewModel.getState(); + currentState.setFolder(folderName); + currentState.setUser(user); + System.out.println("recipe list view"); + System.out.println(folderName); + System.out.println(userInput); + System.out.println(user.getUsername()); this.searchRecipeListByNameController.execute( userInput, currentState.getUser(), currentState.getFolder()); + recipeListViewModel.setState(currentState); + final DefaultListModel recipeSearchListModel = new DefaultListModel<>(); + for (Recipe recipe : currentState.getRecipeList()) { + recipeSearchListModel.addElement(recipe); + } + recipeList.setModel(recipeSearchListModel); } if (event.getSource() == clearSearchButton) { recipeList.setModel(listModel); From 4c8a0181f7bf29fa715787651dc709f9ce6e1e5c Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Mon, 2 Dec 2024 23:00:05 -0500 Subject: [PATCH 145/148] updated readme to be mostly complete with an image --- README.md | 72 +++++++++++++++++++++++++++--------- images/Welcome_Homepage.png | Bin 0 -> 61998 bytes 2 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 images/Welcome_Homepage.png diff --git a/README.md b/README.md index 8975ef23f..907bd31ac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CSC207 Project - Recipe Generator -## Team Members +## Project Contributors ⁠Farshad Haddadi * GitHub username: farshad-haddadi @@ -22,6 +22,15 @@ Shuxin (Kate) Zhou * GitHub username: kayzoo8 * Email: katezhou2005@gmail.com +## Purpose +This project generates a list of recipes from user-inputted ingredients (up to 20 char long). Users can focus on +ingredients they have and generate recipes that meets their needs, rather than coming up with a specific dish +that might use those ingredients. If the user had specific dietary needs and a unique ingredient, it may be difficult +to know what to search for. Each recipe will have the option to display its full nutritional information for those +who have specific health requirements. This project allows users to bookmark their favourite recipes as well as +organize them into folders for easy access. Oftentimes, one may find a recipe but only be missing a couple of +ingredients, our program can generate a shopping list for the user available for export. (*double check) + ## Table of Contents 1) [Features](#features-of-the-project) 2) [Installation Instructions](#installation-instructions) @@ -31,42 +40,71 @@ Shuxin (Kate) Zhou ## Features of the Project -### User Stories -* Member 1: The core feature of the program allows users to input ingredients they have on hand and receive recipe suggestions. Users can enter ingredients separated by commas, and the system will generate relevant recipes. If some ingredients are missing, the system can suggest close alternatives or highlight what else is needed. This feature helps users efficiently use the ingredients they already own, potentially reducing food waste and unnecessary shopping trips. +### User Stories (that each contributor was mainly responsible for) +* **William**: The core feature of the program allows users to input ingredients they have on hand and receive +recipe suggestions. Users can enter ingredients separated by commas, and the system will generate relevant +recipes. If some ingredients are missing, the system can suggest close alternatives or highlight what else is +needed. This feature helps users efficiently use the ingredients they already own, potentially reducing food +waste and unnecessary shopping trips. + + +* **Kate**: A key component of the system is the ability for users to filter recipes by diet and cuisine. +This feature allows users to customize results based on dietary needs—such as vegan, gluten-free, or keto—or +by preferred cuisines like Italian or Mexican. Users can apply multiple filters simultaneously to find recipes +that meet both dietary restrictions and taste preferences. Managing these filters effectively ensures that the +results remain useful even when dietary and cultural preferences overlap. + + +* **Hongcheng**: Another feature provides detailed nutritional information for each recipe. Users can click a button +to access data such as calorie counts, macronutrients (carbs, proteins, fats), and other key nutrients. This +feature supports those who are tracking their nutrition or following specific dietary goals. It is important +for the system to present this information clearly, accounting for potential variations in portion sizes and +ingredient substitutions. + -* Member 2: A key component of the system is the ability for users to filter recipes by diet and cuisine. This feature allows users to customize results based on dietary needs—such as vegan, gluten-free, or keto—or by preferred cuisines like Italian or Mexican. Users can apply multiple filters simultaneously to find recipes that meet both dietary restrictions and taste preferences. Managing these filters effectively ensures that the results remain useful even when dietary and cultural preferences overlap. +* **Cindy**: The system will also allow users to bookmark recipes for future reference. This feature enables +users to save recipes they find interesting or useful and organize them into categories like "Dinner" or +"Favorites." Additionally, a "Recently Viewed" section can help users retrieve recipes they forgot to bookmark. -* Member 3: Another feature provides detailed nutritional information for each recipe. Users can click a button to access data such as calorie counts, macronutrients (carbs, proteins, fats), and other key nutrients. This feature supports those who are tracking their nutrition or following specific dietary goals. It is important for the system to present this information clearly, accounting for potential variations in portion sizes and ingredient substitutions. -* Member 4: The system will also allow users to bookmark recipes for future reference. This feature enables users to save recipes they find interesting or useful and organize them into categories like "Dinner" or "Favorites." Additionally, a "Recently Viewed" section can help users retrieve recipes they forgot to bookmark. +* **Farshad**: Finally, the program offers a shopping list feature for adjacent recipes. When users are missing +one or two ingredients for a recipe, the system will generate a shopping list with only the required items. This +function can help users decide what to buy if they want to try new meals without doing extensive shopping. +Export options, such as saving the list as a PDF or sharing it with other apps, enhance usability. The system +will also need to distinguish between essential ingredients and common pantry staples to keep the lists concise. -* Member 5: Finally, the program offers a shopping list feature for adjacent recipes. When users are missing one or two ingredients for a recipe, the system will generate a shopping list with only the required items. This function can help users decide what to buy if they want to try new meals without doing extensive shopping. Export options, such as saving the list as a PDF or sharing it with other apps, enhance usability. The system will also need to distinguish between essential ingredients and common pantry staples to keep the lists concise. +### Software +This project closely follows Clean Architecture, so the program is divided from high-level to low-level layers, +from entities to the database. This program makes use of the Spoonacular API to retrieve recipes and their +details, like ingredients, nutrition, diet, etc. The Recipe Generator is displayed using the Java Swing GUI in +a user-friendly manner with instructions at each step. + +![Screenshot of "Welcome" homepage for Recipe Generator](images/Welcome_Homepage.png) ## Installation Instructions Ensure that you have Java installed on your machine. This program was developed with the version Java 22, so ensure you are up-to-date. This program is guaranteed to run on macOS or Windows, -other OS or hardware has not been tested. Clone the Recipe Generator repository. -*Json and maven instructions? +other OS or hardware has not been tested. Fork the Recipe Generator repository by clicking the 'Fork' button +on GitHub and select 'copy the `main` branch only'. Then, create a local copy by coping the web URL, open your +preferred IDE, and create a new project from version control by pasting the link. -## Usage -- *gif of running Main once code finalized +The `pom.xml` file contains the project configuration. The IDE should have detected this file and run the +maven commands. If src/main/java isn't 'blue' (or marked as the Sources Root) and src/test/java isn't 'green', +you may need to right click `pom.xml` and select 'Maven -> Reload project'. +## Usage To run the application, go to the `src` folder, then open up `main`, click `java`, then `app`, and run `Main.java`. The app will automatically load, then follow the prompts. Closing the application will close the -entire program. (*unless we want to fix this) - -- *gif of us logging in, searching recipes, filtering, nutrition, bookmarks, shopping list (demo entire program here?) +entire program. ## License This project has the Creative Commons License. See [LICENSE](LICENSE) for more information. ## Feedback and Contributions -- ***should we allow contributions?*** - Contributions are welcome! If you find any issues or have suggestions for improvement, please open an issue. -If you wish to contribute, fork the repository by clicking the `<> Code` button on GitHub +If you wish to contribute, fork the repository by clicking the `Fork` button on GitHub and copy the `main` branch only. Then, create a local copy by coping the web URL, open your preferred IDE, and create a new project from version control by pasting the link. diff --git a/images/Welcome_Homepage.png b/images/Welcome_Homepage.png new file mode 100644 index 0000000000000000000000000000000000000000..66d56da1bfde1e18709251942290097e775cfc6e GIT binary patch literal 61998 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 Date: Mon, 2 Dec 2024 23:45:09 -0500 Subject: [PATCH 146/148] clean up: removed old commented out code --- .../data_access/SpoonacularRecipeDAO.java | 19 ++++++++----------- .../FilterRecipesController.java | 14 -------------- .../FilterRecipesDataAccessInterface.java | 14 -------------- .../FilterRecipesInputBoundary.java | 6 ------ 4 files changed, 8 insertions(+), 45 deletions(-) diff --git a/src/main/java/data_access/SpoonacularRecipeDAO.java b/src/main/java/data_access/SpoonacularRecipeDAO.java index 194df4fbd..d5afe0769 100644 --- a/src/main/java/data_access/SpoonacularRecipeDAO.java +++ b/src/main/java/data_access/SpoonacularRecipeDAO.java @@ -186,7 +186,7 @@ public List filterSearchRecipes(List ingredients, String diet, S urlBuilder.append("&cuisine=").append(cuisine); } - // Build the request **REPEAT OF ABOVE SHOULD REFACTOR + // Build the request final Request request = new Request.Builder() .url(String.valueOf(urlBuilder)) .build(); @@ -208,16 +208,11 @@ public List filterSearchRecipes(List ingredients, String diet, S final JSONObject completeRecipe = getCompleteRecipe(id); // for testing purposes - System.out.println(recipeJson.keySet()); - // System.out.println(completeRecipe.keySet()); - System.out.println(recipeJson.getInt("missedIngredientCount")); String title = recipeJson.getString("title"); - // String recipeUrl = BASE_URL + "/recipes/" + recipeJson.getInt("id") + "/information"; // URL to recipe details final String recipeUrl = completeRecipe.getString("spoonacularSourceUrl"); final JSONArray missedIngredientsJson = recipeJson.getJSONArray("missedIngredients"); final JSONArray usedIngredientsJson = recipeJson.getJSONArray("usedIngredients"); final JSONArray unusedIngredientsJson = recipeJson.getJSONArray("unusedIngredients"); - System.out.println(missedIngredientsJson); // for testing String image = recipeJson.getString("image"); // Collect ingredients from the JSON response @@ -234,19 +229,21 @@ public List filterSearchRecipes(List ingredients, String diet, S } // Create and add Recipe object to the list - Recipe recipe = new Recipe(title, recipeUrl, recipeIngredients, image); + final Recipe recipe = new Recipe(title, recipeUrl, recipeIngredients, image); recipes.add(recipe); } - } else { + } + else { System.out.println("Request failed with code: " + response.code()); } - } catch (IOException e) { - e.printStackTrace(); + } + catch (IOException exception) { + exception.printStackTrace(); } return recipes; } - // hard-coded for now + // hard-coded @Override public List getAvailableDiets() { final List diets = new ArrayList<>(); diff --git a/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java b/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java index cac7f4fcd..9881b3b08 100644 --- a/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java +++ b/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java @@ -2,10 +2,8 @@ import java.util.List; -import entity.Recipe; import use_case.filter_recipes.FilterRecipesInputBoundary; import use_case.filter_recipes.FilterRecipesInputData; -import use_case.filter_recipes.FilterRecipesInteractor; /** * Controller for the filter recipes use case. @@ -17,18 +15,6 @@ public FilterRecipesController(FilterRecipesInputBoundary filterRecipesInputBoun this.filterRecipesInputBoundary = filterRecipesInputBoundary; } - // /** - // * Executes the filter recipes use case. - // * @param diet diet choice - // * @param cuisine cuisine choice - // * @param ingredients list of ingredients - // */ - // public void execute(DietaryPreference diet, CuisinePreference cuisine, List ingredients) { - // final FilterRecipesInputData filterRecipesInputData = new FilterRecipesInputData(diet, cuisine, ingredients); - - // filterRecipesUseCaseInteractor.execute(filterRecipesInputData); - // } - public List getAvailableDiets() { return filterRecipesInputBoundary.getAvailableDiets(); } diff --git a/src/main/java/use_case/filter_recipes/FilterRecipesDataAccessInterface.java b/src/main/java/use_case/filter_recipes/FilterRecipesDataAccessInterface.java index 4c333148e..6b6e9a7bd 100644 --- a/src/main/java/use_case/filter_recipes/FilterRecipesDataAccessInterface.java +++ b/src/main/java/use_case/filter_recipes/FilterRecipesDataAccessInterface.java @@ -9,20 +9,6 @@ */ public interface FilterRecipesDataAccessInterface { - /** - * Filters recipes by diet. - * @param diet the diet preference - * @return a list of recipes with that diet - */ - // List filterRecipesByDiet(DietaryPreference diet); - - /** - * Filters recipes by cuisine. - * @param cuisine the cuisine preference - * @return a list of recipes with that cuisine - */ - // List filterRecipesByCuisine(CuisinePreference cuisine); - /** * Filters recipes by ingredients, optional diet and cuisine. * @param ingredients list of ingredients diff --git a/src/main/java/use_case/filter_recipes/FilterRecipesInputBoundary.java b/src/main/java/use_case/filter_recipes/FilterRecipesInputBoundary.java index b3062f50c..be8fbbca1 100644 --- a/src/main/java/use_case/filter_recipes/FilterRecipesInputBoundary.java +++ b/src/main/java/use_case/filter_recipes/FilterRecipesInputBoundary.java @@ -7,12 +7,6 @@ */ public interface FilterRecipesInputBoundary { - // /** - // * Execute the filter recipes use case. - // * @param filterRecipesInputData the input data for this use case - // */ - // void execute(FilterRecipesInputData filterRecipesInputData); - /** * Get available diets. * @return list of available diets From 8b7241c985cc717b14bd616bfb08f15be7dfe9aa Mon Sep 17 00:00:00 2001 From: kayzoo8 Date: Tue, 3 Dec 2024 00:25:42 -0500 Subject: [PATCH 147/148] dependency between recipe view and controller --- .../FilterRecipesController.java | 36 ++++++++++--------- src/main/java/view/HomePage.java | 7 +++- src/main/java/view/RecipeView.java | 13 +++++++ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java b/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java index 9881b3b08..8d3b46715 100644 --- a/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java +++ b/src/main/java/interface_adapter/filter_recipes/FilterRecipesController.java @@ -9,30 +9,34 @@ * Controller for the filter recipes use case. */ public class FilterRecipesController { - private FilterRecipesInputBoundary filterRecipesInputBoundary; + private final FilterRecipesInputBoundary filterRecipesInteractor; - public FilterRecipesController(FilterRecipesInputBoundary filterRecipesInputBoundary) { - this.filterRecipesInputBoundary = filterRecipesInputBoundary; + public FilterRecipesController(FilterRecipesInputBoundary filterRecipesInteractor) { + this.filterRecipesInteractor = filterRecipesInteractor; } - public List getAvailableDiets() { - return filterRecipesInputBoundary.getAvailableDiets(); - } - - public List getAvailableCuisines() { - // return filterRecipesUseCaseInteractor.getAvailableCuisines(); - return filterRecipesInputBoundary.getAvailableCuisines(); - } /** - * Filter searches recipes. + * Executes the filter recipes use case. * @param ingredients list of ingredients - * @param selectedDiet diet choice - * @param selectedCuisine cuisine choice + * @param selectedDiet chosen diet filter + * @param selectedCuisine chosen cuisine filter */ - public void filterSearchRecipes(List ingredients, String selectedDiet, String selectedCuisine) { + public void execute(List ingredients, String selectedDiet, String selectedCuisine) { final FilterRecipesInputData filterRecipesInputData = new FilterRecipesInputData(ingredients, selectedDiet, selectedCuisine); - filterRecipesInputBoundary.filterSearchRecipes(filterRecipesInputData); + filterRecipesInteractor.filterSearchRecipes(filterRecipesInputData); } + +// /** +// * Filter searches recipes. +// * @param ingredients list of ingredients +// * @param selectedDiet diet choice +// * @param selectedCuisine cuisine choice +// */ +// public void filterSearchRecipes(List ingredients, String selectedDiet, String selectedCuisine) { +// final FilterRecipesInputData filterRecipesInputData = +// new FilterRecipesInputData(ingredients, selectedDiet, selectedCuisine); +// filterRecipesInputBoundary.filterSearchRecipes(filterRecipesInputData); +// } } diff --git a/src/main/java/view/HomePage.java b/src/main/java/view/HomePage.java index e81b728f3..84641a7f7 100644 --- a/src/main/java/view/HomePage.java +++ b/src/main/java/view/HomePage.java @@ -14,7 +14,9 @@ import interface_adapter.RecipeListViewModel; import interface_adapter.SearchRecipePresenter; import interface_adapter.ShoppingListController; +import interface_adapter.filter_recipes.FilterRecipesController; import use_case.ShoppingListUseCase; +import use_case.filter_recipes.FilterRecipesInteractor; import use_case.search_recipe.SearchRecipeUseCase; public class HomePage extends JFrame { @@ -41,7 +43,10 @@ public HomePage(User user) { // Create the controller final RecipeController controller = new RecipeController(interactor); // Open the RecipeView and pass dependencies - new RecipeView(controller, presenter, user, new SpoonacularRecipeDAO()); + // new RecipeView(controller, presenter, user, new SpoonacularRecipeDAO()); + final FilterRecipesInteractor filterInteractor = new FilterRecipesInteractor(new SpoonacularRecipeDAO()); + final FilterRecipesController filterController = new FilterRecipesController(filterInteractor); + new RecipeView(controller, presenter, user, filterController, new SpoonacularRecipeDAO()); }); // Shopping List Button diff --git a/src/main/java/view/RecipeView.java b/src/main/java/view/RecipeView.java index fee1cc95b..6452eecd0 100644 --- a/src/main/java/view/RecipeView.java +++ b/src/main/java/view/RecipeView.java @@ -12,6 +12,7 @@ import entity.User; import interface_adapter.RecipeController; import interface_adapter.SearchRecipePresenter; +import interface_adapter.filter_recipes.FilterRecipesController; import use_case.filter_recipes.FilterRecipesDataAccessInterface; public class RecipeView extends JFrame { @@ -21,6 +22,7 @@ public class RecipeView extends JFrame { private final JList recipeList; private final DefaultListModel listModel; private final RecipeController controller; + private final FilterRecipesController filterController; private final SearchRecipePresenter presenter; private final User user; @@ -29,11 +31,21 @@ public class RecipeView extends JFrame { private final JComboBox cuisineComboBox; private final String defaultFilter; +// public RecipeView(RecipeController controller, SearchRecipePresenter presenter, User user, +// FilterRecipesDataAccessInterface frDataAccessInterface) { +// this.controller = controller; +// this.presenter = presenter; +// this.user = user; +// this.frDataAccessInterface = frDataAccessInterface; +// this.defaultFilter = "Any"; + public RecipeView(RecipeController controller, SearchRecipePresenter presenter, User user, + FilterRecipesController filterController, FilterRecipesDataAccessInterface frDataAccessInterface) { this.controller = controller; this.presenter = presenter; this.user = user; + this.filterController = filterController; this.frDataAccessInterface = frDataAccessInterface; this.defaultFilter = "Any"; @@ -138,4 +150,5 @@ private List applyFilters() { frDataAccessInterface.filterSearchRecipes(ingredients, selectedDiet, selectedCuisine); return recipesFiltered; } + } From a214c5ff87b26b952c24f82ef08bb165254860bd Mon Sep 17 00:00:00 2001 From: Cindyzzz616 Date: Tue, 3 Dec 2024 01:17:27 -0500 Subject: [PATCH 148/148] cleaned up some print statements in the code; completed the accessibility report --- accessibility-report.md | 38 ++++++++++++------- src/main/java/SpoonacularExample.java | 5 --- .../data_access/SpoonacularRecipeDAO.java | 10 ----- src/main/java/data_access/UserDAOImpl.java | 2 +- src/main/java/entity/User.java | 1 - .../RecipeListController.java | 27 ------------- .../bookmark/BookmarkController.java | 31 --------------- .../bookmark/BookmarkPresenter.java | 14 ------- ...earchRecipeListByIngredientInteractor.java | 12 ++++-- .../SearchRecipeListByIngredientUseCase.java | 36 ------------------ .../SearchRecipeListByNameInteractor.java | 14 +++++-- .../SearchRecipeListByNameUseCase.java | 27 ------------- src/main/java/view/BookmarkView.java | 1 - src/main/java/view/RecipeListView.java | 22 ----------- 14 files changed, 44 insertions(+), 196 deletions(-) delete mode 100644 src/main/java/interface_adapter/RecipeListController.java delete mode 100644 src/main/java/interface_adapter/bookmark/BookmarkController.java delete mode 100644 src/main/java/interface_adapter/bookmark/BookmarkPresenter.java delete mode 100644 src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java delete mode 100644 src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java diff --git a/accessibility-report.md b/accessibility-report.md index 8a8ffbc34..a47a17ddc 100644 --- a/accessibility-report.md +++ b/accessibility-report.md @@ -1,10 +1,10 @@ # Accessibility Report ## Question 1 -For each Principle of Universal Design, write 2-3 sentences — or point form notes — explaining which features of your program adhere to that principle. If you do not have any such features, you can either: +*For each Principle of Universal Design, write 2-3 sentences — or point form notes — explaining which features of your program adhere to that principle. If you do not have any such features, you can either:* -(a) Describe features that you could implement in the future that would adhere to the principle or -(b) Explain why the principle does not apply to a program like yours. +*(a) Describe features that you could implement in the future that would adhere to the principle or* +*(b) Explain why the principle does not apply to a program like yours.* ### Principle 1: Equitable Use - this software can be accessed from any smart device - it does not require any special hardware or plugins @@ -26,17 +26,27 @@ For each Principle of Universal Design, write 2-3 sentences — or point form no ### Principle 7: Size and Space for Approach and Use - not applicable here since the software is not connect to any hardware - -*(for Q2 and 3, just wrote down some bullet points for now)* - ## Question 2 -- university students learning to cook -- busy parents who are too busy for meal planning -- people looking to explore new cuisines and recipes -- athletes who are conscious about nutrition +*Write a paragraph (3-6 sentences) about who you would market your program towards, if you were to sell or license your program to customers. This could be a specific category of users, such as "students", or more vague, such as "people who like games". Try to give a bit more detail along with the category.* + +This software could be market towards a variety of users who have a similar need - to find recipes based on the ingredients +they had on hand. Users might seek out this program for a variety of reasons: for example, university students learning to cook +would find it useful, since they might not know what dishes can be made from the ingredients they have. They are also likely +to need instructions when cooking, which the software can provide by opening details of recipes in a browsers. This software +could also be helpful for busy professionals or parents who are too busy for meal planning. They can use the software to find +last-minute recipes they can make. People exploring new cuisines and recipes can also benefit from this software, since it +provides features like filtering by cuisines. Finally, this software is useful to those on special diets or are conscious about +nutrition, since it allows users to filter recipes by diet and view the nutrition breakdown of each recipe. ## Question 3 -Write a paragraph about whether or not your program is less likely to be used by certain demographics. Your discussion here should be informed by the content of our embedded ethics modules this term. -- older people living alone who are not familiar with technology, or are more familiar with traditional recipes -- people from cultural groups whose cuisines are not represented in the recipe database -- people with religious dietary restrictions (no filters for halal, kosher, etc.) +*Write a paragraph about whether or not your program is less likely to be used by certain demographics. Your discussion here should be informed by the content of our embedded ethics modules this term.* + +This software could be challenging to use by older people who are not familiar with technology or who might prefer traditional +recipes. They might suffer more tangible harm from the lack of access to practical recipes, since it is more likely for them to +have mobility impairments and thus a more limited stock of ingredients. Those with visual impairments might also have difficulty +using the software, since it does not currently have features such as speech-to-text and alternate captions, which allow for +hands-free usage. This is an intervention that stems from a social model of disability, since it would also allow users to use +the software when their hands are occupied with cooking. Furthermore, the software might not be favoured by people form cultural +groups whose cuisines are not well-represented in the recipe database from Spoonacular. This potentially causes relational harm, +since users from those cultural groups might feel like their cuisines are marginalized. A similar reasoning applies for users +with religious dietary restrictions, since there are no filters for categories such as halal and kosher. diff --git a/src/main/java/SpoonacularExample.java b/src/main/java/SpoonacularExample.java index 668a56c2c..85c109dea 100644 --- a/src/main/java/SpoonacularExample.java +++ b/src/main/java/SpoonacularExample.java @@ -32,11 +32,6 @@ public static void main(String[] args) { // Loop through the results and print recipe titles and IDs for (int i = 0; i < results.length(); i++) { final JSONObject recipe = results.getJSONObject(i); - System.out.println("Recipe: " + recipe.getString("title")); - System.out.println("ID: " + recipe.getInt("id")); - System.out.println("Image: " + recipe.getString("image")); - // Not sure what data type is "cuisines"... - // System.out.println("Cuisines: " + recipe.getString("cuisines")); } } else { diff --git a/src/main/java/data_access/SpoonacularRecipeDAO.java b/src/main/java/data_access/SpoonacularRecipeDAO.java index 194df4fbd..7bf0b00c3 100644 --- a/src/main/java/data_access/SpoonacularRecipeDAO.java +++ b/src/main/java/data_access/SpoonacularRecipeDAO.java @@ -87,17 +87,11 @@ public List getRecipesByIngredients(List ingredients) { System.out.println("Request failed with code: " + response.code()); } - // for testing purposes - System.out.println(recipeJson.keySet()); - System.out.println(completeRecipe.keySet()); - System.out.println(recipeJson.getInt("missedIngredientCount")); String title = recipeJson.getString("title"); - // String recipeUrl = BASE_URL + "/recipes/" + recipeJson.getInt("id") + "/information"; // URL to recipe details final String recipeUrl = completeRecipe.getString("spoonacularSourceUrl"); final JSONArray missedIngredientsJson = recipeJson.getJSONArray("missedIngredients"); final JSONArray usedIngredientsJson = recipeJson.getJSONArray("usedIngredients"); final JSONArray unusedIngredientsJson = recipeJson.getJSONArray("unusedIngredients"); - System.out.println(missedIngredientsJson); // for testing String image = recipeJson.getString("image"); // Collect ingredients from the JSON response @@ -207,10 +201,6 @@ public List filterSearchRecipes(List ingredients, String diet, S final int id = recipeJson.getInt("id"); final JSONObject completeRecipe = getCompleteRecipe(id); - // for testing purposes - System.out.println(recipeJson.keySet()); - // System.out.println(completeRecipe.keySet()); - System.out.println(recipeJson.getInt("missedIngredientCount")); String title = recipeJson.getString("title"); // String recipeUrl = BASE_URL + "/recipes/" + recipeJson.getInt("id") + "/information"; // URL to recipe details final String recipeUrl = completeRecipe.getString("spoonacularSourceUrl"); diff --git a/src/main/java/data_access/UserDAOImpl.java b/src/main/java/data_access/UserDAOImpl.java index cdc062803..039c98705 100644 --- a/src/main/java/data_access/UserDAOImpl.java +++ b/src/main/java/data_access/UserDAOImpl.java @@ -334,7 +334,7 @@ else if ("recentlyViewed".equals(folder)) { for (Recipe recipe : recipeList) { final String[] words = recipe.getName().split(" "); for (String word : words) { - if (word.equalsIgnoreCase(recipeName)) { + if (word.toLowerCase().contains(recipeName.trim().toLowerCase())) { results.add(recipe); } } diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java index 2b03134c0..1fae23bf2 100644 --- a/src/main/java/entity/User.java +++ b/src/main/java/entity/User.java @@ -70,7 +70,6 @@ public Map> getFolders() { public List getFolder(String folder) { if (!folders.containsKey(folder)) { - System.out.println("No such folder: " + folder); return List.of(); } else { diff --git a/src/main/java/interface_adapter/RecipeListController.java b/src/main/java/interface_adapter/RecipeListController.java deleted file mode 100644 index 8426bf001..000000000 --- a/src/main/java/interface_adapter/RecipeListController.java +++ /dev/null @@ -1,27 +0,0 @@ -package interface_adapter; - -import entity.Recipe; -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; - -import java.util.List; - -public class RecipeListController { - private final SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase; - private final SearchRecipeListByNameUseCase searchRecipeListByNameUseCase; - - public RecipeListController(SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase, - SearchRecipeListByNameUseCase searchRecipeListByNameUseCase) { - this.searchRecipeListByIngredientUseCase = searchRecipeListByIngredientUseCase; - this.searchRecipeListByNameUseCase = searchRecipeListByNameUseCase; - } - - // getters - public List getRecipesByIngredients(List ingredients) { - return searchRecipeListByIngredientUseCase.searchRecipes(ingredients); - } - - public List getRecipesByName(String name) { - return searchRecipeListByNameUseCase.searchRecipes(name); - } -} diff --git a/src/main/java/interface_adapter/bookmark/BookmarkController.java b/src/main/java/interface_adapter/bookmark/BookmarkController.java deleted file mode 100644 index fabb96e58..000000000 --- a/src/main/java/interface_adapter/bookmark/BookmarkController.java +++ /dev/null @@ -1,31 +0,0 @@ -//package interface_adapter.bookmark; -// -//import interface_adapter.RecipeListController; -//import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; -//import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; -// -//public class BookmarkController extends RecipeListController { -// private final BookmarkInputBoundary bookmarkUseCaseInteractor; -// -// public BookmarkController(SearchRecipeListByIngredientUseCase searchRecipeListByIngredientUseCase, -// SearchRecipeListByNameUseCase searchRecipeListByNameUseCase, -// BookmarkInputBoundary bookmarkUseCaseInteractor) { -// super(searchRecipeListByIngredientUseCase, searchRecipeListByNameUseCase); -// this.bookmarkUseCaseInteractor = bookmarkUseCaseInteractor; -// } -// -// public void execute(...) { -// final BookmarkInputData bookmarkInputData = new BookmarkInputData(...); -// -// bookmarkUseCaseInteractor.execute(bookmarkInputData); -// } -// -//// public addBookmark(Recipe recipe) { -//// -//// } -//// -//// public void execute(Recipe recipe) { -//// final BookmarkInputData bookmarkInputData = new BookmarkInputData(recipe); -//// -//// bookmarkUseCaseInteractor.execute(bookmarkInputData); -//} diff --git a/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java b/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java deleted file mode 100644 index f8e7da30e..000000000 --- a/src/main/java/interface_adapter/bookmark/BookmarkPresenter.java +++ /dev/null @@ -1,14 +0,0 @@ -//package interface_adapter.bookmark; -// -//public class BookmarkPresenter implements BookmarkOutputBoundary { -// private final BookmarkViewModel bookmarkViewModel; -// -// public BookmarkPresenter(BookmarkViewModel bookmarkViewModel) { -// this.bookmarkViewModel = bookmarkViewModel; -// } -// -// @Override -// public void prepareSuccessView(BookmarkOutputData outputData) { -// bookmarkViewModel.firePrepertyChanged("property name..."); -// } -//} diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java index 62a90adbd..06b21206f 100644 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java +++ b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientInteractor.java @@ -1,10 +1,13 @@ package use_case.search_recipe_list_by_ingredient; +import java.util.List; + import entity.Recipe; import entity.User; -import java.util.List; - +/** + * The Interactor for the Search Recipe List By Name Use Case. + */ public class SearchRecipeListByIngredientInteractor implements SearchRecipeListByIngredientInputBoundary { /** @@ -22,14 +25,17 @@ public SearchRecipeListByIngredientInteractor( @Override public void execute(SearchRecipeListByIngredientInputData searchRecipeListByIngredientInputData) { + // Obtain the parameters for the search from the input data object final List ingredients = searchRecipeListByIngredientInputData.getIngredients(); final User user = searchRecipeListByIngredientInputData.getUser(); final String folder = searchRecipeListByIngredientInputData.getFolder(); + // Obtain the results of the search from the userDAO final List recipes = userDataAccessObject.searchRecipeListByIngredient(ingredients, user, folder); - + // Put the results in the output data object final SearchRecipeListByIngredientOutputData searchRecipeListByIngredientOutputData = new SearchRecipeListByIngredientOutputData(recipes, false); + // Pass the output data to the presenter userPresenter.prepareSuccessView(searchRecipeListByIngredientOutputData); } } diff --git a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java b/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java deleted file mode 100644 index 1c23cc0bf..000000000 --- a/src/main/java/use_case/search_recipe_list_by_ingredient/SearchRecipeListByIngredientUseCase.java +++ /dev/null @@ -1,36 +0,0 @@ -package use_case.search_recipe_list_by_ingredient; - -import entity.Ingredient; -import entity.Recipe; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class SearchRecipeListByIngredientUseCase { - private final List recipeList; - - public SearchRecipeListByIngredientUseCase(List recipeList) { - this.recipeList = recipeList; - } - - public List searchRecipes(List enteredIngredients) { - final List results = new ArrayList<>(); - for (Recipe recipe : recipeList) { - final List recipeIngredients = recipe.getIngredients(); - final List recipeIngredientsString = new ArrayList<>(); - for (Ingredient ingredient : recipeIngredients) { - final String[] words = ingredient.getName().split(" "); - recipeIngredientsString.addAll(Arrays.asList(words)); - } - for (String recipeIngredient : recipeIngredientsString) { - for (String enteredIngredient : enteredIngredients) { - if (recipeIngredient.equalsIgnoreCase(enteredIngredient)) { - results.add(recipe); - } - } - } - } - return results; - } -} diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java index d98610be4..23d840924 100644 --- a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java +++ b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameInteractor.java @@ -1,10 +1,13 @@ package use_case.search_recipe_list_by_name; +import java.util.List; + import entity.Recipe; import entity.User; -import java.util.List; - +/** + * The Interactor for the Search Recipe List By Name Use Case. + */ public class SearchRecipeListByNameInteractor implements SearchRecipeListByNameInputBoundary { /** @@ -22,14 +25,17 @@ public SearchRecipeListByNameInteractor( @Override public void execute(SearchRecipeListByNameInputData searchRecipeListByNameInputData) { - String recipeName = searchRecipeListByNameInputData.getRecipeName(); + // Obtain the parameters for the search from the input data object + final String recipeName = searchRecipeListByNameInputData.getRecipeName(); final User user = searchRecipeListByNameInputData.getUser(); final String folder = searchRecipeListByNameInputData.getFolder(); + // Obtain the results of the search from the userDAO final List recipes = userDataAccessObject.searchRecipeListByName(recipeName, user, folder); - + // Put the results in the output data object final SearchRecipeListByNameOutputData searchRecipeListByNameOutputData = new SearchRecipeListByNameOutputData(recipes, false); + // Pass the output data to the presenter userPresenter.prepareSuccessView(searchRecipeListByNameOutputData); } } diff --git a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java b/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java deleted file mode 100644 index aca808a2e..000000000 --- a/src/main/java/use_case/search_recipe_list_by_name/SearchRecipeListByNameUseCase.java +++ /dev/null @@ -1,27 +0,0 @@ -package use_case.search_recipe_list_by_name; - -import entity.Recipe; - -import java.util.ArrayList; -import java.util.List; - -public class SearchRecipeListByNameUseCase { - private final List recipeList; - - public SearchRecipeListByNameUseCase(List recipeList) { - this.recipeList = recipeList; - } - - public List searchRecipes(String name) { - final List results = new ArrayList<>(); - for (Recipe recipe : recipeList) { - final String[] words = recipe.getName().split(" "); - for (String word : words) { - if (word.equalsIgnoreCase(name)) { - results.add(recipe); - } - } - } - return results; - } -} diff --git a/src/main/java/view/BookmarkView.java b/src/main/java/view/BookmarkView.java index 23e288ce5..843d1fbd9 100644 --- a/src/main/java/view/BookmarkView.java +++ b/src/main/java/view/BookmarkView.java @@ -39,7 +39,6 @@ private JComboBox getDropdown(User user) { dropdown.addActionListener(event -> { final String selectedOption = (String) dropdown.getSelectedItem(); - System.out.println(selectedOption + "folder name in bookmark view"); new FolderView(user, selectedOption, new RecipeListViewModel()); }); return dropdown; diff --git a/src/main/java/view/RecipeListView.java b/src/main/java/view/RecipeListView.java index a67710d5f..44e351d7c 100644 --- a/src/main/java/view/RecipeListView.java +++ b/src/main/java/view/RecipeListView.java @@ -17,7 +17,6 @@ import data_access.UserDAOImpl; import entity.Recipe; import entity.User; -import interface_adapter.RecipeListController; import interface_adapter.RecipeListState; import interface_adapter.RecipeListViewModel; import interface_adapter.search_recipe_list_by_ingredient.SearchRecipeListByIngredientController; @@ -27,17 +26,14 @@ import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInputBoundary; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientInteractor; import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientOutputBoundary; -import use_case.search_recipe_list_by_ingredient.SearchRecipeListByIngredientUseCase; import use_case.search_recipe_list_by_name.SearchRecipeListByNameInputBoundary; import use_case.search_recipe_list_by_name.SearchRecipeListByNameInteractor; import use_case.search_recipe_list_by_name.SearchRecipeListByNameOutputBoundary; -import use_case.search_recipe_list_by_name.SearchRecipeListByNameUseCase; public abstract class RecipeListView extends JFrame implements ActionListener, PropertyChangeListener { protected static UserDAOImpl userDAO; protected final JList recipeList; protected final DefaultListModel listModel; - private final RecipeListController controller; private final User user; private JTextField ingredientSearchField; @@ -65,9 +61,6 @@ public RecipeListView(User user, String folderName, RecipeListViewModel recipeLi this.userDAO = new UserDAOImpl(); this.recipeList = new JList<>(); this.listModel = new DefaultListModel<>(); - this.controller = new RecipeListController(new SearchRecipeListByIngredientUseCase( - getRecipeList(userDAO.findUserByUsername(user.getUsername()))), - new SearchRecipeListByNameUseCase(getRecipeList(userDAO.findUserByUsername(user.getUsername())))); this.spoonacularRecipeDAO = new SpoonacularRecipeDAO(); this.recipeListViewModel = recipeListViewModel; @@ -193,14 +186,6 @@ public void actionPerformed(ActionEvent event) { if (event.getSource() == ingredientSearchButton) { final String userInput = ingredientSearchField.getText(); final List ingredients = Arrays.asList(userInput.split(",")); - // TODO double check then delete -// List recipes = controller.getRecipesByIngredients(ingredients); -// final DefaultListModel ingredientSearchListModel = new DefaultListModel<>(); -// for (Recipe recipe : recipes) { -// ingredientSearchListModel.addElement(recipe); -// } -// recipeList.setModel(ingredientSearchListModel); - final RecipeListState currentState = recipeListViewModel.getState(); currentState.setFolder(folderName); currentState.setUser(user); @@ -215,16 +200,9 @@ public void actionPerformed(ActionEvent event) { } if (event.getSource() == recipeSearchButton) { final String userInput = recipeSearchField.getText(); - // TODO double check then delete -// final List recipes = controller.getRecipesByName(userInput); - final RecipeListState currentState = recipeListViewModel.getState(); currentState.setFolder(folderName); currentState.setUser(user); - System.out.println("recipe list view"); - System.out.println(folderName); - System.out.println(userInput); - System.out.println(user.getUsername()); this.searchRecipeListByNameController.execute( userInput, currentState.getUser(), currentState.getFolder()); recipeListViewModel.setState(currentState);