end By: Team HYBB      Since: Mar 2020      Licence: MIT

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the Using PlantUML guide to learn how to create and edit diagrams.

Main has two classes called Main and MainApp. It is responsible for,

  • 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.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

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.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

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 delete 1.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, RecipeListPanel, StatusBarFooter etc. 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 Logic component.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

2.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the RecipeBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a recipe).

  4. The result of the command execution is encapsulated as a CommandResult object which is passed git back to the Ui.

  5. In addition, the CommandResult object can also instruct the Ui to 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("delete 1") API call.

DeleteSequenceDiagram
Figure 6. Interactions Inside the Logic Component for the delete 1 Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

2.4. Model component

ModelClassDiagram
Figure 7. Simplified Structure of the Model Component

API : Model.java

The diagram above has been simplified in order to provide a clearer Overview of the Model component.
The Model consists of 4 main sections: recipe, plan, record and quote.
For more details on each of the main sections of the Model, please refer to the corresponding models illustrated in the next few sections of this document.

The Model component stores a,

  • UserPref object that represents the user’s preferences.

  • RecipeBook object that stores all recipes.

  • PlannedBook object that stores all plans.

  • CookedRecordBook object that stores the records of all the cooked recipes.

  • QuoteBook object that stores all quotes.

It also exposes five unmodifiable lists that can be 'observed' by the UI:

  • ObservableList<Recipe>

  • ObservableList<Plan>

  • ObservableList<Record>

  • ObservableList<GoalCount>

  • ObservableList<Quote>
    The UI can be bound to these lists so that the UI automatically updates when the data in the list changes.

The Model does not depend on any of the other three components.

2.4.1. Recipe Model

ModelRecipeClassDiagram
Figure 8. Structure of the Recipe Model

The Recipe Model stores the UniqueRecipeList containing all recipes.

Each Recipe consists of,

  • One Name

  • One Time

  • Any number of Step

  • At least one Ingredient

For a more comprehensive description on the structure of a Recipe, please refer to The Anatomy of a Recipe in our User Guide.

2.4.2. Plan Model

ModelPlanClassDiagram
Figure 9. Structure of the Plan Model

The Plan Model stores the,

  • UniquePlannedList which contains all plans

  • PlannedRecipeMap which maintains the mapping from Recipe to all the plans that uses this Recipe

Each Plan consists of,

  • One Date

  • One Recipe

2.4.3. Record Model

ModelRecordClassDiagram
Figure 10. Structure of the Record Model

The Record Model stores the UniqueRecordList which contains all records.

Each Record consists of,

  • One Date

  • One Name from a Recipe

  • One set of Goal list

2.4.4. Quote Model

ModelQuoteClassDiagram
Figure 11. Structure of the Quote Model

The Quote Model stores the UniqueQuoteList which contains all quotes.

Each Quote consists of one Content.

2.5. Storage component

StorageClassDiagram
Figure 12. Structure of the Storage Component

API : Storage.java

In the figure above, we can see that we are maintaining 5 different storages. These storages aim to keep the memory of:

  • UserPrefs

  • RecipeBook

  • PlannedBook

  • CookedRecords

  • QuoteBook

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the HYBB data in json format and read it back.

2.6. Common classes

Classes used by multiple components are in the seedu.recipe.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. Basic commands

3.1.1. Switch command (Harshita)

The switch command is facilitated by the MainWindow, MainTabPanel, SwitchCommandParser and SwitchCommandParser.

The following lists in sequential order the process of how switch behaves with user input.

*The user launches HYBB and the default start tab is set to the recipes tab.

*The user now executes switch planning to view the planning tab.

*LogicManager uses RecipeBookParser#parseCommand() to parse the input from the user upon execution of the switch command.

*RecipeBookParser determines which command is being used and creates SwitchCommandParser to parse the input from the user to obtain the arguments.

*SwitchCommandParser parses the argument and checks its validity. If it is invalid, SwitchCommandParser throws an exception and terminates. Else, it returns a SwitchCommand that contains a Tab.

  • LogicManager uses SwitchCommand#execute() to switch to the planning tab.

  • SwitchCommand returns a CommandResult to the LogicManager with the Tab. LogicManager then returns the CommandResult to MainWindow.

  • MainWindow checks if there is a change in state for Tab and if switching is needed. If there is, MainWindow uses MainWindow#handleSwitchTab() to switch tab. Else, MainWindow does nothing.

The following activity diagram shows the flow of activites from when the switch command is executed.

SwitchActivityDiagram

3.1.2. Edit command (Beatrice)

The edit feature allows users to edit the properties of a Recipe with ease using the edit command.

This feature is facilitated by the EditCommand class.

The following activity diagram illustrates how the EditCommand is used.

EditActivityDiagram
Figure 13. Activity Diagram for edit command
Implementation

This section explains how the edit command is implemented.

  1. User specified arguments are passed to the EditCommandParser and the arguments are broken up by the ArgumentTokenizer and ArgumentMultimap.

  2. The arguments will then be parsed by ParserUtil and passed into EditRecipeDescriptor. An error will be thrown if the inputs were invalid or if no properties of the Recipe were edited.

  3. A new EditCommand object will be created containing the new properties of the Recipe.

  4. EditCommand#execute() will then get the latest list of recipes from Model and obtain the Recipe that is being edited.

  5. This Recipe is passed into EditCommand#createEditedRecipe() which creates a new Recipe with the edited properties.

  6. Model#setRecipe() will then replace the Recipe being edited with the new Recipe and update the list of recipes and plans.

  7. The success message will be returned to the user by the CommandResult.

