By: AY1920S2-CS2103T-F11-4 Since: Jun 2016 Licence: MIT
- 1. Setting up
- 2. Design
- 3. Implementation
- 3.1. Add recipe to the cookbook (by Teo Jun Xiong)
- 3.2. View recipe in the cookbook (by Teo Jun Xiong)
- 3.3. Search for recipes based on ingredients in the inventory (by Goh Ka Hui)
- 3.4. Add ingredients to inventory and cart (by Tay Zi Hiang Willis)
- 3.5. Remove ingredients of a recipe from the inventory (by Chan Shun Jie)
- 3.6. Move ingredients from cart to inventory (by Tay Zi Hiang Willis)
- 3.7. Add a Recipe’s Ingredients to Cart (by Ong Han Sheng)
- 3.8. Export ingredients in cart to PDF file (by Teo Jun Xiong)
- 3.9. Configuration
- 4. Documentation
- 5. Testing
- 6. Dev Ops
- Appendix A: Product Scope
- Appendix B: User Stories
- Appendix C: Use Cases
- Appendix D: Non Functional Requirements
- Appendix E: Glossary
- Appendix F: Instructions for Manual Testing
- Appendix G: Effort
1. Setting up
Refer to the guide here.
2. Design
2.1. Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
-
At app launch: Initializes the components in the correct sequence, and connects them up with each other.
-
At shut down: Shuts down the components and invokes cleanup method where necessary.
Commons represents a collection of classes used by multiple other components.
The following class plays an important role at the architecture level:
-
LogsCenter: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four components.
Each of the four components
-
Defines its API in an
interfacewith the same name as the Component. -
Exposes its functionality using a
{Component Name}Managerclass.
For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command cookbook remove recipe 2.
cookbook remove recipe 2 commandThe sections below give more details of each component.
2.2. UI component (by Teo Jun Xiong)
API : Ui.java
The UI consists of a MainWindow that is made up of several UI parts: CommandBox, ResultDisplay,
CookbookPanel,
RecipeCard, InventoryPanel, CartPanel, IngredientCard,
StatusBarFooter. All these, including the MainWindow, inherit from the abstract UiPart class.
The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml
The UI component,
-
Executes user commands using the
Logiccomponent. -
Listens for changes to
Modeldata so that the UI can be updated with the modified data.
2.3. Logic component (by Goh Ka Hui)
API :
Logic.java
-
Logicuses theCookingPapaParserclass to parse the user command. -
This results in a
Commandobject which is executed by theLogicManager. -
The command execution can affect the
Model(e.g. adding a recipe). -
The result of the command execution is encapsulated as a
CommandResultobject which is passed back to theUi. -
In addition, the
CommandResultobject can also instruct theUito perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the Logic component for the execute("cookbook remove recipe 2") API call.
cookbook remove recipe 2 command2.4. Model component (by Goh Ka Hui)
API : Model.java
The Model,
-
stores a
UserPrefobject that represents the user’s preferences. -
stores the Cookbook data.
-
exposes an unmodifiable
ObservableList<Recipe>that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -
stores the Inventory and Cart data.
-
exposes an unmodifiable
ObservableList<Recipe>each for both Inventory and Cart, that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -
does not depend on any of the other three components.
As a more OOP model, we can store a Tag list in Cookbook, which Recipe can reference. This would allow Cookbook to only require one Tag object per unique Tag, instead of each Recipe needing their own Tag object. An example of how such a model may look like is given below.
|
2.5. Storage component (by Goh Ka Hui)
API : Storage.java
The Storage component,
-
can save
UserPrefobjects in json format and read it back. -
can save
Cookbookdata in json format and read it back. -
can save
Inventorydata in json format and read it back. -
can save
Cartdata in json format and read it back.
2.6. Common classes
Classes used by multiple components are in the seedu.addressbook.commons package.
3. Implementation
This section describes some noteworthy details on how certain features are implemented.
3.1. Add recipe to the cookbook (by Teo Jun Xiong)
3.1.1. Implementation
The recipe addition mechanism is facilitated by CookbookAddCommand, which extends the Command abstract class. The
format of the command is as follows: cookbook add recipe n/NAME d/DESCRIPTION [i/INGREDIENT]… [q/QUANTITY]… [t/TAG]….
This command is implemented this way to allow a user to add a recipe with optional fields (ingredients, steps, tags) -
only the recipe name and recipe description are mandatory fields. This way, a user does not have input all the fields
that they may not have at the moment to create a recipe. After creating the skeleton of the recipe, the user can then
use the other Cookbook commands to add ingredients and steps to the recipe. However, one key point is that should
ingredient names be provided, the same number of ingredient quantities have to be provided as well.
Below is a step by step sequence of what happens when a user enters this command:
-
The user enters a recipe adding command using the command line input
cookbook add recipe n/NAME d/DESCRIPTION [i/INGREDIENT]… [q/QUANTITY]… [s/STEP_DESCRIPTION]… [t/TAG]…. -
CookingPapaParserparses the user input and checks if it is valid. If it is invalid, i.e. an unknown command category, aParseExceptionwill be thrown. If the input is valid, with the command categorycookbook, a newCookbookCommandParseris created. -
CookbookCommandParserthen parsesadd recipe n/NAME d/DESCRIPTION [i/INGREDIENT]… [q/QUANTITY]… [s/STEP_DESCRIPTION]… [t/TAG]…. If it is invalid, i.e. an unknown command word, aParseExceptionwill be thrown. If the input is valid, with the command wordadd, a newCookbookAddCommandParseris created. -
CookbookAddCommandParserparsesrecipe n/NAME d/DESCRIPTION [i/INGREDIENT]… [q/QUANTITY]… [s/STEP_DESCRIPTION]… [t/TAG]…and checks ifn/NAMEandd/DESCRIPTIONare provided. If either are not provided, then aParseExceptionwill be thrown.It then parses the input into the following fields: recipe name, recipe description, ingredients, steps, and tags.
Note that the ingredient names and ingredient quantities provided must be the same, or a
ParseExceptionwill be thrown:if (names.size() != quantities.size()) { throw new ParseException( String.format(MESSAGE_DIFFERENT_NUMBER_OF_INPUTS, names.size(), quantities.size())); } -
These fields are then passed as parameters for
Recipe, which is then passed as the parameter forCookbookAddCommandand returned toLogicManager. -
LogicManagercallsCookbookAddCommand#execute()which checks if the cookbook already contains the same recipe with the same name, description, ingredient names, ingredient quantities, and tags usingModel#hasCookbookRecipe().If there is a duplicate, a
CommandExceptionis thrown, stating that the user is attempting to add a duplicate recipe:if (model.hasCookbookRecipe(toAdd)) { throw new CommandException(MESSAGE_DUPLICATE_RECIPE); } -
If
CommandExceptionis not thrown,Model#addCookbookRecipewill be executed, with the recipe to be added as a parameter. -
Model#addCookbookRecipe()then executesCookbook#addRecipe(), which adds the recipe to the cookbook, and theFilteredList<Recipe>representing the recipes in the cookbook are updated withModel#updateFilteredCookbookRecipeList():updateFilteredCookbookRecipeList(PREDICATE_SHOW_ALL_RECIPES)where
PREDICATE_SHOW_ALL_RECIPES = unused → true. -
A
CommandResultwith the text to display to the user is then returned toLogicManager, which can passed back toMainWindow, which displays it to the user on the CLI and GUI:resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()). The text displayed will notify the user on whether their addition was successful.
The following Recipe object diagram is an overview of the attributes of a Recipe object:
The following sequence diagram shows how the recipe adding function works (full command [cookbook add recipe
n/Recipe name d/Recipe
description i/Ingredient 1
q/1 piece i/Ingredient 2
q/20 ml s/Do step 1 s/Do
step 2 t/This t/Is t/A
t/Tag] omitted from diagram
for brevity):
The following activity diagram shows a possible flow of events for a user using this feature:
3.1.2. Design considerations
Aspect 1: How to parse optional parameters
Design A (current choice): Parse each category separately |
Design B: Parse all the categories together |
|
Description |
Each category (ingredient name, ingredient quantity, step description, tag) are parsed separately and returned as
|
Each category will be parsed together in one function in |
Pros |
|
|
Cons |
|
|
Design A was chosen because it was more user-friendly, and removed the restriction of having to include ingredients, steps, and tags at the stage of recipe creation, some of which the user may not have at the moment, i.e. experimenting with different ingredients. Additionally, design A allowed us to be more modular while coding.
Aspect 2: Result to show user
Design A (current choice): Show a short result on the success of the command |
Design B: Show all the details back to the user |
|
Description |
Show a message to a usage which notifies them that the command was successful in adding the recipe to the cookbook. |
Shows a message similar to design choice A, and also show all the details of the added recipe. |
Pros |
|
|
Cons |
|
|
Design A was chosen because it did not reuse the same component for multiple uses. Additionally, it allows us to reduce the size of result display, as most of the time, it displays only a short message displaying the success of a command.
3.2. View recipe in the cookbook (by Teo Jun Xiong)
The user may use this command to view a recipe in the cookbook. This command is integrated into the Graphical User Interface (GUI) through a button.
3.2.1. Implementation
The recipe viewing mechanism (via the command line input) is facilitated by CookbookViewCommand, which extends the
Command abstract class. The format is as follows: cookbook view recipe INDEX, which index has to be a valid
integer that is not out of bounds.
The recipe viewing mechanism (via the GUI) is facilitated by RecipeCard, which extends the UiPart abstract class.
It is triggered upon clicking the "view" icon in the recipe panel:
Implementing this function, cookbook view recipe through a button in the GUI allows user to view the details of a
recipe with a click of a button, greatly increasing convenience and user experience. The button also had to be
"activated" without the
button,
as
the command still had
to be testable through the command line.
Below is a step by step sequence of what happens when a user enters this command:
-
The user enters a view recipe command using the command line input
cookbook view recipe INDEX. -
CookingPapaParserparses the user input and checks if it is valid. If it is invalid, i.e. an unknown command category, aParseExceptionwill be thrown. If the input is valid, with the command categorycookbook, a newCookbookCommandParseris created. -
CookbookCommandParserthen parsesview recipe INDEX. If it is invalid, i.e. an unknown command word, aParseExceptionwill be thrown. If the input is valid, with the command categoryview, a newCookbookViewCommandParseris created. -
CookbookViewCommandParserthen parsesrecipe INDEXand checks if theStringcontains "recipe", and an index. If either are absent, aParseExceptionwill be thrown. If theStringis valid, aCookbookViewis created. -
CookbookViewCommandParserthen returns aCookbookViewCommandtoLogicManager. -
LogicManagercallsCookbookViewCommand#execute()which checks if the providedIndexis within the bounds of theFilteredCookbookRecipeList()inCookbook, i.e.index.getZeroBased() >= list.size(). If it is not, aCommandExceptionwill be thrown. If it is valid, aCommandResultis created with a boolean valuetrue. -
A
CommandResultwith the text to display to the user will be returned toLogicManager. TheCommandResultis then passed back toMainWindow. The boolean value stated in step 6 determines whether a successfully parsed command is acookbook view recipe INDEXcommand. -
MainWindow#handleViewRecipeis then executed, which creates a newCookbookPanelwith the same set of data, callingCookbookPanel#handleViewRecipe, which creates newRecipeCards forCookbook, and for theRecipeCardthat has an index equal to the index processed from the user’s input, it will create aRecipeCardthat toggles open the recipe details. More on how theRecipeCardmanages this will be discussed in the following section on how clicking on a button in the GUI has the same effect as thecookbook view recipe INDEXcommand. -
Lastly, the user then is shown a
CookbookPanelwith the selected recipe toggled open, which displays the full details of that recipe:
Below is a step by step sequence of what happens when a user clicks the button on the GUI:
-
When the button is pressed, the onAction method,
RecipeCard#handleViewButtonAction()is executed. ARecipeCardhas a variableisFullyDisplayed, which indicates whether it is displaying an overview of the recipe, or fully displaying details of the recipe. -
If
isFullyDisplayedis false, i.e. theRecipeCardis currently displaying an overview of the recipe,RecipeCard#displayRecipeComplete()is executed, which replaces the text displayed by the FXML object,Label, with the full details of the recipe. -
If
isFullyDisplayedis true, i.e. theRecipeCardis currently fully displaying the details of the recipe,RecipeCard#displayRecipeOverview()is executed, which replaces the text displayed by the FXML object,Labelwith the overview of the recipe. -
Both methods executed in step 3 and 4 will flip the boolean value of
isFullyDisplayed, and this means that the next time the button for the same recipe is clicked, it toggles back. For example, if a recipe with its overview shown has its view button clicked, it will show the full details of the recipe. If the button is clicked again, it toggles, and shows the overview of the recipe.This feature is not reflected with
cookbook view recipe INDEXwhen it is entered again in the command line, because the function of the command is to view a recipe, not to "un-view" it.
The following sequence diagram shows how the recipe viewing function interacts between the classes in Logic:
The following sequence diagram shows how the recipe viewing function interacts between the classes in Ui:
3.2.2. Design considerations
Aspect: what UI component to display the toggled content
Design A (current choice): toggles the content in the recipe panel |
Design B: add a new UI component that pops up, i .e. overlay |
|
Description |
The content in the recipe panel can freely switch from overview to full details of a recipe. |
A UI component appears as a small overlay, displaying the details of a recipe. The overlay can then be "exited" by clicking on an area within the application that is outside of the overlay. |
Pros |
|
No need to interact between various UI components, as much as design A |
Cons |
There is a need to keep track of the state of a |
Difficult to implement as it includes creating an entirely new component (overlay) with different features than the existing one. The effort estimated did not seem to be worth, as the use is limited to just this command. |
Design A was chosen because it made the GUI more functional, and less complicated to implement in terms of connecting
the various UiPart s.
3.3. Search for recipes based on ingredients in the inventory (by Goh Ka Hui)
The user may use this command to search for recipes that they can cook using the ingredients available in their
inventory. This feature was implemented to address users' need of easily finding a recipe based on the ingredients they have.
With this feature, users can whip up a meal without having to go grocery shopping if they are short of time. This feature sorts
recipes by how much the inventory fulfils their ingredient requirements, and filters out recipes whose ingredient
requirements are not met at all. Users can immediately see at the top of the cookbook the recipes that their ingredients
are most suitable for preparing. A user can use this feature by typing the command: cookbook search inventory.
3.3.1. Implementation
The comparison between the ingredients a recipe requires and the ingredients in the inventory is facilitated by the
RecipeInventoryIngredientsSimilarityComparator. It extends Comparator<Recipe> and stores the inventory being used
for ingredient comparison. Additionally, it implements the method calculateSimilarity(), which accepts a Recipe and
a ReadOnlyInventory as parameters, and returns a double value between 0 and 1 (both inclusive) that represents the
proportion of the recipe’s ingredient requirements that are fulfilled.
The following class diagram summarizes how the RecipeInventoryIngredientsSimilarityComparator interacts with Recipe
and Inventory:
The calculateSimilarity() method first calculates the proportion of ingredient quantity fulfilled by the inventory
for each ingredient that the recipe requires. For example, if one of the ingredients required by a recipe is 4 eggs
and the inventory contains 2 eggs, the proportion fulfilled for this particular ingredient is 0.5. This is done for
all the ingredients in the recipe. If the units of an ingredient in the recipe does not match that of the same
ingredient in the inventory, the proportion will be set at 0.5 by default. An example is when the recipe requires
1 cup flour and the inventory contains 200 g flour.
When the proportion fulfilled has been calculated for each ingredient, the values for each ingredient are summed up and
divided by the number of ingredients to obtain the average. In the case where the recipe does not have any ingredients
added to it yet, the calculateSimilarity() method will return 0, indicating no similarity to the inventory
ingredients. This is because it is likely that recipes with no ingredients have just been added by the user, and the
ingredients have not been added yet. If the user is using this feature to search for a recipe to cook, they would
probably not be interested in seeing a recipe that they have not added ingredients for yet. This is implemented via
a guard clause as shown in the following code snippet:
if (recipe.getIngredients().size() == NO_INGREDIENTS) {
return ZERO_SIMILARITY;
}
The following activity diagram shows a possible flow of events for a user using this feature:
cookbook search inventory commandThe following sequence diagram summarizes how objects interact when a user executes the command, with more focus on how the command is parsed in the `Logic`component:
3.3.2. Design considerations
Aspect 1: Weighting of each ingredient
| Design A (Current choice): Every ingredient is weighted equally | Design B: More important ingredients are given a larger weigting | |
|---|---|---|
Description |
The similarity of a recipe’s ingredients to an inventory’s ingredients is calculated by taking the mean of the proportions calculated for each ingredient, with equal weighting given to all ingredients. |
The similarity of a recipe’s ingredients to an inventory’s ingredients is calculated by taking the weighted mean of the proportions calculated for each ingredient, with larger weightings given to more important ingredients ingredients. |
Pros |
Gives a good rough estimate of the proportion of ingredient requirements fulfilled for a recipe, and straightforward to implement. |
May give a better gauge of the proportion of ingredient requirements fulfilled for a recipe, by accounting for the importance of the ingredient. For example, beef would be an important ingredient for a steak recipe, but garnishes might be considered less important as they can be substituted more easily. |
Cons |
Does not account for the importance of the ingredient in the recipe |
Difficult to judge the importance of the ingredient, and complicated to implement categorisation of the types ingredients and their relative importance. |
Design A was chosen as it provided a fair estimate of the similarity between the recipe and inventory ingredients, with a simple implementation. The cons for Design B were deemed to outweigh the pros, especially since the importance of an ingredient in a recipe could be rather subjective.
Aspect 2: Handling ingredients with different units
Design A (Current choice): Use a default similarity value of 0.5 |
Design B: Convert the units | |
|---|---|---|
Description |
The similarity value of an ingredient with different units in the recipe and the inventory is treated as |
The similarity value of an ingredient with different units in the recipe and the inventory is calculated by converting the units, such that the proportion of the recipe ingredient in the inventory can be determined. |
Pros |
Simple to implement. |
Able to calculate the proportion of the recipe ingredient fulfilled by the inventory, even when dealing with different units. |
Cons |
Unable to calculate the proportion of the recipe ingredient fulfilled by the inventory when dealing with different units, and can only give a fixed default value of |
More complicated to implement as it requires CookingPapa to recognise the units in both the recipe and inventory and be able to convert between them.
Some units such as |
Design A was chosen due to time constraints, as handling the conversion between different units would take time away from developing other parts of the application.
Given more time, Design B will be implemented to handle conversion for standard units, such as between g and kg, but Design A would still have to be used for units with non-standard conversion factors.
3.4. Add ingredients to inventory and cart (by Tay Zi Hiang Willis)
The inventory and cart acts as storage for Ingredient classes. They are facilitated by InventoryCommand and CartCommand
respectively, which extends the Command abstract class. Since CartAddCommand and InventoryAddCommand both serve the
same purpose in different contexts of Cart and Inventory respectively, they will be mentioned together in tandem.
This command was implemented to allow the user know to add an ingredient to the cart or inventory respectively.
An ingredient only has two main components - its name and quantity. We allow the user to use their own measurement up to their own
preferences and do not force any fixed unit of measurement. Although similar, Cart and Ingredients differ in certain functions
from a user’s point of view. For a user to immediately sort where they wish to sort the ingredient they are adding, Cart and
3.4.1. Implementation
Below is a step-by-step sequence of what happens when the command cart add ingredient i/INGREDIENT_NAME q/INGREDIENT_QUANTITY is added.
-
The user adds a ingredient to the cart by entering the command
cart add ingredient i/INGREDIENT_NAME q/INGREDIENT_QUANTITYin the command line input. -
CartAddCommandParserparsers the input to check and verify that the input provided fori/INGREDIENT_NAMEamdq/INGREDIENT_QUANTITYare correct. Otherwise aParseExceptionwill be thrown. -
The fields are then passed to
CartAddIngredientCommandas anIngredientobject and is returned toLogicManager. -
LogicManagercallsCartAddIngredientCommand#execute()and checks if theIngredientobject given has the sameINGREDIENT_NAMEandINGREDIENT_QUANTITYunit. If thatIngredientexists, it will simply add on to the quantity of that ingredient. Otherwise, a new instance of thatIngredientwill be added to the Cart. -
If
CommandExceptionis not thrown,Model#addCartIngredientwill be executed, with the givenIngredientas the parameter -
Model#addCartIngredientthen executes, adding theIngredientto the local cart storage and updates withModel#updateFilteredCartIngredientList(). -
A
CommandResultwith the successful text message is returned toLogicManagerand will be displayed to the user via the GUI to feedback to the user that theIngredienthas been successfully added.
The above implementation is the same for Inventory with the command inventory add ingredient i/INGREDIENT_NAME q/INGREDIENT_QUANTITY
The following sequence diagram shows how the function of adding ingredients to cart work (full command omitted for brevity):
3.4.2. Design Considerations
Aspect: The need for many parsers for this command
| Design A (Current choice): Create parsers for every individual action | Design B: Create parsers for each specific action | |
|---|---|---|
Description |
The command will go through the parsers in the following order: |
|
Pros |
More organised and can do more with |
The classes can be more specific and atomic in their actions. |
Cons |
Many parser classes to make and keep track of. |
Might lead to disorganisation during troubleshooting with so many classes to keep track. |
3.5. Remove ingredients of a recipe from the inventory (by Chan Shun Jie)
3.5.1. Implementation
The mechanism is facilitated by InventoryCookCommand, which extends the Command abstract class. The format of the command is as follows: inventory cook recipe INDEX.
This command was implemented to allow users to remove multiple ingredients and their quantities found in a recipe from their inventory.
If the inventory contains an ingredient that has a higher quantity than specified in the selected recipe, its quantity will be subtracted accordingly.
If the ingredient has a lower quantity than specified in the selected recipe or if there is a missing ingredient in the inventory, the feature will not be executed and an error will be thrown.
Without this command, users can only remove ingredients through the inventory remove ingredient command one at a time.
Moreover, they have to constantly cross-check the ingredient quantities in the recipe for accuracy.
Therefore, this command provides convenience after users have prepared a recipe and wish to update their inventory ingredients through a single step.
Below is a step-by-step sequence of what happens when a user enters this command:
-
The user enters an inventory cook command
inventory cook recipe INDEXusing the command line input. -
InventoryCookCommandParserparses the input to check and verify the input provided by the user. If the input provided is invalid, aParseExceptionwill be thrown. -
The valid index is then passed to
InventoryCookCommandas anIndexobject. -
LogicManagercallsInventoryCookCommand#execute()and checks if theIndexprovided is within bounds and if the specifiedRecipecontains ingredients. Otherwise, aCommandExceptionis thrown. -
Subsequently, two checks are performed to check if the inventory contains all of the ingredients specified and whether those quantities are sufficient to be subtracted.
-
If all the checks passed,
model#removeInventoryIngredientis called through astream()to remove the ingredients of a selected recipe from the inventory.selectedRecipe.getIngredients().stream().forEach(model::removeInventoryIngredient); -
A
CommandResultwith a success message is returned toLogicManagerand passed back toMainWindowwhich displays the text to the user through the GUI.
The following sequence diagram shows how the command inventory cook recipe 1 works:
3.5.2. Design considerations
Aspect: Allowing users to execute the inventory cook recipe command when there are missing or insufficient ingredients in the inventory.
Design A: Allow the execution of inventory cook recipe command regardless of missing or insufficient ingredients in the inventory |
Design B (Current choice): Do not allow execution of inventory cook recipe command when there are missing or insufficient ingredients in the inventory |
|
|---|---|---|
Description |
Allow the users to execute the command regardless of missing or insufficient ingredients in the inventory. Missing ingredients will be ignored and ingredients with insufficient quantities will be entirely removed. |
When there are missing or insufficient ingredients in the inventory, the execution of the command will throw an error to warn users whether they have missing ingredients or insufficient ingredients in their inventory. |
Pros |
Straightforward for users to use the command as they do not have to check whether they have all the ingredients in sufficient quantities. |
Enhances user experience. The application can notify users that they have missing or insufficient ingredients when they attempt to prepare a recipe through this command. |
Cons |
Reduces code readability as more methods and steps are needed to check and isolate a list of missing and insufficient ingredients. This list of ingredients are also to be treated differently from the other ingredients when removing from the inventory. |
A potential hassle for users as they have to ensure that all ingredients are present and are sufficient in their inventory to use the command. |
3.6. Move ingredients from cart to inventory (by Tay Zi Hiang Willis)
The user may use this command after their shopping trip. With this one command, all ingredients will be shifted from the cart to the inventory.
This command is implemented to ease the process of having the user adding every single ingredient to their inventory after they have bought ingredients from their cart and eventually deleting the cart after that tedious process. These gives a convenience to users that frequently use our application and we forsee that such an action will be used very often by these users. As this command only performs an atomic action, no extra arguments are needed to further supplement the use of this command.
3.6.1. Implementation
This command is facilitated by CartMoveCommand, which extends the Command class. The format of the command is as follows:
cart move.
Below is a step by step sequence of what happens when the user executes this command.
-
The user enters the command
cart movein to the command line input. -
CartMoveCommandParserthen ensures that the user does not enter any other commands aftercart clear. -
CartMoveCommandParserthen returns aCartMoveCommandand returns it toLogicManager -
LogicManagercallsCartMoveCommand#execute(). If there are other commands aftercart clear, aCommandExceptionwill be thrown. -
If
CommandExceptionis not thrown,Model#cartMoveIngredients()will be executed. -
Model#cartMoveIngredients()will move every ingredient from thecartand add it into theinventory -
A
CommandResultwith the success message text will be returned toLogicManager, which will then be passed toMainWindowand will then feedback to the user.
The following sequence diagram shows how this function works (full command omitted for brevity):
3.6.2. Design considerations
Aspect: Allowing users to move some or all ingredients from cart to inventory
| Design A (Current choice): Move all ingredients | Design B: Allow users to move individually or exclude some ingredients when moving | |
|---|---|---|
Description |
There was a consideration to allow the user to move the ingredients by individual ingredients. Eventually the options was not given as we know that typical users will want to move all the ingredients except for individual ingredients. |
The use cases of such an action happening is very little and the user can simply manually remove the few
ingredients they do not wish to add to the inventory after using the |
Pros |
Straightforward to implement |
Lesser implementations, more time to focus on other parts of the project |
Cons |
Lesser functionality to users that really want to only move certain ingredients |
Poorer user experience for users that do not want to move all ingredients from the cart to inventory on a regular basis, |
3.7. Add a Recipe’s Ingredients to Cart (by Ong Han Sheng)
The user may want to buy the required ingredients to cook a certain recipe in the cookbook. This feature allows the user to add a certain recipe’s required ingredients into the cart.
3.7.1. Implementation
The action of adding a recipe’s ingredients to cart mechanism is facilitated by CartAddRecipeIngredientCommand, which
extends the CartAddCommand abstract class. The format is as follows: cart add recipe INDEX.
This command is implemented to ease the tedious process of having the user adding every single ingredient
to their cart when they want to purchase ingredients to cook a certain recipe. This provides convenience to users
that frequently use our application and such process like shopping for a certain recipe’s ingredient is
intuitive to users. Furthermore, this command creates interaction between the Cookbook and Cart which
helps to further integrate the application as an all-in-one application.
Below is a step by step sequence of what happens when a user enters this command:
-
The user enters the command
cart add recipe INDEXin the command line input. -
CartAddRecipeIngredientParserparses the user input and checks if the index provided is an integer. Note that the parser will throw aParseExceptionif the given index is not an integer.try { recipeIndex = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX, CartAddCommand.MESSAGE_USAGE), pe); } -
The index is passed as a parameter for
CartAddRecipeIngredientCommandwhich is returned toLogicManager. -
LogicManagercallsCartAddRecipeIngredientCommand#execute()which checks if the given index is a valid index of a recipe. Note that the command will throw aCommandExceptionif the given index is not valid.if (recipeIndex.getZeroBased() >= model.getCookbook().getRecipeList().size()) { throw new CommandException(String.format(MESSAGE_INVALID_RECIPE_DISPLAYED_INDEX, MESSAGE_USAGE)); } -
If the index is valid, the selected recipe’s ingredients will be added accordingly. This is done through calling
Model#addCartIngredient(), with each ingredient as the parameter. -
Model#addCartIngredientcallsCart#addIngredident()which then adds the ingredient to the cart. If a certain ingredient exists in the cart, adding a ingredient to a cart will increase the quantity instead. Otherwise, a new instance of that ingredient will be added to the cart. After adding an ingredient, the cart will be updated withModel#updateFilteredCartIngredientList(). -
A
CommandResultwith the successful text message is returned toLogicManagerand will be displayed to the user via the GUI to feedback to the user that the selected recipe’s ingredients has been successfully added to the cart.
The following activity diagram shows a possible flow of events for a user using this feature:
The following sequence diagram shows how the function of adding recipe’s ingredients to cart works:
3.7.2. Design considerations
Aspect: Allowing users to add all or some recipe’s ingredients
Design A (current choice): Adding all recipe’s ingredients to the cart |
Design B: Adding only recipe’s ingredients that are missing in the inventory to the cart |
|
Description |
Allows user to add a recipe’s ingredients to the cart for shopping. This design is currently chosen due to ease of implementation and it works for all situations. |
Allows user to add a recipe’s ingredients base on the inventory status. However, there are some situations where this design not does work. One example would be like planning to cook at outside where the inventory status is unknown. |
Pros |
|
|
Cons |
|
|
3.8. Export ingredients in cart to PDF file (by Teo Jun Xiong)
The user may use this command to export the ingredients in their cart to a PDF file, which they can then use as their shopping list at the supermarkets.
3.8.1. Implementation
The cart exporting mechanism is facilitated by CookbookExportCommand, which extends the Command abstract class.
The format is as follows: cart export.
This command was implemented to bridge the (current, v1.4) inadequacy of Cooking Papa, which is that it is not portable (yet). It was still not convenient enough to be able to organize cart ingredients. Evenutally, users had to go outside to the supermarket, and Cooking Papa is a desktop-only application. By allowing users to export the ingredients in their cart to a PDF file, they can then print it out, or transfer it to their mobile devices, and bring them along as shopping lists. Additionally, the layout and content of the generated PDF file is simple, informational, and easy for users to extend, allowing them to add (handwritten or annotated) remarks.
Below is a step by step sequence of what happens when a user enters this command:
-
The user enters a cart export command using the command line input
cart export. -
CookingPapaParserparses the user input and checks if it is valid. If it is invalid, i.e. an unknown command category, aParseExceptionwill be thrown. If the input is valid, with the command categorycart, a newCartCommandParseris created. -
CartCommandParserthen parsesexport. If it is invalid, i.e. an unknown command word, aParseExceptionwill be thrown. If the input is valid, with the command categoryexport, a newCookbookExportCommandParseris created. -
CartExportCommandParserparses the user input and checks if the argument passed to it is an empty String, as the command takes in no extra parameters.Note that if the String is not empty, a
ParseExceptionwill be thrown:if (userInput.isEmpty()) { return new CartExportCommand(); } else { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CartExportCommand.MESSAGE_USAGE)); }This means that
cart export ingredientwill not work. -
CartExportCommandParserthen returns aCartExportCommandtoLogicManager. -
LogicManagercallsCartExportCommand#execute()calls the static method ofPdfExporter,PdfExporter#exportCart(), which takes in theObservableList<Ingredient>stored inCart -
Step 4 is executed within a try-catch block. If a previously generated pdf (saved as
cart.pdfby default) is opened in another program, or there is an issue writing to the PDF file, aCommandResultwith an error message will returned toLogicManager(skipping step 7 and 8):try { PdfExporter.exportCart(model.getCart().getIngredientList()); } catch (IOException e) { return new CommandResult(MESSAGE_FILE_NOT_FOUND); } -
The ingredients in the
Cartis passed to the static methodPdfExporter#exportCart(), which then makes use of the library,PDFbox, to parse the data. -
Within
PdfExporter,PdfExporter#getTextFromCartparse the data and splits them manually, in order to wrap the text (this has to be done due to the inadequacy ofPDFbox). The method returns aList<String>, where each string represents a new line on the PDF file. -
Subsequently,
PdfExporterchecks if the number ofStrings in the list in step 7 is greater than the number of lines a single page of the PDF can accomodate. If it is, it adds a new page, and adds lines to the PDF until the limit is hit. This repeats until all the lines are added to the PDF. -
A
CommandResultwith the text to display to the user will be returned toLogicManager. TheCommandResultis then passed back toMainWindow, which displays it to the user on the CLI and GUI:resultDisplay .setFeedbackToUser(commandResult.getFeedbackToUser()). The text displayed will notify the user on whether their addition was successful.
The following sequence diagram shows how the function of exporting ingredients in the cart to a PDF file works:
The following activity diagram shows a possible flow of events for a user using this command: .Activity diagram for CartExportCommand image::CartExportActivityDiagram.png[]
3.8.2. Design considerations
Aspect 1: File format to export ingredients in cart to
Design A (current choice): .pdf |
Design B: .txt |
|
Description |
Exports it to a flexible pdf file |
Exports it to a txt file |
Pros |
|
|
Cons |
|
|
Aspect 2: What information to export
Design A (current choice): Export the ingredient names and quantities in the cart |
Design B: Export the entirety of Cooking Papa (cookbook, inventory, cart) |
|
Description |
Allow exporting of just the cart |
Allow exporting of the cart, inventory, and cookbook |
Pros |
Figure 21. A sample shopping list generated by the command
|
|
Cons |
|
|
3.9. Configuration
Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).
4. Documentation
Refer to the guide here.
5. Testing
Refer to the guide here.
6. Dev Ops
Refer to the guide here.
Appendix A: Product Scope
Target user profile:
-
has a need to manage a significant number of recipes
-
has a need to manage food resources efficiently
-
prefer desktop apps over other types of apps
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
Value proposition: manage recipes and food resources faster than a typical mouse/GUI driven app
Appendix B: User Stories
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *
| Priority | As a … | I want to … | So that … |
|---|---|---|---|
|
beginner cook |
find new recipes easily |
I don’t waste time searching though recipes from different sources |
|
regular cook |
record my own recipes |
I can refer to them easily in future |
|
forgetful person |
add ingredients for my planned meals to a grocery list easily |
I know what I need to get when shopping |
|
disorganized person |
keep track of the ingredients I have at home |
I can plan my meals better |
|
busy student |
cook a meal with the ingredients I already have |
I don’t waste time on grocery shopping |
|
low-income individual |
cook a meal with the ingredients I already have |
I can save money |
|
person with food allergies |
cook meals that I am not allergic to |
I do not have an allergic reaction |
|
regular cook |
edit recipes |
I can tweak a recipe to my liking |
|
regular cook |
set a timer during meal preparation |
I can control the quality of my meal |
|
CS student |
cook a quick meal |
I can spend more doing CS2103T |
|
vegetarian |
find recipes that don’t contain meat |
I can keep to my diet constraints |
|
picky eater |
choose recipes that only contain the food I like |
I can enjoy the meals I cook |
|
working adult |
plan meals for the next week |
I can buy all the ingredients I need in one trip |
|
person with health issues |
record the meals I eat |
I can share the information with my doctor easily |
|
health-conscious person |
keep track of the nutritional value of the food I eat |
I can meet my nutritional goals |
|
regular gym-goer |
keep track of my dietary intake |
I can meet my fitness goals |
|
obesity fighter |
keep track of my calorie and fat intake |
I can lose weight |
|
stay-at-home parent |
plan a variety of meals for the week |
I can make sure that my family eats healthily |
|
kiasu parent |
know how much ingredients I need for 2 weeks |
ensure my family never runs out of food |
|
party host |
scale recipe ingredients by the number of servings |
I can prepare meals for large groups |
|
cafe manager |
keep track of the expiry dates of my ingredients |
I know what ingredients I need to stock up on |
Appendix C: Use Cases
(For all use cases below, the System is Cooking Papa and the Actor is the user, unless specified otherwise)
Use case: UC01 - Create a recipe
MSS:
1. User chooses to create a recipe.
2. Cooking Papa requests for details of the recipe.
3. User enters the requested details.
4. Cooking Papa creates the recipe and stores it in the cookbook, and displays the newly created recipe.
Use case ends.
Extensions:
3a. Cooking Papa detects an error in the entered data.
3a1. Cooking Papa shows an error message.
3a2. Cooking Papa requests for the correct data.
3a3. User enters new data.
Steps 3a1 to 3a3 are repeated until the data entered is correct.
Use case resumes from step 4.
*a. At any time, User chooses to end the creation of a recipe.
*a1. Cooking Papa cancels creation of a recipe.
Use Case: UC02 - Search for recipes MSS: 1. User chooses to search recipes. 2. Cooking Papa requests for the tag to be searched. 3. User enters the tag. 4. Cooking Papa displays recipes with the corresponding tag. Use case ends.
Use Case: UC03 - View a recipe
MSS:
1. User chooses to view recipes.
2. Cooking Papa requests for the index of the recipe.
3. User enters the requested index.
4. Cooking Papa displays the entire recipe with the corresponding index.
Use case ends.
Extensions:
3a. The given index is invalid.
3a1. Cooking Papa shows an error message.
3a2. Cooking Papa requests for the correct index.
3a3. User enters the new index.
Steps 3a1-3a3 are repeated until the index entered is valid.
Use case resumes from step 4.
Use case: UC04 - Add a recipe's ingredients to the cart
MSS:
1. User chooses to add a recipe's ingredients to the cart.
2. Cooking Papa requests for the index of the recipe.
3. User enters the requested index.
4. Cooking Papa add the ingredients to the cart.
Use case ends.
Extensions:
3a. The given index is invalid.
3a1. Cooking Papa shows an error message.
3a2. Cooking Papa requests for the correct index.
3a3. User enters the new index.
Steps 3a1-3a3 are repeated until the index entered is valid.
Use case resumes from step 4.
Appendix D: Non Functional Requirements
-
Should work on any mainstream OS as long as it has Java
11or above installed. -
Should be able to hold up to 500 recipes without a noticeable sluggishness in performance for typical usage.
-
A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
Appendix F: Instructions for Manual Testing
Given below are instructions to test the app manually.
| These instructions only provide a starting point for testers to work on, and are in no way exhaustive. |
Below are some test inputs for manual testing, please note that these test inputs are only valid for the sample
cookbook, cart, and inventory data, i.e. the data that is present when Cooking Papa is opened for the first time. If
the data has been modified prior to using these commands, please delete the .json files in /data (cookbook
.json, inventory.json, cart.json).
F.1. Launch and Shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI with a set of sample cookbook, inventory, and cart.
-
F.2. Adding a recipe to the cookbook
Please note that these cases are to be tested individually, i.e. should test case
a be executed, executing test case e will not be valid as there is already an existing recipe with the recipe name
"Name". In such cases, please remove the existing recipe in the cookbook using cookbook remove recipe INDEX.
-
Prerequisites: List all recipes in the cookbook using the
cookbook listcommand, and using the sample cookbook. -
Test case:
cookbook add recipe n/Name d/Description i/Ingredient q/1 s/Step 1 t/Tag
Expected: a new recipe is added to the cookbook, and displayed as the index 3 (one-based index) in the cookbook panel. -
Test case:
cookbook add recipe n/Name d/Description i/Ingredient q/1 s/Step 1 t/Tag(a duplicate recipe)
Expected: no recipe will be added, and an error message indicating that there is already an existing recipe with the same name in the cookbook will be displayed. -
Test case:
cookbook add recipe n/Name d/Description i/Ingredient q/1 s/Step 1 s/Step 1 t/Tag(a recipe with duplicated steps)
Expected: no recipe will be added, and an error message indicating that there is a duplicate step in the command will be displayed. -
Test case:
cookbook add recipe n/Name d/Description i/Ingredient q/1 i/Ingredient q/1 s/Step 1t/Tag(a recipe with duplicated ingredients)
Expected: a new recipe is added to the cookbook, with the duplicate ingredients being added to one another. The new recipe will be displayed as the index 3 (one-based index) in the cookbook panel.
F.3. Remove a recipe from the cookbook
Please note that these test cases are to be tested individually, i.e. should test case a be executed, executing test
case a again will remove a different recipe from the cookbook. In this case, after executing test case a once, to
execute it again, please add back the removed recipe using cookbook add recipe … or by deleting the .json files.
-
Prerequisites: List all recipes in the cookbook using the
cookbook listcommand, and using the sample cookbook. -
Test case:
cookbook remove recipe 1
Expected: a recipe (Aglio Olio) will be removed from the cookbook. -
Test case:
cookbook remove recipe 0andcookbook remove recipe 5
Expected: since the indices in the recipe panel are one-based, i.e. starting from 1, the former command is out-of-bounds; the latter command is out-of-bounds because there are only 4 recipes in the cookbook. Both commands will show an error message reflecting the invalid recipe indices provided.
F.4. Search for recipes by tags
Please note for this search command, with more tags being included, the number of results returned will be greater, i .e. if there are three tags included, the recipes returned do not have to be tagged with all three tags.
-
Prerequisites: List all recipes in the cookbook using the
cookbook listcommand, and using the sample cookbook. -
Test case:
cookbook search tag t/Simple
Expected: the recipe panel will be updated to show only two recipes, both which are tagged with "Simple". -
Test case:
cookbook search tag t/Simple t/CelebrityExpected: the recipe panel will be updated to show only three recipes, of these three recipes, they are either tagged with "Simple" or "Celebrity".
F.5. Add an ingredient to a recipe
Please note that the results of these test cases are based on the sample cookbook. The tests can be performed in any order.
-
Prerequisites: List all recipes in the cookbook with the
cookbook listcommand, and view all the ingredients in recipe 1 with thecookbook view recipe INDEXcommand, using the sample cookbook. -
Test case:
recipe 1 add ingredient i/Olive Oil q/1 tbsp
Expected: 1 tbsp of olive oil is added to the recipe, and the ingredient quantity of olive oil is changed from 1 to 2 tbsp. -
Test case:
recipe 1 add ingredient i/Olive Oil q/1 tsp(an ingredient with incompatible units)
Expected: the specified ingredient will not be added, and an error message indicating that the existing ingredient in the recipe has a different unit will be displayed. -
Test case:
recipe 1 add ingredient i/Ingredient q/1(a new ingredient)
Expected: the specified ingredient is added to the recipe.
F.6. Removing an ingredient from a recipe
Please note that the results of these test cases are based on the sample cookbook. The tests can be performed in any order.
-
Prerequisites: List all recipes in the cookbook with the
cookbook listcommand, and view all the ingredients in recipe 1 with thecookbook view recipe INDEXcommand, using the sample cookbook. -
Test case:
recipe 1 remove ingredient i/Olive Oil q/1/2 tbsp
Expected: 1/2 tbsp of olive oil is removed from the recipe, and the ingredient quantity of olive oil is changed from 1 to 1/2 tbsp. -
Test case:
recipe 1 remove ingredient i/Olive Oil q/1 tsp(an ingredient with incompatible units)
Expected: the specified ingredient will not be removed, and an error message is shown indicating that the existing ingredient in the recipe has a different unit will be displayed. -
Test case:
recipe 1 remove ingredient i/Ingredient q/1(an ingredient that is not in the recipe)
Expected: the ingredient is not removed, and an error message is shown indicating that the ingredient is not in the recipe -
Test case:
recipe 1 remove ingredient i/Olive Oil q/10 tbsp(ingredient quantity too high)
Expected: the ingredient is not removed, and an error message is shown indicating that the specified quantity is greater than that in the recipe -
Test case:
recipe 1 remove ingredient i/Cheese(no quantity specified)
Expected: cheese is removed from the recipe entirely, and the recipe no longer contains any cheese
F.7. Export the cart to a PDF file
Please note that for the export command, the result is based on the sample cart.
a. Prerequisite: have the sample cart data in cart.json, if the file has been modified, please exit Cooking Papa,
and delete it in /data, and run Cooking Papa again.
-
Test case:
cart exportExpected: a PDF file will be created in the same folder as Cooking Papa, and the content should look like:
Figure 22. Content of cart.pdf created from sample cart data -
Test case:
cart export, with a previously createdcart.pdfopen in a program Expected: an error will be thrown, asPdfExporteris unable to modify a file that is currently open in another program. Closing the file and executing the command will return the same result (assuming the cart data is the same as the sample cart data) as test case a.
Appendix G: Effort
Achievements/ challenges |
Effort required |
Difficulty level (out of |
Greater number of entities than AB3 |
As AB3 only had one overarching entity ( |
|
Development of the GUI |
As the team had not much experience with regards to CSS and JavaFX, it took awhile to get rolling and adapt the aesthetics to Cooking Papa’s needs. Moreover, one challenge faced was ensuring that the GUI ran as expected on Windows, MacOS, and Linux. Additionally, the use of SceneBuilder was encouraged, however, it led to many unintended changes and extra variables which made troubleshooting a lot more complex (especially to a novice). |
|
Integrating |
We wanted to make the command more of a toggle instead
of something users had to type, as it was not intuitive. While implementing the button was rather trivial, one
requirement of the app was that it had to be testable via the command line. Connecting the command from the command
line ( In hindsight, perhaps greater experience with GUIs would have made this process easier, but our team were all novices in that aspect, and being able to pull this off, especially when we could have simply left it as the status quo, is a huge achievement. |
|
Refactoring |
As the original PDF library used (iTextPDF) has not permitted due to its license, the whole code had to be refactored to use the current PDF library (Apache PDFbox). The challenge was the lack of features in PDFbox, i.e. tables were not a feature, and had to be drawn using lines. This was a huge hinder in achieving the intended output PDF file. Eventually, it was decided to simply create a list in the PDF instead of a table due to the lack of time, and the payoff for tinkering with PDFbox was not worth the effort. |
|
