PROJECT: HealthyBaby


Overview

HealthyBaby (HYBB) is a desktop application targeted towards university students that provides users with a database of quick, easy, and healthy recipes. HYBB helps alleviate the challenges faced while maintaining healthy eating habits, easing the transition from eating out everyday to cooking healthy meals regularly.

The user interacts with HYBB using a Command Line Interface, and it has a Graphical User Interface created with JavaFX. It is written in Java, and has about 18 kLoC.

Summary of contributions

  • Major enhancement 1: Added the undo and redo feature

    • What it does: It allows the user to undo the actions of previous commands. The user may choose undo one command at a time, undo multiple commands at a time, or even undo all previous commands at once. Undo commands can be reversed by using the redo command (with equal robustness).

    • Justification: This feature vastly improves the user-friendliness of the app as the user can now conveniently rectify any mistakes made.

    • Highlights: HYBB maintains 4 different "books" (databases) and this feature involves keeping track of the changes made to these 4 books. Since, most commands will make changes to at least one of these books when executed, designing and implementing this feature was meaningful and challenging as it required a deep understanding of how each command affects the 4 books. Essentially, this feature affects most of the existing commands and commands that are added in future!

      Changes to existing commands had to made, and more data structures and tracking mechanisms had to be added on to make undo/redo compatible with all the commands in HYBB. Though inspiration was intially taken from the proposed undo/redo feature in AB3, the final implementation of undo/redo in HYBB is significantly different to suit our needs.

    • Credits: As mentioned above, this was inspired by the proposed undo/redo feature in AB3.

  • Major enhancement 2: Added recipe customisation commands (addingredient, editingredient, deleteingredient, addstep, editstep, editingredient)

    • What they do: These commands allow the user to edit the ingredients or steps of a recipe without having to rewrite the entire field.

    • Justification: The edit command inherited from AB3 was limited as it could only edit entire fields at once. These commands vastly improve the user-friendliness of the app as the user can now make small changes without having to rewrite the entire field (imagine if the recipe has 10 steps but the user only wants to edit 1 of them!)

    • Highlights: Implementing these commands required a good understanding of how recipes are created and edited, and how recipes interact with the RecipeBook (the database for all recipes) and the Model.

  • Major enhancement 3: Added the filter feature

    • What it does: It allows users to search for recipes using various criteria such as prep time, ingredients used, or by the goals tagged to the recipe.

    • Justification: The find command inherited from AB3 was limited as it could only find recipes by name. This means the user will have to roughly know a recipe’s name beforehand. Not only is this inconvenient, but it doesn’t tell the user much about the recipe. The filter feature allows the user to find recipes with much more ease and makes the search more targeted. E.g. people with allergies can find recipes without allergens, people with no time can find recipes with short prep time etc.

    • Highlights: Implementing this feature required a good understanding of how recipes are filtered (using predicates). It was challenging because the construction of the predicate had to be precise and accurate, otherwise the wrong recipes would be displayed to the user.

  • Minor enhancement: Added the ability to favourite and unfavourite recipes

    • What it does: It allows the user to mark a recipe as "favourite" or unmark a favourited recipe.

    • Justification: It is observed that humans are habitual creatures and will tend to eat the same things. Hence this minor feature is to complement the filter feature, allowing the user to find all his favourites with one simple line: filter favourites

    • Highlights: Implementing this feature required a good understanding of how recipes are edited. This understanding provided a good foundation for the implementation of the recipe customisation commands (which were implemented after this feature). It also required a good understanding of how the UI components work with each other as I had to display a star beside the recipe name if it’s a favourite.

  • Code contributed: [RepoSense Report for Brian Pek]

  • Other contributions:

    • Enhancements to existing features:

      • Enhanced the find command to have 2 methods of searching with 2 corresponding levels of strictness (Pull request #159)

      • Enhanced ParserUtil to be able to parse multiple indexes (instead of just one) (Pull request #68). Initially, this was an enhancement for delete so that the user can delete multiple recipes at once, but subsequently, it was taken up by other commands!

    • Community:

      • PRs reviewed (with non-trivial review comments): #23, #95, #105, #164

      • The multiple index input parsing enhancement was adopted by several other teammates (Pull request #107, #176)

Contributions to the User Guide

The following section is a portion of my contributions to the User Guide. They showcase my ability to write documentation targeting end-users.

Recipe Customisation Commands (Brian)

As we see from the edit command in section 6.6, if you want to add, edit, or delete a single ingredient or step, you would have to rewrite the whole field that you wish to edit. This would be troublesome if the field contains multiple ingredients or steps that you may not necessarily want to edit. Therefore, the following commands are used in occasions like these!

Add Ingredients to a Recipe: addIngredient (Brian)

Adds more ingredients to an existing recipe.
Format: addIngredient [recipe index] [<ig/grain>…​ <iv/vegetable>…​ <ip/protein>…​ <if/fruit>…​ <io/other>…​]

Example: addIngredient 2 ig/50g, Bread io/5g, Butter
Adds 50g of Bread and 5g of Butter to recipe 2.

You can add multiple ingredients at a time (at least one ingredient must be added).
If you add an ingredient that already exists in the recipe, that existing ingredient will be replaced with the new one.

Edit Ingredient Quantity in a Recipe: editIngredient (Brian)

Edits the quantity of an ingredient in an existing recipe.
Format: editIngredient [recipe index] [<ig/grain>…​ <iv/vegetable>…​ <ip/protein>…​ <if/fruit>… <io/other>…​]

Example: editIngredient 3 ig/50g, Bread
Searches for Bread in recipe 3 and changes its quantity to 50g. An error message will appear if Bread does not exist in recipe 3’s ingredients set.

You can edit multiple ingredients at a time (at least one ingredient must be edited).

Delete Ingredients in a Recipe: deleteIngredient (Brian)

Deletes the specified ingredient(s) from an existing recipe.
Format: deleteIngredient [recipe index] [<ig/grain name>…​ <iv/vegetable name>…​ <ip/protein name>…​ <if/fruit name>…​ <io/other name>…​]

Example: deleteIngredient 3 ig/Rice iv/Kailan
Searches for Rice and Kailan in recipe 3 and deletes them. An error message will appear if Rice and/or Kailan does not exist in recipe 3’s ingredients set.

You can delete multiple ingredients at a time (at least one ingredient must be deleted).
There is no need to specify quantity here. Just the ingredient name will do!
If you type an ingredient prefix (e.g. "ig/") but don’t specify any ingredients, all ingredients of that type will be deleted.

Add Steps to a Recipe: addStep (Brian)

Adds more steps to an existing recipe.
Format: addStep [recipe index] [s/step] <s/next step>…​

Example: addStep 1 s/New step s/Another new step
Adds 2 new steps to recipe 1.

You can add multiple steps at a time (at least one step must be added).

Edit a Step in a Recipe: editStep (Brian)

Edits the specified step in an existing recipe.
Format: editStep [recipe index] [step index] [s/new step]

Example: editStep 3 4 s/Edited new step
Replaces step 4 of recipe 3 with “Edited new step”. If you specify more than one step, only the first one will be used to replace the old step.

Delete Steps in a Recipe: deleteStep (Brian)

Deletes the specified step(s) from an existing recipe.
Format: deleteStep [recipe index] [step index] <step index>…​

Example: deleteStep 3 2 3 5
Deletes steps 2, 3, and 5 of recipe 3.

You can delete multiple steps at a time (at least one step must be deleted).

Filter Command (Brian)

This command is not to be confused with the Find command, which only searches for recipes by their names. The Filter command is a more robust search command that allows you to search for recipes using various criteria.

You can combine the input of the next few subsections to filter the recipes by multiple criteria!

Filter by ingredients (Brian)

Finds recipes that contains the specified ingredients.
Format: filter <ig/grain>…​ <iv/vegetable>…​ <ip/protein>…​ <if/fruit>…​ <io/other>…​

Example 1: filter ig/Rice iv/Cabbage
Finds recipes that contains Rice and Cabbage.

Example 2: filter ig/exclude Pasta ip/Chicken
Finds recipes that does not contain Pasta and contains Chicken.

Notice the use of the keyword "exclude" in Example 2? Use this to exclude ingredients that you do not want!

Filter by goals (Brian)

Finds recipes that are tagged with the specified goal.
Format: filter [g/goal] <g/goal>…​

Example: filter g/Herbivore
Finds recipes that are tagged with the Herbivore goal.

Filter by favourites (Brian)

Finds recipes that are tagged as favourites.
Format: filter favourites

Filter by preparation time (Brian)

Finds recipes that have preparation time less than or equals to the specified time (in minutes).
Format: filter [t/time] or [t/time range]

Example 1: filter t/15
Finds recipes that have 15 minutes or less of preparation time.

Example 2: filter t/20-30
Finds recipes that have 20 to 30 minutes (inclusive) of preparation time.

Contributions to the Developer Guide

The following section is a portion of my contributions to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.

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 1. 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 2. Class diagram for MultipleBookStateManager

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

UndoRedoState0
Figure 3. When the app is first opened
UndoRedoState1
Figure 4. When "delete 5" is called, the state of RecipeBook is changed. This new state is added to ArrayList<RecipeBook>.
UndoRedoState2
Figure 5. When "favourite 3" is called, the state of RecipeBook is changed. This new state is added to ArrayList<RecipeBook>.
UndoRedoState3
Figure 6. 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 7. 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 8. 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).

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.

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 9. General activity diagram for recipe customisation commands