The following sequence diagram summarizes the steps taken so far:

EditSequenceDiagram
Figure 14. Edit sequence diagram
The lifeline for EditCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The details of some methods, like the the usage of EditRecipeDecriptor, was omitted to reduce clutter in the diagram.

The edited recipe will be updated in both the list of recipes and plans. The following section explains in detail the implementation behind how each list is updated in the RecipeBook and PlannedBook class.

  1. Continuing off from Step 8, ModelManager#setRecipe() will be called to replace the target Recipe with the edited Recipe. (ModelManager implements Model)

  2. The target and edited Recipe is passed into RecipeBook#setRecipe() and UniqueRecipeList#setRecipe(), which will replace the target with the edited Recipe in the recipe list.

  3. The same arguments are then passed into PlannedBook#setRecipe() which will get a list of all the plans that uses the target Recipe from PlannedRecipeMap.

  4. If there are no plans that uses Recipe, the process stops. However if plans exists, the PlannedBook will iterate through each old plan and update each plan.

Step 4. is an example of how the PlannedRecipeMap can be used to ease the cost of updating each plan.

The following sequence diagram summarizes how the Recipe and all its related Plan are updated when the Recipe is edited.

EditModelSequenceDiagram
Figure 15. Edit sequence diagram focusing on Model
Design Considerations
Aspect: How recipes are edited
  • Alternative 1 (current choice): The EditRecipeDescriptor class is used to make sense of user input and mimics the Recipe class with the same properties.

    • Pros: Multiple fields can be edited in one go.

    • Cons: Might make testing harder since there are many properties in a Recipe and an edit command can take on any combination of each property.

  • Alternative 2: Allow each property in the Recipe to have its own edit command.

    • Pros: Implementation of each command will be simpler.

    • Cons: Editing a recipe will be harder and more troublesome for the user.

We decided to stick with alternative 1, which is the implementation inherited by AB3, as we believe that being able to edit multiple fields in one go provides much more versatility and convenience to the user. Additionally, although there are many properties to test, it is still a finite number and testing can be done with adequate time.

3.1.3. Favourite and Unfavourite commands (Brian)

Implementation

For brevity, we will only talk about the favourite command. Note that the unfavourite command is implemented in the same way.

  1. The user input received by FavouriteCommandParser#parse will pass on the user input to ParserUtil#parseMultipleIndex to verify if the indexes keyed in are non-zero, unsigned integers. An error is thrown if any of the indexes do not meet this requirement.

    On top of verifying the validity of the indexes, ParserUtil#parseMultipleIndex will remove any duplicate indexes and sort them. It returns a sorted array of one-based indexes.

  2. A new FavouriteCommand object will be created with the array of indexes and returned to the LogicManager.

  3. The FavouriteCommand#execute method is executed. First, the array of indexes will be checked against the currently displayed recipe list to ensure that there exists a corresponding recipe index. An error will be thrown if a user specified recipe index is out of bounds.

  4. Next, we check if the specified recipe(s) is already a favourite. If it is not a favourite yet, we use an EditRecipeDescriptor to set the recipe’s isFavourite to true.

  5. Finally, we display the names of the recipes that have been newly made favourites, and the names of the recipes that were already favourites.

Here is a sample sequence diagram that shows what happens when the user inputs favourite 3:

FavouriteCommandSequenceDiagram
Figure 16. Favourite command sequence diagram

This operation favourites recipe 2 and 3.

3.1.4. Undo and Redo commands (Brian)

The implementation of undo and redo was adapted from AB3. However, HYBB requires more book-keeping because on top of the RecipeBook, we have a PlannedBook, a CookedRecordBook, and a QuoteBook to keep track of as well.

For brevity, we will only talk about the undo command. Note that the redo command is implemented in the same way.

Implementation (before undo is called)
  1. Whenever a command that changes the state of any of the books (RecipeBook, PlannedBook, CookedRecordBook, or QuoteBook) is called, Model#commitBook is called as well.

  2. Model#commitBook will first purge all redundant states in MultipleBookStateManager (ie. if the user called undo before and is now committing a new book, he will not be able to redo the actions of those undos anymore). This is the behavior that most modern desktop applications like Microsoft Word adopt.

  3. Model#commitBook also saves the CommandType and Tab of the command in 2 separate stacks in MultipleBookStateManager. Finally, it saves the new state of the affected book(s) in an ArrayList of that book type.

Note #1: CommandType tells you which book(s) the command affects, while Tab tells you which tab should be displayed upon execution of the command.

Note #2: All 4 ArrayLists of the 4 book types have a "current pointer" each, which points to the respective states of the books that the Model is currently using (ie. what the user is seeing).

The following diagram summarizes what happens when the user executes a command that changes the state of any book:

CommitActivityDiagram
Figure 17. Activity diagram when a command is executed
Implementation (when undo is called)
  1. Model#canUndo is called to check if there are sufficient actions to be undone. An error is thrown if there are insufficient actions to be undone.

  2. If able to undo, Model#undoBook is called. The CommandType stack is popped to know which book(s) need undoing. At the same time, the "current pointer" of the corresponding book ArrayList(s) is/are shifted backwards. All 4 books in Model are then set to the version of the book that each "current pointer" is pointing to.

This class diagram shows the components of MultipleBookStateManager:

MultipleBookStateManagerClassDiagram
Figure 18. Class diagram for MultipleBookStateManager

The following diagrams show what happens after the execution of various commands:

