PROJECT: SousChef


Overview

Souschef is a desktop smart cooking sidekick, offering personalised guidance every step of the way. From recipe recommendations just for you, to meal planning and inventory management, SousChef has everything you need to improve life in the kitchen.

Our users interact via CLI and GUI created with JavaFX. Written in Java, experience what many are enjoying.

Summary of contributions

  • Code contributed: [Functional code]

  • Major enhancement: added the Meal Planner feature.

    • What it does: Allows users to add their desired recipes to a Meal Planner so that they can plan their days' meals.

    • Justification: This feature improves the product significantly because a user can not only browse recipes, but also keep a record of the recipes they want to try on which specific days.

    • Highlights: This enhancement was originally unsupported by the AB4 code; it is a completely new component feature.

  • Major enhancement: restructured and modularised UI component and implemented panel switching between different features.

    • Justification: This was to allow easy and efficient integration for the UI aspect across multiple features through the use of general classes, as well as to allow switching between the display panels of different features.

    • Highlights: This enables both modularised and efficient integration of features into UI and allowed easy switching between different feature panels through the use of Universal Commands.

    • #65, #243

  • Minor enhancement: Implemented cross-model deletion and clearing in order to maintain data consistency across the different models.

    • Justification: There are some scenarios to consider where modifying one model has to affect one or more other models.

      • Recipe that is already planned in the Meal Planner is deleted from the recipe model.
        In this scenario, the deleted recipe will now also be removed from the days in the Meal Planner where it was previously planned, and also from the day planned in the Health Planner.

      • 3 recipes that have been planned in a single day are deleted from the recipe model.
        In this scenario, the day in the Meal Planner which has had all its recipes removed will also be deleted from the Meal Planner. This then cascades on to the Health Planner, and the deleted day will be removed from any health plans where it was previously added to.

      • A day from the Meal Planner that has been added to a health plan in the Health Planner is deleted from the Meal Planner.
        In this scenario, the deleted day will now also be removed from the health plans in the Health Planner where it was previously added.

      • Clearing the Meal Planner while days have been added to health plans in the Health Planner.
        In this scenario, all the health plans in the Health Planner will now have all their previously added days removed.

      • A recipe that is already planned in the Meal Planner is edited.
        In this scenario, the recipe’s changes will now also be reflected in the Meal Planner and also in the Health Planner if the day has been added to a health plan.

    • Highlights: This ensures that the different types of data across the different models in SousChef all remain in synchronization and logically correct.

    • #256, #265

  • Other contributions:

    • General/Team Enhancements:

      • Clean up code by decoupling Storage and Ui components from Logic component (#243 #256).

    • Documentation (see Contributions to Developer Guide for details):

      • Update UI class diagram to reflect new generic classes.

      • Documented and explained modularisation of UI component, including creating class diagrams to show new classes

      • Documented and explained implementation of MealPlanner component.

    • Enhancements to new features:

      • Wrote additional tests for Meal Planner component to increase coverage.

    • Community:

      • PRs reviewed (mostly wth regards to the UI component): #83, #84, #56

      • Reported bugs and suggestions for other teams in the class (examples: 1, 2, 3)

Contributions to the User Guide

Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users.

Meal Planner Commands (Only applicable in meal planner context)

  • DATE should be entered in the format yyyy-mm-dd

  • MEAL can be specified with the following keywords: breakfast, lunch, dinner

Delete recipe: delete

After displaying the meal planner, deletes the specified day.

Format: delete INDEX

Clears all the meal slots of the meal planner.

Format: clear

Select recipe: select

Selects and views the details of a recipe at a specified meal slot of a specified day.

Format: select INDEX MEAL

Find recipe: find

Finds the Day with the specified date.

Format: find DATE

List all recipes: list

Lists all the currently planned Days in the Meal Planner.

Format: list

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project.


Meal Planner feature

Current Implementation

Model

The Meal Planner feature is supported by the classes Meal and Day.

Supporting classes:

  • Day: encapsulates a LocalDate date and an ObservableList<Meal> mealList containing 3 meals (breakfast, lunch, dinner). Day objects are always instantiated with 3 empty Meal objects in the mealList.

    • An empty Day is defined as having 3 empty meals in its mealList.

    • 2 Day objects are defined as same (through overriding equals() method) as long as they have the same LocalDate date. This is to ensure no duplication of dates within the Meal Planner.

  • Meal: abstract class which encapsulates a Optional<Recipe> recipe, an int index indicating which meal index each Meal object is (0 for breakfast, 1 for lunch, 2 for dinner), and a String slot containing its corresponding name.

    • Breakfast: inherits from Meal class and represents the breakfast meal slot of the day. Contains a predefined integer index of 0 and a predefined String slot name of "breakfast".

    • Lunch: inherits from Meal class and represents the lunch meal slot of the day. Contains a predefined integer index of 1 and a predefined String slot name of "lunch".

    • Dinner: inherits from Meal class and represents the dinner meal slot of the day. Contains a predefined integer index of 2 and a predefined String slot name of "dinner".

    • An empty Meal is defined as having no recipe, i.e. empty Optional.

Class diagram:

MealPlannerClassDiagram

Brief explanation:

Meal Planner is currently implemented as a list of non-empty Day objects. This is ensured as Day objects are only instantiated when recipes are added to a certain date, with that date being assigned to its Day object. When all the recipes are deleted from a certain Day, the Day object is removed from the Meal Planner. Day objects in the Meal Planner list panel are sorted in chronological order according to their dates. This is done by sorting the list when instantiated and when Day objects are added or removed.

Logic

For Meal Planner commands to be accessible, context has to be switched to "Meal Planner via the command -mealplanner (except for the case of the plan command for adding recipes to the Meal Planner, which has to be done in the recipe context since the user needs to be able to see the recipes). From there, the Meal Planner commands can be used and AppContentParser will redirect the different commands to their respective CommandParser.

Plan Command: plan

The plan command adds a recipe from the recipeModel to the mealPlannerModel. Therefore both models have to be passed to PlanMealCommandParser.

Within PlanMealCommandParser, it parses the target date from a String into a LocalDate. Only dates from the present or future are allowed to be entered. This LocalDate object is then used to find out whether mealPlannerModel currently contains a Day with the target date. If such a Day object already exists, it is taken from mealPlannerModel to be used in PlanMealCommand. Else, a new Day object is instantiated with the target LocalDate date. The target meal is then taken from the Day object. mealPlannerModel, target day target recipe and target meal are then passed to PlanMealCommand.

Within the execute method of PlanMealCommand, target recipe is then added the target meal and target day is then added to mealPlannerModel.

Finally, the new content is committed via mealPlannerModel.commitAppContent().

Select Command: select

The select command selects a recipe of a specified meal of a specified day and displays the details of the recipe (ingredients, instructions, etc) in the DetailPanel.
MealPlannerParser invokes the parseMealRecipe() command of SelectCommandParser. The desired index and meal index are parsed from the user input and used to extract the recipe from the specified day’s desired meal index. Using this recipe, the recipeModel is searched and the index of the matching recipe is returned.
The index and the recipeModel are then passed to a SelectCommand which executes the displaying of the recipe details in the same manner as in the recipe context.

Clear Command: clear

The clear command simply clears the Meal Planner of all days.
This is done by setting (i.e. replacing) the existing UniqueList of mealPlannerModel with a new empty UniqueList.

Delete Command: delete

The delete command deletes a specified Day from the mealPlannerModel along with all its meals. The Day is specified by its index as seen on the list panel.
MealPlannerParser invokes the parseMealPlan() method of DeleteCommandParser. The user input is then parsed to get the index of the Day to be deleted. The Day object at the given index of the mealPlannerModel list is then deleted.

Alternative implementation

Earlier MealPlanner implementation used a ObservableHashMap with LocalDate and Day as the key and value pairs, each LocalDate being the key for the Day with the same LocalDate. Theoretically, this would work as well. However, this was scrapped as a simple ObservableList of Days would work equally well. In addition, since AB4 already has an implemented UniqueList, using a simple List makes it easier to integrate the Meal Planner into the existing infrastructure.
HashMaps are mainly used as they give a time complexity advantage, having an O(1) time complexity. However, that advantage is not present here since our Lists usually access elements by index, and thus also have a time complexity of O(1).


Meal Planner

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

* * *

user

plan my meals for breakfast, lunch and dinner

I can remember my meals for each day

* * *

fickle-minded user

edit my meal plans

I can add different recipes if I change my mind

* * *

efficient user

clear the meal planner immediately at the end of the week

I can save time

* *

health-conscious user

view the nutritional value of each recipe

I can choose recipes according to my nutritional needs

*

meticulous user

view the time taken for each recipe

I can plan ahead for meal preparations to fit my schedule


Explanation of the modularisation of the UI component

Modularisation

List Panel

Brief overview: Many of the features (such as MealPlanner, HealPlan, IngredientManager, Recipes, Favourites) need to use the list panel section to display their own UniqueType classes. Therefore, this has been abstracted and modularised for easier implementation of usage and for any future implementation of new components which may need to use the list panel. This is done by inserting generic classes into the hierarchy.

Old Hierarchy:

UiPanelOldHierarchy

New Hierachy:

UiPanelNewHierarchy
Not all the components are shown, only MealPlanner and Recipe components are shown in the diagram as an example. Notice how the new generic abstract classes are simply inserted between the superclass and subclass of the old hierarchy to give the new hierarchy.

Explanation:

With the GenericListPanel, GenericCard and GenericPanelSelectionChangedEvent classes, new features which wish to use the UI list panel simply have to have their respective classes extend these three abstract classes and implement their methods accordingly. Then the respective fxml files for [feature]ListPanel and [feature]Card have to be created.

MainWindow.java and ListPanelSwitchEvent.java:

ListPanelSwitchEvent encapsulates an event where the list panel needs to change context. It extends BaseEvent and contains a Context attribute to store information on which context the list panel needs to switch to.
To switch between different list panels for different features, switchTo[feature]ListPanel() methods have to be implemented in MainWindow. This method gets the respective filteredList from the model within modelSet within logic and uses it to change the generalListPanel. The handleListPanelSwitchEvent() method is invoked when a ListPanelSwitchEvent is posted to the EventsCenter, and the method then checks the context attribute of the ListPanelSwitchEvent object to determine which context the list panel needs to switch to, and then calls the correct method.

Usage:

Now in order to switch between the different feature list panels, the developer just has to create their handler method, instantiating a ListPanelSwitchEvent with their desired context and then raising the event by posting it to the EventsCenter.