UndoRedoState0
Figure 19. When the app is first opened
UndoRedoState1
Figure 20. When "delete 5" is called, the state of RecipeBook is changed. This new state is added to ArrayList<RecipeBook>.
UndoRedoState2
Figure 21. When "favourite 3" is called, the state of RecipeBook is changed. This new state is added to ArrayList<RecipeBook>.
UndoRedoState3
Figure 22. When "undo" is called, the current state pointer of ArrayList<RecipeBook> is shifted back and the Model’s RecipeBook is set to this version.
UndoRedoState4
Figure 23. When a command like "list" (that does not change the state of any book) is called, the current state pointer remains where it is (ie. there is no change of states).
UndoRedoState5
Figure 24. If a command like "clear" (that changes the state of a book) is called while the current pointer is not pointing to the latest version, all versions after the current pointer will be purged and the newest version will be added to the ArrayList.
Design Considerations
Aspect: How undo and redo executes

One concern we had while choosing the design of the undo and redo features was the amount of memory that has to be used to keep track of the different states of the 3 books.

On top of the ArrayLists of different book types, we also needed to have 2 additional stacks to keep track of the corresponding CommandType and Tab.

We eventually decided on the current implementation because we do not expect the user to make that many changes to the books in a single session. We also do not expect the size of any book to grow so huge that a single commit would take up all the memory capacity. In other words, we foresee that the "cons" of our current choice will not happen (it would take really abnormal user behavior for it to reach that stage).

  • Alternative 1 (current choice): Saves the entire recipe book.

    • Pros: Easy to implement.

    • Cons: May use up a lot of memory space within a single session 1) if there is a large number of book commits and/or 2) if the magnitude of a single commit is large (ie. the book being committed is huge just by itself).

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory.

    • Cons: Tedious to ensure the correctness of the implementation of each individual command. Furthermore, some commands affect multiple books, making book-keeping even more complicated (and hence, susceptible to error).

3.2. Recipe customisation commands (Brian)

The following commands: addingredient, editingredient, deleteingredient, addstep, editstep, and deletestep were implemented to overcome the limitations of the edit command. These recipe customisation commands allow the user to make targeted changes to the ingredient or step fields instead of having to rewrite the entire field using edit.

3.2.1. Implementation

  1. The commands listed above make use of EditRecipeDescriptor (ERD) to add, edit, or delete ingredients or steps. This is done by comparing the contents of the ERD to the contents of the field to be edited and making the necessary changes described below (note that at this point of time, the ERD is already populated with the user’s input):

    • If the command is addingredient or addstep, the existing ingredients or steps from the recipe will be added to the ERD.

    • If the command is editingredient or editstep, the ERD will be checked against the recipe to see if the ingredients or step exists in the recipe. If it exists, the remaining ingredients or steps that were not changed will be added to the ERD. Otherwise, an exception is thrown.

    • If the command is deleteingredient or deletestep, the ERD will be checked against the recipe to see if the ingredients or step exists in the recipe. If it exists, the ERD will be re-populated with the existing ingredients or steps from the recipe, less the ones that were specified by the user. Otherwise, an exception is thrown.

  2. With the ERD fields set, the specified recipe is edited by EditCommand#createEditedRecipe using the ERD.

  3. Finally, Model#setRecipe will replace the old version of the recipe in RecipeBook with the newly edited one. Model#commitBook will commit the new state of the RecipeBook to the MultipleBookStateManager so that the user will be able to undo this command if he wishes to.

RecipeCustomisationCommandsActivityDiagram
Figure 25. General activity diagram for recipe customisation commands

3.3. Filter command (Brian)

The advanced filter feature uses the filter command to search for recipes according to the set of keywords provided by the user. Think of it as a greatly enhanced and more robust version of the find command, which only allows the user to find recipes by their name.

3.3.1. Implementation

This section explains how the filter command is implemented.

  1. User specified keywords are directed to FilterCommandParser#parse where ArgumentTokenizer and ArgumentMultimap are used to parse the user input. An exception will be thrown if no keywords are specified at all.

  2. The parsed user input is then fed into RecipeMatchesKeywordPredicate where a Predicate, p, is created. This predicate will subsequently be used as the filter to get all recipes that meet the user specified criteria.

  3. A new FilterCommand object will be created with the predicate, p, and be returned to the LogicManager.

  4. The FilterCommand#execute method is executed and Model#updateFilteredRecipeList is called. This tests every recipe in the database against the predicate, p, and updates the filtered recipe list with recipes that meet the user specified criteria.

  5. Once complete, this filtered recipe list is displayed to the user.

Here is a sample sequence diagram that shows what happens when the user inputs filter favourites t/20 ig/Pasta:

AdvancedFilterSequenceDiagram
Figure 26. Advanced filter sequence diagram

This operation displays all recipes that 1) are marked as favourites, 2) take 20 minutes or less to prepare, and 3) contains pasta as an ingredient.

3.3.2. Design Considerations

Aspect: Consistency of user input format

One concern we had while implementing this feature was the sheer number of commands and prefixes that our app had. Eventually, the current implementation was chosen because we didn’t want to define a new format for filter keywords which might potentially confuse our users.

  • Alternative 1 (current choice): Use the existing prefixes and format in the user input.

    • Pros: The existing ArgumentTokenizer and ArgumentMultimap classes already have capabilities to parse user input that is in a certain format. Thus, using the same format saves us time and effort in implementing our own parser. It also spares the user from having to remember multiple formats / keywords.

    • Cons: The user has to be familiar with the prefixes and other special keywords in order to use this feature to its fullest potential.

  • Alternative 2: Define new keywords that the user can use. These keywords could be "more english-like" as opposed to using shortened tags as prefixes.

    • Pros: Easy to remember these keywords since they are more english-like.

    • Cons: We must implement our own parser which is tedious. The user will also have to remember a new set of keywords on top of the existing prefixes. This is double work for the user.

3.4. Plan command (Beatrice)

The plan feature allows users to plan for recipes that they wish to cook at a certain date.
This feature is facilitated by the PlanCommand class.

3.4.1. Implementation

This section explains how the plan command is implemented.

  1. User specified arguments are passed to the PlanCommandParser which uses ArgumentTokenizer and ArgumentMultimap to break up the user input.

  2. The arguments are parsed by ParserUtil and if no invalid inputs were found, a PlanCommand object will be created.

  3. PlanCommand#execute() gets the latest list of recipes from Model.

  4. For every Index, a new Plan object is created and added into the Model. This is done by passing the Plan and the Recipe that is being planned into the UniquePlannedList and PlannedRecipeMap.

  5. The Plan is added to the UniquePlannedList and the Plan is added to the list of plans at the Recipe key in the PlannedRecipeMap.

  6. The success message will be returned to the user by the CommandResult.

The diagram below summarises the steps taken:

PlanningSequenceDiagram
Figure 27. Plan sequence diagram
The lifeline for EditCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

3.4.2. Design Considerations

Aspect: Data structure to support plans
  • Alternative 1 (current choice): Use a UniquePlannedList with an ObservableList to display the list of plans, and an internal PlannedRecipeMap that maintains the mapping between a Recipe and all plans that were made for the recipe.

    • Pros: The UniquePlannedList provides the list of plans and updates the UI for every change in plan. In the background, the PlannedRecipeMap is maintained and used to ease the cost of iterating through an entire list of plans to search for all the plans that uses a specific Recipe.

    • Cons: Performance might not be optimised as UniqueRecipeList still uses a list.

  • Alternative 2: Maintain the recipes and plans in one ObservableMap instead.

    • Pros: Performance will be better by using a Map than a List.

    • Cons: There are no official javafx classes that supports an sorted ObservableMap or a filtered ObservableMap. We will have to write and maintain our own implementation or import from other libraries.

We decided to use alternative 1, as the cons of alternative 2 are too heavy. The plans need to be sorted in a chronological order, and future implementations of the viewWeek and viewMonth command will require the plans to be filtered as well.
Additionally, we would not have enough time in the scope of this project to write a fully functional implementation, and importing from other libraries introduces the risk of running into bugs if the the dependencies were not maintained in the future.

3.5. Goals feature (Harshita)

The main functionalities and commands associated with the entire goals feature are add, addIngredient, edit, editIngredient, cooked, listGoals and 'removeGoals'. Goals are auto-generated and added to a recipe every time add, addIngredient, edit or editIngredient is executed.

3.5.1. Details of Implementation (Auto-generation of goals)

The following sequence diagram shows how goals are generated through the example of addIngredient command execution

AddSequenceDiagram
Figure 28. Add command sequence diagram
AddRefSequenceDiagram
Figure 29. Reference Frame for Add command sequence diagram

A recipe is initially created with an empty goals set from parser and calculateGoals() is then called in the AddCommand.

Each ingredient type that is associated with a goal (Vegetable, Protein, Fruit, Grain) is listed as an enum type in MainIngredientType. This ensures that invalid goals are not created and simplifies the mapping between MainIngredientType and Goal.

The calculation of goals then occurs through looping through each ingredient type and executing the method call to Recipe#calculateIngredientQuantity(). This would obtain the total quantity for each ingredient, firstly by calling Ingredient#getMainIngredientType() to ensure the validity of ingredients beings calculated (e.g. any instance of 'Other' ingredient would throw an InvalidStateException). Secondly, by obtaining the quantity in grams through the method calls to Ingredient#getQuantity()' and 'Quantity#convertToGram().

After the calculation for each main ingredient type is completed, an instance of MainIngredientTypeMagnitude is created. It acts as a container to store the quantities and conduct the checks for whether these quantities meet the minimum quantity requirement for their respective food group. This calculation and checks are done through the method call to MainIngredientTypeMagnitude#getMainTypes() which would then return a set of MainIngredientType that successfully met the minimum requirement.

Lastly, after looping through this set and creating each goal with the mapping from MainIngredientType to Goal done (e.g. MainIngredientType.FRUIT leads to the creation of goal with goal name generated as "Fruity Fiesta"), the goals will be updated in the particular instance of Recipe r and Model#addRecipe(r) would then update RecipeBook in storage.

The immutability of each object is supported to ensure the correctness of undo and redo functionality.

3.5.2. Details of Implementation (Statistics of cooked recipes)

CookedActivityDiagram
Figure 30. Activity diagram when a cooked command is entered

After CookedCommand#execute(model) is called, the series of checks shown in the above diagram is done to determine if the recipe can be marked as cooked. With multiple recipes inputted (eg cooked 1 2 3, the series of checks will loop through for for each recipe.

The checks ensure that all the recipes inputted are valid, else the CookedCommand throws an exception and terminates. If successful, a new Record containing the Name, current Date and set of Goals associated with the recipe is created and Model#addRecord(record) would then update CookedRecordBook in storage.

Furthermore, if the recipe marked as cooked was included in the Planned Recipes for the day, it will be removed from the planned list.

With reference to the structure of the CookedRecords Model,

ModelRecordClassDiagram
Figure 31. Class diagram of Record

We can see above that once a record is added to the UniqueRecordList two Observable lists will be updated for each addition of Record. Firstly, it is the internalRecordsList that stores unique Record. Secondly, based on this list, an internalGoalsTally that stores GoalCount will be updated each time. This GoalCount consists of one of the four main goal and its respective tally and this observable list is what the pie chart will be listening to for updates and will change each time the internalGoalsTally has been updated as well.

Hence the cooked command is essential in not only archiving data, but also giving the user personalised statistics on their overall goal distribution that resembles the Healthy Eating Plate. The immutability of each object is supported to ensure the correctness of undo and redo functionality as well.

3.5.3. Design Considerations

Aspect: How goals are being tagged
  • Alternative 1 (current choice): System generates tags for each recipe based on food algorithm.

    • Pros: Higher accuracy and makes use of inputs of ingredients class.

    • Cons: Would require several criteria checks that may not be intuitive and would require the use of artificial intelligence for the highest accuracy.

  • Alternative 2 : User chooses from 4 given goals and user adds the tags to the recipes.

    • Pros: Easy to implement. User can filter their preferred goals easily.

    • Cons: Is dependant on user’s understanding and not universal understanding of what may be deemed healthy.

Alternative 1 was chosen as standardising the goals give the recipes more meaning, especially when we are able to calculate statistics and present in in an meaningful and appealing way for users when it models the Healthy eating Plate. Furthermore, custom goals would not have checks would not have been implemented. for users to filter preferred recipes, the command favourite as been implemented.

Aspect: How to determine the criteria for each goal
  • Alternative 1 (current choice): Check by quantity

    • Pros: More accurate and can be modelled against ideal ratio of a healthy meal.

    • Cons: Harder to implement as we need to standardise the ingredient measurements, not as intuitive.

  • Alternative 2 : Check by variety

    • Pros: Easy to implement.

    • Cons: Not as accurate as one grain of rice or 1 grape would still be counted as variety despite the small portion.

Alternative 1 was chosen because of its higher accuracy, although conversion between different measurement may be overestimated. The command deleteGoal was then created in order to enable users to delete goals they deem inaccurate.

Aspect: How the check for goal is done
  • Alternative 1 (current choice): Check every time a recipe is added or edited and store this data

    • Pros: More consistent for the user in keeping track of their goals.

    • Cons: Harder to implement as repetitive checks are needed every time ingredients are added or modified. Will be more expensive to calculate with a larger database.

  • Alternative 2 : Calculate the goals for each recipe every time it is retrieved from storage and set in RecipeList.

    • Pros: Easier to implement as only one check is needed for when the recipes are set.

    • Cons: Goals will reset each time the application is open. If goal has been deleted by user with deleteGoal, it will not be updated the next time the user opens the application as the checks will be the same.

Alternative 1 was chosen as it optimizes the function of deleteGoal`, taking user preference into consideration.

Aspect: How to store data for records
  • Alternative 1 (current choice): Store in a json file called records and calculate goal tally the first time it is set and update accordingly

    • Pros: A custom date can be set and it will be easier to iterate through the list to obtain goal tally.

    • Cons: Duplicate data will be stored and is harder to implement.

  • Alternative 2 : Use recipebook and add boolean attribute isCooked

    • Pros: Easy to implement.

    • Cons: Restricted usage, unable to implement date and do statistical analysis for the user.

  • Alternative 3 : Store goal tally in a new json file.

    • Pros: No need to iterate through recipe list each time and would be less expensive with a larger database.

    • Cons: Only contains four values.

Alternative 1 was chosen as it optimizes the functions and uses of a Record and the scale for a personal data base is smaller, storage would not be an issue. Records need to be iterated through when set initially anyway, hence the association between GoalCount and Record makes the tally process more efficient. end::goals[]

3.6. Quote Command (Yue Tong)

The quote command feature uses the quote command for users to input their own quotes to add on to the existing set of quotes that is already in the database. This allows the users to add in customised quotes that would suit their preferance more if the current list of quotes is not to their liking.

The following sequence diagram illustrates how the QuoteCommand is used.

QuoteSequenceDiagram
Figure 32. Quote command sequence diagram
The lifeline for EditCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Undo button currently does not work for adding of quotes as there is no remove function currently implemented for quote ==== Implementation

This section explains how the quote command is implemented.

  1. User specified arguments are passed to the QuoteCommandParser#parse which will then parse the user input to verify if the quote field is empty. An error is thrown if the user had keyed in an empty field for quote.

  2. A new QuoteCommand object will be created with a Content field to store the quote and returned to the LogicManager

  3. The QuoteCommand#execute method is executed and Model#addQuote is called. This attempts to add the quote to the database and verify if any duplicate quote exists in the database via Model#hasQuote. If a duplicate quote is detected in the database, an error will be thrown to indicate that the quote already exists in the database.

  4. Once the quote has been successfully updated to the database, the result window informs the user that the quote has been successfully added.

The following activity diagram further illustrates how the QuoteCommand is used.

QuoteActivityDiagram
Figure 33. Quote command activity diagram

3.6.1. Design Considerations

Aspect: How quote command executes
  • Alternative 1 (current choice): The QuoteCommand only accepts adding of quotes

    • Pros: Maintaining the database for quotes is easier

    • Cons: Users will not be able to remove a quote if it is not to their liking and undo function does not work for quote command

  • Alternative 2: The QuoteCommand accepts both adding and removal of quotes

    • Pros: Users can customize whicher quotes they want to be displayed

    • Cons: If they user deletes the quote that is on display for Quote of the Day in the Achievement tab, this could cause display problems and as UniqueQuotesList is a hidden list, users will have to type out the quote completely similar in order to locate it

3.7. Streak feature(Yue Tong)

The Streak feature mainly deals with keeping track of the cooked meals that the user has logged into the system. The command that Streak is associated with is CookedCommand as every user input of cooked will result in the streak log to be reflected when cooked is executed. The changes are reflected in two main attributes, Current streak and High streak.

The following sequence diagram illustrates how the QuoteCommand is used.

StreakActivityDiagram
Figure 34. Quote command sequence diagram

3.7.1. Implementation

The streak feature uses data from the UniqueRecordList to parse through the recipes that the user has already cooked and extract the dates from these recipes to determine the current streak for the user and the highest streak score the user has attained as of yet.

The UniqueRecordList provides Streak with an observableList so that a listener could be added to it to make sure that the streak always auto-updates whenever a new CookedRecord is added in to the database. Via the addListener, whenever a new record is detected, the streaks are calculated again through parsing of the CookedRecords list and updated in real time in the achievement tab.

3.7.2. Design Considerations

Aspect: How streak is stored
  • Alternative 1 (current choice): Streak is calculated and updated via access to UniqueRecordList

    • Pros: Easier to access CookedRecord list when there is direct access to UniqueRecordList and to update in real time

    • Cons: There is more co-dependency among classes and there may be performance issues if UniqueRecordList is too large

  • Alternative 2: Streak has its own database and json file to keep track of streaks

    • Pros: Lesser time is required to re-calculate streaks every time a cookedRecord is updated as streak can just be added and subtracted from its recorded data in the database

    • Cons: A database section will be dedicated to just storing one number and without access to the UniqueRecordsList it is harder for Streak to be updated realtime when cookedRecord is updated.

Aspect: How streak is calculated
  • Alternative 1 (current choice): Accumulative streaks is calculated based on whether there is a 1-day difference between 2 consecutive cooked recipes

    • Pros: Easier to compute compared to implementing via a midnight deadline basis to calculate accumulative streaks

    • Cons: Streaks are not necessarily accounted for within a 24-hour period

  • Alternative 2: Accumulative streaks is calculated based on a stricter within 24-hour new logged cooked recipes

    • Pros: The accountability is higher for users to actually accomplish their streaks by having to cook recipes within a 24-hour period and not be able to go for more than a day without cooking new recipes without having their streaks jeopardised.

    • Cons: It is easier for users to lose their streaks and more difficult for users to ascertain when is the deadline to maintain their streaks

3.8. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.9, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

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:

  • university students

  • wishes to lead a healthier lifestyle

  • has trouble thinking about what to cook

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: Focuses on healthy, simple recipes with short cooking time with ingredients filter to minimise food wastage.

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

picky eater

filter food preferences

cook food that matches my taste

* * *

frugal user

easily search for recipes with the ingredients I already have

use up all the food in my fridge

* * *

frequent user

save my favourite recipes

quickly navigate to them without having to search them up again

* * *

goal-oriented student

track my progress

see how far I came and how much further I have to go to reach my goal

* * *

user with many recipes in the recipe book

filter recipes by various criteria

locate a recipe easily

* * *

user with allergies

exclude ingredients that I am allergic to

obtain recipes that are catered to me

* * *

user struggling to eat healthier

receive motivation for eating healthy meals

stay motivated on my goal

* * *

user who cooks regularly

add my own recipes with the goals they fall under

progress in my goals when I cook my own unique meals

* * *

unmotivated user

choose a goal for myself

cook more meals and be motivated by my progress

* * *

unmotivated user

track my streak of healthy meals

motivated to keep eating healthy

* * *

user who loses motivation easily

look at daily quotes to remind myself

remember why I wanted to continue to be healthy

* *

user who dislikes food wastage

see what ingredients I need to buy when I grocery shop

only buy ingredients that I will use

* *

busy student

get a list of the ingredients I need for the week in one go

save time and not make wasted trips

* *

busy student

pre-select meals for certain days

save time from ruminating over what to cook

* *

bodybuilder

search for protein-rich recipes specifically

build my muscles

* *

busy student

filter recipes by preparation time

choose meals that can be done quickly

* *

avid planner

choose recipes and place them in a timetable for the week

plan my meals beforehand

*

novice cook

filter recipes by difficulty level

select easier recipes

*

user who is passionate about cooking

share the recipes on social media

show my friends what I have cooked today

*

adventurous user

filter the recipes by cuisine

try a new cuisine every time

*

user who prefers hard-copy materials

save my favourite recipes locally

print them out

*

frequent party host

scale up the amount of ingredients needed

make the correct amount of food

*

student on budget

choose recipes that require lower cost

save money

*

adventurous user

ask for suggested recipes

choose a random recipe and start cooking

*

adventurous user

mix up recipes

try something completely new

*

motivational user

add custom quotes to app online

motivate other users with different quotes

Appendix C: Use Cases

(For all use cases below, the System is HealthyBaby and the Actor is the user, unless specified otherwise)

Use case: Add recipe

MSS

  1. User requests to add recipe

  2. HealthyBaby creates a new recipe with the specified name

    Use case ends.

Extensions

  • 2a. The name/time/ingredients fields are empty.

    • 2a1. HealthyBaby shows an error message.

      Use case resumes at step 1.

  • 2b. The given name already exists.

    • 2b1. HealthyBaby shows an error message.

      Use case resumes at step 1.

  • 2c. The user tries to add goals that do not exist in the goals list.

    • 2c1. HealthyBaby shows an error message.

      Use case resumes at step 1.

Use case: Delete recipe

MSS

  1. User requests to list recipes

  2. HealthyBaby shows a list of recipes

  3. User requests to delete a specific recipe in the list

  4. HealthyBaby deletes the recipe

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. RecipeBook shows an error message.

      Use case resumes at step 2.

Use case: Find recipe

MSS

  1. User enters desired recipe name into CLI

  2. HealthyBaby shows the desired recipe

    Use case ends.

Extensions

  • 2a. The desired recipe name does not exist in the list.

    • 2a1. HealthyBaby tells the user that 0 recipes are listed.

      Use case ends.

Use case: Plan for a recipe

MSS

  1. User enters the recipe index and date that they would like to plan for

  2. HealthyBaby adds the new plan to the list of plans

  3. HealthyBaby displays the success message, switches to the planning tab and displays the updated list of plans.

    Use case ends.

Extensions

  • 2a. The given recipe index is invalid.

    • 2a1. HealthyBaby shows an error message.

      Use case ends.

  • 2b. The given date is invalid.

    • 2b1. HealthyBaby shows an error message.

      Use case ends.

  • 2c. A similar plan with the same recipe and date already exists in the list.

    • 2c1. HealthyBaby shows an error message.

      Use case ends.

Use case: Clear entire recipe list

MSS

  1. User enters the clear command

  2. HealthyBaby clears the entire recipe and plans list

    Use case ends.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

  2. Should be able to hold up to 1000 recipes without a noticeable sluggishness in performance for typical usage.

  3. 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.

  4. The software should be portable (i.e. works on and can be moved to different operating systems)

{More to be added}

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Main Nutritional Food Group

HYBB identifies Grain, Vegetable, Protein and Fruit to be the main nutritional food groups.

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; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Deleting a recipe

  1. Deleting a recipe while all recipes are listed and no plans exist

    1. Prerequisites: List all recipes using the list command.
      Multiple recipes in the list must be present. If required, add recipes by using the add command.
      Plan list is empty. If required, clear plans by using the clearPlan command.

    2. Test case: delete 1
      Expected: First recipe is deleted from the list. Name of the deleted recipe shown in the result box.

    3. Test case: delete 0
      Expected: No recipe is deleted. Error details shown in the result box.

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size)
      Expected: Similar to previous.

  2. Deleting a recipe while all recipes are listed and plans exist

    1. Prerequisites: List all recipes using the list command.
      Multiple recipes in the list must be present. If required, add recipes by using the add command.
      Plan list contains a plan for the recipe at recipe index 1. If required, add a plan using the plan command.

    2. Test case: delete 1
      Expected: First recipe is deleted from the list. Name of the deleted recipe shown in the result box. The plan for recipe 1 is removed from the list of plans in the 'Planning' tab.

F.3. Favourite a recipe

  1. Favouriting a recipe while all recipes are listed

    1. Prerequisites: List all recipes using the list command. There must be at least 2 recipes for all the following test cases to work as expected.

    2. Test case: favourite 1
      Expected: A star appears beside the recipe name, indicating that it has been marked as a favourite and a success message appears in the message box.

    3. Test case: favourite 0
      Expected: No recipe is favourited. An error message appears in the message box.

    4. Test case: favourite 1 2
      Expected: A star appears beside the names of recipe 1 and 2, indicating that they have been marked as favourites and a success message appears in the message box.

F.4. Unfavourite a recipe

  1. Unfavouriting a recipe while all recipes are listed

    1. Prerequisites: List all recipes using the list command. Recipe 1 must be favourited as well.

    2. Test case: unfavourite 1
      Expected: The star beside recipe 1 disappears, indicating that it has been removed from favourites and a success message appears in the message box.

    3. Test case: unfavourite 0
      Expected: No recipe is unfavourited. An error message appears in the message box.

F.5. Filtering recipes

  1. Filters recipes that match the given criteria

    1. Prerequisites: List all recipes using the list command. At least one recipe should be favourited and at least one recipe should contain a grain ingredient called "Bread"

    2. Test case: filter favourites
      Expected: Only recipes that are favourites (has a star beside its name) are displayed.

    3. Test case: filter ig/Bread
      Expected: Only recipes that have a grain ingredient called "Bread" are displayed.

F.6. Undo and Redo

  1. Undoes the previous action, followed by restoring the action that was undone

    1. Prerequisites: List all recipes using the list command. Recipe 1 should not be favourited and should not contain a grain ingredient called "Bread"

    2. Test case: favourite 1, followed by undo, followed by redo Expected: A star appears beside recipe 1’s name upon favouriting it, disappears upon undoing, and reappears upon redoing.

    3. Test case: addingredient 1 ig/2g, Bread, followed by undo, followed by redo Expected: A bread ingredient appears under the "Grains" section of recipe 1 upon addingredient, disappears upon undoing, and reappears upon redoing.

F.7. Clearing the recipe list

  1. Clearing the list while plans are present.

    1. Prerequisites: Plan a recipe using the plan command.

    2. Test case: clear
      Expected: All recipes and plans are cleared from the list.

  2. Clearing the list while plans are not present.

    1. Prerequisites: No plans are present. Clear the plans by using the clearPlan command.

    2. Test case: clear
      Expected: All recipes are cleared from the list.

F.8. Adding plans

  1. Adding a plan to an empty plan list.

    1. Prerequisites: No plans are present. If required, clear the plans by using the clearPlan command.
      At least one recipe exist in the recipe list. If required, add recipes by using the add command.

    2. Test case: plan 1 d/2020-05-20
      Expected: Recipe at index 1 is planned on 20 May 2020.

    3. Test case: plan -1 d/2020-05-20
      Expected: Plan is not added. Error details shown in the result box. Other incorrect plan commands to try: plan, plan 0, plan x (where x is larger than the list size)
      Expected: Similar to previous.

    4. Test case: plan 1 d/2019-05-20
      Expected: Plan is not added. Error details shown in the result box.

  2. Adding multiple plans to an empty plan list.

    1. Prerequisites: No plans are present. If required, clear the plans by using the clearPlan command.
      Multiple recipes exist in the recipe list. If required, add recipes by using the add command.

    2. Test case: plan 1 2 3 d/2020-05-20
      Expected: Recipes at indexes 1, 2 and 3 are planned on 20 May 2020.

    3. Test case: plan 1 -2 3 d/2020-05-20
      Expected: No plans are added. Error details shown in the result box.

    4. Test case: plan 1 2 3 d/2019-05-20
      Expected: Plan is not added. Error details shown in the result box.

  3. Adding plan(s) to a plan list that is not empty.

    1. Prerequisites: A plan for the recipe at recipe index 1 exists. If required, plan for the recipe by using the plan command.

    2. Test case: plan 1 d/2020-05-20
      Expected: No plans are added. Duplicate message is shown in the result box.

    3. Test case: plan 1 -2 3 d/2020-05-20

F.9. Deleting plans

  1. Deleting a plan while plans exist.

    1. Prerequisites: Plans are present. If required, add plans by using the plan command.

    2. Test case: deletePlan 1
      Expected: Plan at plan index 1 is deleted.

    3. Test case: deletePlan -1
      Expected: Plan is not deleted. Error details shown in the result box. Other incorrect plan commands to try: deletePlan, deletePlan 0, deletePlan x (where x is larger than the list size)
      Expected: Similar to previous.

  2. Deleting a plan while multiple plans exist.

    1. Prerequisites: More than one plan are present. If required, add plans by using the plan command.

    2. Test case: deletePlan 1 2
      Expected: Plan at plan indexes 1 and 2 are deleted.

    3. Test case: deletePlan 1 -2
      Expected: Plan is not deleted. Error details shown in the result box. Other incorrect plan commands to try: deletePlan 1 0, deletePlan 2 x, (where x is larger than the list size)
      Expected: Similar to previous.

F.10. Clearing plans

  1. Clearing plans while plans exist.

    1. Prerequisites: Plans are present. If required, add plans by using the plan command.

    2. Test case: clearPlan
      Expected: All plans in the plan list are cleared.

F.11. Obtaining grocery list

  1. Obtain grocery list while plans exist.

    1. Prerequisites: Plans are present. If required, add plans by using the plan command.

    2. Test case: groceryList
      Expected: A window appears, listing all the ingredients used for the recipes in the plans.

  2. Obtain grocery list while no plans exist.

    1. Prerequisites: Plan list is empty. If required, clear plans by using the clearPlan command.

    2. Test case: groceryList
      Expected: No window appears. Error details shown in the result box.

  3. Update and show grocery list while grocery list window is minimised.

    1. Prerequisites: Plans are present. If required, add plans by using the plan command. Grocery list window is opened then minimised. If required, open the window first by using the groceryList command, then minimise that window.

    2. Test case: groceryList
      Expected: Window with updated grocery list appears.

F.12. Cooking recipes

  1. Mark a recipe as cooked

    1. Prerequisites: Recipes are present. If required, add recipes by using the add command.

    2. Test case: cooked 1
      Expected: The result box below informs the user that the recipe has been cooked.

  2. Cooking a recipe that has already been cooked.

    1. Prerequisites: Records list contains records cooked within the day.

    2. Test case: cooked 1
      Expected: The result box below informs the user that the recipe is a duplicated and cannot be added in.

  3. Cooking multiple recipes

    1. Prerequisites: Recipes are present. If required, add recipes by using the add command.

    2. Test case: cooked 1 2 3
      Expected: The result box below informs the user that the recipes have been cooked.

F.13. Adding quotes

  1. Quote displays while quotelist is not empty.

    1. Prerequisites: Quotes are present. If required, add quotes by using the quote command.

    2. Test case: quote Today is a good day
      Expected: The result box below informs the user that the quote has been added.

  2. Adding a quote while it already exists in the list

    1. Prerequisites: Quotes list contains quote. If required, add the same quote by using the quote command.

    2. Test case: quote Skip the diet, just eat healthy!
      Expected: The result box below informs the user that the quote is a duplicated and cannot be added in.

{ more test cases …​ }

F.14. Saving data

  1. Dealing with missing data files.

    1. Close the jar file.

    2. Delete one or more files from the data folder.

    3. Double-click the jar file.
      Expected: Deleting the files would result in these corresponding results:

      1. Recipebook: Default recipes in 'Recipes'

      2. Quotebook: Default quotes in 'Achievements'

      3. Plannedbook: An empty plan list in 'Planning'

      4. Cookedrecords: An empty cooked meals list with no pie chart in 'Goals'

  2. Dealing with corrupted data files.

    1. Close the jar file.

    2. Type '~!@' in any of the files in the data folder and save the file.

    3. Double-click the jar file.
      Expected: Adding corrupted data into the files would result in these corresponding results:

      1. Recipebook: An empty recipe list in 'Recipes' as well as an empty plan list in 'Planning'

      2. Quotebook: An empty quote list in 'Quotes' and default quote in 'Achievements'

      3. Plannedbook: An empty plan list in 'Planning'

      4. Cookedrecords: An empty cooked meals list with no pie chard in 'Goals'

{ more test cases …​ }