By: Team F11-1 Since: March 2020 Licence: MIT
Table of Contents:
- 1. About this guide
- 2. Setting up
- 3. Design
- 4. Implementation
- 4.1. Generating consumption statistics and insights report
- 4.2. Setting daily calorie goals for motivation
- 4.3. Searching for specific
Foodvia categories and substrings - 4.4. Lexicographical
Foodorder - 4.5. Exporting the current
FoodRecordinto a portable file - 4.6.
Foodconsumption management - 4.7. Modifying the
FoodRecord - 4.8. Real-time Suggestions for existing
FoodinFoodRecord - 4.9. Command guide
helpcommand - 4.10. Past seven days calorie data graph
- 4.11. Logging
- 4.12. Configuration
- 4.1. Generating consumption statistics and insights report
- 5. Documentation
- 6. Testing
- 7. 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
- F.1. Launch and Shutdown
- F.2. Getting help
- F.3. Creating a
Food - F.4. Editing a
Food - F.5. Deleting a
Food - F.6. Listing all
Foodentries - F.7. Navigating
Food record - F.8. Adding
Foodto consumption record - F.9. Removing a portion of
Foodfrom consumption record - F.10. Navigating
Consumption Record - F.11. Setting a goal
- F.12. Generating report for the day
- F.13. Generating a copy of your
Food Record - F.14. Clearing
Food Recorddata - F.15. Saving data
- Appendix G: Effort
1. About this guide
This Developer Guide is a document to guide future software developers of the Calgo App by providing a sufficient and comprehensible overview of the project.
While we aim to provide a reasonable amount of depth, do keep in mind that the goal of this document is not to serve as a replacement for reading the actual code.
Welcome on-board the Software Development Team for Calgo! Together, we will inspire a healthier lifestyle!
2. Setting up
Refer to the guide here.
3. Design
3.1. Architecture
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.
|
The Architecture Diagram given above describes the high-level design of the Calgo Application. From now on, all instances of Calgo Application will be referred to as App. Given below is a quick overview of each component.
The Main component comprises of two classes called Main and
MainApp.
This component is responsible for:
-
Launching App: Initializes the other components in the correct sequence, and connects them up with each other.
-
Exiting App: Shuts down the components and invokes cleanup method where necessary.
Commons represents a collection of classes used by multiple other components.
In particular, the LogsCenter class plays an important role at the architecture level:
-
LogsCenter: Writes log messages to the App’s log file, for various classes.
The rest of the App comprises of four components.
Each of the four components:
-
Defines its Application Programming Interface (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 its 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 delete n/Apple.
delete n/Apple commandThe sections below give more details of each component.
3.2. UI component
API : Ui.java
The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, FoodListPanel, DailyListPanel, 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
Logiccomponent. -
Listens for changes to
Modeldata so that the UI can be updated with the modified data.
3.3. Logic component
API :
Logic.java
-
Logicuses theCalgoParserclass 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 food). -
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("delete n/Apple") API call.
delete n/Apple Command
The lifeline for DeleteCommandParser and DeleteCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
|
3.4. Model component
API : Model.java
-
Modelstores user’s preferences in aUserPrefobject. -
Modelalso stores Food Record and Consumption Record data. -
This component exposes both
ObservableList<Food>andObservableList<DisplayFood>. The data stored in these two list objects is reflected in UI. Therefore, any changes made to the data in these lists are shown in the UI in real-time. -
To update the
Model(and hence reflect the changes in the UI),Foodattributes need to satisfy certainPredicates, which represent these changes. -
This component does not depend on any of the other three components.
To make Model follow the Object-Oriented Paradigm (OOP) more closely,
we can store a Tag list in Food Record, which Food objects can reference.
This would allow Food Record to only require one Tag object per unique Tag,
instead of each Food needing their own Tag object.
An example of how such a model may look like is given in the below diagram. |
3.5. Storage component
API : Storage.java
The Storage component allows us to save FoodRecord, UserPref, Goal, and ConsumptionRecord data in json format onto the disk, and read them back later on during the next session.
This would facilitate the following functions:
-
Load past user App data and preferences.
-
Generate and save insights reports based on previously and currently recorded user consumption.
-
Generate and save a user-friendly version of the accumulated
FoodRecord.
3.6. Common classes
Classes used by multiple components are in the life.calgo.commons package.
4. Implementation
This section describes some noteworthy details on how certain features are implemented.
4.1. Generating consumption statistics and insights report
(by Vineeth)
This feature allows a user to automatically generate a report that contains statistics and personalised insights based on his or her food consumption pattern on a given date. Do note that the generated report is a .txt file.
The user can invoke this functionality by entering the report command, which follows the following format: report d/DATE.
This section explains:
-
how the
reportcommand works and the crucial method it invokes during execution (In the Implementation subsection). -
the various aspects that were deliberated over when coming up with the design of the statistics and insights report feature (In the Design Considerations subsection).
-
a summary that shows a simplified Activity Diagram that captures the essential logic in the execution of the
reportcommand (In the Summary subsection).
Before moving on to learn how the feature works, if you want to see what the report includes, you can refer to our User Guide.
4.1.1. Implementation
The specified feature is facilitated by a ReportGenerator object. If you are interested in how ReportGenerator fits into the architecture of Calgo,
refer to this section.
To learn how the report command works, the most important method that you need to know is the generateReport() method from the ReportGenerator class.
Refer to the sequence diagram below to understand the top-level execution of the generateReport() operation after the user enters a valid report command.
report command: generating 2020-03-27_report.txt
The lifelines for the ReportCommandParser object, ReportCommand object, ReportGenerator object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
From the above diagram, creating a report for the consumption patterns on 27th of March 2020 involves the following steps:
Step 1: User inputs report d/2020-03-27 to generate the insights report based on food consumption of the abovementioned date.
Step 2: This input is saved as a String and passed into the LogicManager.
Step 3: The String input is parsed by CalgoParser, which removes the "d/" Prefix and sends
the date input to ReportCommandParser.
Step 4: Once the ReportCommandParser checks that the given date is valid, it creates a ReportCommand object and
returns it to LogicManager.
Step 5: LogicManager then executes the ReportCommand.
Step 6: From Model, ReportCommand retrieves the required objects to construct an instance of ReportGenerator.
Step 7: With the relevant objects retrieved from Steps 6, ReportCommand constructs a
ReportGenerator object.
Step 8: Using the ReportGenerator object, ReportCommand invokes the crucial method generateInsights(), which prints
neatly organised sections of analysed data based on the DailyFoodLog of the input date. For the section that gives insights related to
the user’s favourite Food, the past seven days of DailyFoodLog objects are analysed.
Step 9: This newly generated report is saved in the data/reports folder. If the report is successfully generated,
the CommandResult is true. Otherwise, it is false. This CommandResult object is finally returned to LogicManager,
to signify the end of the command and GUI shows a result message to the user.
4.1.2. Design considerations
Many of the design considerations made for report command are similar to that of the export command. You can check
out the similar design considerations over here.
In addition to those design considerations, the following consideration is specific to the functionality of the report command.
Aspect: How many days of past data should be used to analyse user’s favourite Food for the suggestions section of the report.
-
Alternative 1 (current choice): Analyse past seven days of consumption data
-
Pros: This implementation is more efficient as it does not have to churn out all existing consumption data produced by the user so far.
-
Cons: The reliability of the suggestions may be impacted due to certain special events such as buffet celebrations, in which the user may consume
Foodthat he or she does not usually eat.
-
-
Alternative 2: Analyse all data
-
Pros: The insights will be much more reliable and more tolerant of outlier data points.
-
Cons: The efficiency of the
reportcommand gets worse over time and may cause dissatisfaction to the user.
-
4.1.3. Summary
The following activity diagram summarizes what happens when user executes a report d/DATE command with a correctly formatted date:
You can check out this code snippet to see how the ReportCommand object determines if there are no Food entries present
in the given date:
// if there is no food consumed on the given date, do not execute command
if (!model.hasLogWithSameDate(queryDate) || model.getLogByDate(queryDate).getFoods().size() == 0) {
throw new CommandException(MESSAGE_REPORT_FAILURE + "\n" + String.format(NO_SUCH_DATE, queryDate))
}
4.2. Setting daily calorie goals for motivation
(by Vineeth)
This feature helps a user chunk his or her long term goal of developing a healthy lifestyle into smaller daily goals. Psychologically, this helps to motivate them as the perceived difficulty of achieving the long term goal reduces.
The user can set a daily calorie goal with the goal command, which follows the following format: goal GOAL.
This section addresses how the goal command works.
4.2.1. Implementation
In addition to the GoalCommandParser, the goal command relies heavily on the DailyGoal class, which is part of the
Model component. Refer to the Model component diagram here.
To address the issue where a user does not want to set up a daily calorie goal, Calgo places a DUMMY_VALUE of 0 calories,
as shown in the code snippet below, from DailyGoal class. To cater to a wide range of users, it also has a broad range of acceptable
values, ranging from 1 to 99999. However, to guide users towards a healthy lifestyle, the App does display a warning
message whenever a user sets a goal below the MINIMUM_HEALTHY_CALORIES.
// Values used for GoalCommandParser when parsing user inputted goals.
public static final int MINIMUM_HEALTHY_CALORIES = 1200;
public static final int MINIMUM_ACCEPTABLE_CALORIES = 1;
public static final int MAXIMUM_ACCEPTABLE_CALORIES = 99999;
// Default value, when user does not input a goal.
public static final int DUMMY_VALUE = 0;
Refer to the sequence diagram below to understand how a goal command is executed.
.Sequence Diagram for goal command: updating daily calorie goal to 2000 calories.
The lifelines of the GoalCommandParser object, GoalCommand object and DailyGoal object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
The following steps explain the sequence diagram:
Step 1: User inputs goal 2000 to update his or her goal to 2000 calories.
Step 2: This input is saved as a String and passed into the LogicManager.
Step 3: The String input is parsed by CalgoParser, which sends the goal value input to GoalCommandParser.
Step 4: Once the GoalCommandParser checks that the given value is valid, it converts the input to an Integer and creates a GoalCommand object and
returns it to LogicManager.
Step 5: LogicManager then executes the GoalCommand, which in turn invokes updateDailyGoal method of Model.
Step 6: In Model, the updateDailyGoal method is a static method that generates a new DailyGoal object with the corresponding input. This DailyGoal object is returned to Model
, which replaces the existing DailyGoal attribute of the ModelManager with the newly generated DailyGoal object.
4.2.2. Design Considerations
Aspect: Type of user input data that is required for goal command
-
Alternative 1 (current choice): Use a simple goal feature that accepts the user’s inputted value.
-
Pros:
-
The user is not daunted by the large amount of information he or she needs to provide to set a goal.
-
The user will not feel paranoid as Calgo does not ask for personal data such as height, weight, gender and age.
-
-
Cons:
-
The goal may not be effective unless the user diligently checks online for a appropriate goal and then enters it into Calgo.
-
-
-
Alternative 2: Use a scientific method to calculate the basal metabolic rate of the user.
-
Pros:
-
The goal is very effective because it matches their body type.
-
-
Cons:
-
A lot of data is required to be inputted by the user.
-
May cause users to avoid setting goals because of the large amount of personal data they need to store in Calgo.
-
User feeling uncomfortable about setting a goal will also affect effectiveness of
reportcommand.
-
-
4.2.3. Summary
In essence, the goal command is a fun feature that is used to motivate the user and generate specific insights if the
user were to invoke the report command after setting a daily calorie goal.
Refer to the Activity Diagram below for a visual summary of the logic behind the execution of the goal command.
goal command.4.3. Searching for specific Food via categories and substrings
(by Eugene)
This section addresses how the find and list commands work. As they are complementary in their functions during the search process, both find and list commands will be explained together here for better coherence.
The find command allows us to search through the FoodRecord (via categorical or substring search) based on what the user enters for the Prefix. Users may enter one and only one Prefix. The search results can then be displayed in the GUI’s Food Record.
Meanwhile, the list command allows us to reset the GUI’s Food Record to once again show all entries in lexicographical order. This can be thought of as the reverse of a find command. However, unlike the find command, the list command does not use any Prefix, and ignores any input after its command word.
Prefix here indicates which Food attribute we are interested in. Categorical search finds Food objects with values that match the user-specified value representing one of the nutritional categories (Calorie, Protein, Carbohydrate, or Fat). Meanwhile, substring search finds matches for the user-entered substring in any part of the the Name or in any of the Tag objects belonging to the Food objects.
|
| For more information on lexicographical ordering, please refer to its relevant section here. |
The above commands rely on the FindCommand and ListCommand objects respectively. Objects of both classes use a Predicate<Food> object to filter through the current Food objects, where Food objects will be displayed in the GUI’s Food Record should they evaluate these predicates to be true.
4.3.1. Implementation
To search via a particular Food attribute, we use a FindCommandParser to create the corresponding Predicate<Food> based on which Food attribute the Prefix entered represents. This predicate is then used to construct a new FindCommand object, which changes the GUI display when executed.
The class diagram below shows the relevant Predicate<Food> classes used in the construction of FindCommand objects.
FindCommand objectsAs seen in the above class diagram, each Predicate<Food> is indeed representative of either Name, Calorie, Protein, Carbohydrate, Fat, or Tag. Moreover, it should be noted that each of these predicates test against a Food object, and therefore have a dependency on Food.
The sequence diagram below demonstrates how the find command works, for both categorical and substring search:
find command: Categorical Search and Substring Search
The lifeline for the both of the FindCommandParser objects, and both of the FindCommand objects should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
From the above, it is clear that both categorical search and substring search of the find command have similar steps:
Step 1: LogicManager executes the user input, using CalgoParser to realise this is a find command, and a new FindCommandParser object is then created.
Step 2: The FindCommandParser object parses the user-entered arguments that come with the Prefix, creating a Predicate<Food> object based on which Food attribute the Prefix represents.
-
In the above diagram examples, a
ProteinContainsKeywordsPredicateobject is created for the categorical search viaProteinwhile aNameContainsKeywordsPredicateobject is created for the substring search viaName.
Step 3: This Predicate<Food> object is then used to construct a new FindCommand object, returned to LogicManager.
Step 4: LogicManager calls the execute method of the FindCommand created, which filters for Food objects that evaluate the predicate previously created to be true. It then returns a new CommandResult object reflecting the status of the execution. These changes are eventually reflected in the GUI.
The find command therefore searches through the existing FoodRecord and then displays the relevant search results in the GUI’s Food Record. To once again show all Food entries in the display, we use the list command.
In constrast to FindCommand, the ListCommand constructor takes in no arguments and simply uses the predicate Model.PREDICATE_SHOW_ALL_FOODS to always show all Food entries in its execute method. This is described by the sequence diagram below:
list command
The lifeline for the ListCommand object should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
|
How the list command works:
Step 1: LogicManager executes the user input, using CalgoParser to realise this is a list command, and a new ListCommand object is created.
Step 2: LogicManager then calls the execute method of this ListCommand, which uses Model.PREDICATE_SHOW_ALL_FOODS to evaluate to true for all Food objects in the FoodRecord.
Step 3: LogicManager then returns a new CommandResult object to reflect the status of the execution, in the GUI. The GUI’s Food Record reflects the above changes to show all Food entries once again.
4.3.2. Design considerations
Aspect: Predicate construction source.
-
Alternative 1 (current choice): Each
Predicate<Food>is constructed using a new object of type eitherName,Calorie,Protein,Carbohydrate,Fat,Tag.-
Pros:
-
Defensive programming by building new objects rather than relying on mutable sources.
-
Can reuse existing code and classes like ArgumentMultimap and their methods.
-
Models objects well to reflect the real-world.
-
-
Cons:
-
May be more resource-intensive than other alternatives.
-
New developers may not find this intuitive.
-
-
-
Alternative 2: Each
Predicate<Food>is created using aStringwhich represents the keywords.-
Pros:
-
Easier to implement with fewer existing dependencies.
-
Less resource-intensive.
-
-
Cons:
-
More prone to bugs.
-
Difficult to ascertain which
Foodattribute it actually represents. -
More difficult to debug as
Stringtype is easily modified. -
Does not reflect good OOP practices
-
-
Aspect: Enabling substring search.
-
Alternative 1 (current choice): Allow substring search for both
NameandTag-
Pros:
-
Improves user experience.
-
Can reuse common code as the approach for both
NameandTagare similar. -
Generally easy to implement substring finding.
-
Can use regular expressions if needed, which are powerful and suitable for our purpose.
-
-
Cons:
-
Requires good understanding of the original project.
-
Need to know the
Stringtype, regular expressions, and their implications. -
Need to implement searching via multiple types of
Foodattributes and hence introduces more dependencies. -
Need to implement a new
Parserclass to detect each relevantPrefix.
-
-
-
Alternative 2: Only allow exact word matches for
NameandTag-
Pros:
-
Can simply reuse large parts of the original project’s existing code.
-
Less prone to bugs.
-
Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-
-
Cons:
-
Diminishes user experience.
-
May not fully satisfy the user requirements.
-
Need to implement searching via multiple types of
Foodattributes and hence introduces more dependencies. -
Need to implement a new
Parserclass to detect each relevantPrefix.
-
-
4.3.3. Summary
In essence, this section focuses on searching which is implemented via find and list commands.
The find command performs a categorical search if a value from a nutritional category (Calorie, Protein, Carbohydrate, Fat) is specified. Otherwise, a substring search is performed to find Food objects that contain the entered substring in their Name or in one of their Tag s. These rely on the Predicate<Food> object used in constructing the FindCommand, which depend on the Prefix entered by the user.
Meanwhile, the list command simply uses the predicate already defined in Model to display all Food objects.
The above can be summarised in the activity diagram below:
4.4. Lexicographical Food order
(by Eugene)
This section addresses how the GUI Food Record entries appear in lexicographical order, which is an effect of sorting Food objects in the FoodRecord.
Over time, users will eventually have many Food entries — these should be sorted for a better experience. Intuitively, the lexicographical order is the most suitable here.
In essence, Food objects are sorted by the UniqueFoodList (which is inside FoodRecord).
Sorting is performed each time Food object(s) are newly added to the UniqueFoodList, or during the initialisation of the UniqueFoodList upon App start-up.
There is no need to re-sort when a Food object is deleted or edited as the order is maintained.
For a better understanding of adding and editing Food objects using the update command, please refer to its relevant section here.
|
Although the the list command changes the GUI Food Record display, it does not actually perform sorting. It simply resets the GUI Food Record to show all Food entries, and is usually used after a find command. You can read more about them here.
|
4.4.1. Implementation
The UniqueFoodList is able to sort Food objects because the Food class implements the Comparable<Food> interface.
This allows us to specify the lexicographical order for sorting Food objects via their Name, using the following compareTo method in the Food class:
public int compareTo(Food other) {
String currentName = this.getName().toString();
String otherName = other.getName().toString();
return currentName.compareTo(otherName);
}
How the sorting process works:
-
When the App starts up, a new
UniqueFoodListis created from the source json file (if available) or otherwise the default entries, and the createdFoodobjects are sorted as they are added to it. -
Existing
Foodobjects are therefore arranged in lexicographic order byName. -
Thereafter,
UniqueFoodListsorts theFoodobjects whenever newFoodobjects are added.
It should be noted that sorting is only performed by the addFood and setFoods method of the UniqueFoodList, which calls the sortInternalList method. Not to be confused, the setFood method, which is used when a Food object is edited, does not perform any sorting.
The sequence diagram below shows how the lexicographical ordering is performed when Calgo starts up:
Based on the above diagram, when Calgo starts:
Step 1: We initialise the ModelManager object. For this, we use previously stored user data if available (by reading in from the source json files). Otherwise, we use the default Calgo Food entries.
Step 2: Before we can finish constructing a new ModelManager object, we require the creation of a new FoodRecord object which in turn requires the creation of a new UniqueFoodList object.
Step 3: Once UniqueFoodList is constructed, we introduce the initialising data into it using the setFoods method. This calls the sortInternalList method, which sorts the newly added Food objects in the ObservableList<Food> contained in UniqueFoodList, according to the specified lexicographical order (defined in the Food class).
Moving on, the sequence diagram below (which is a reference frame omitting irrelevant update command details) describes the lexicographical sorting process when Food objects are added (not edited) using the update command:
This is in a reference frame as it is reused in the update section here)
|
Here, the diagram describes what happens after parsing the user input and creating an UpdateCommand object. Since the Food entered by the user is an entirely new Food object without a Name-equivalent Food existing in the UniqueFoodList:
Step 1: We call the respective addFood and add methods as seen in the diagram, eventually adding the Food object into the UniqueFoodList and arriving at its sortInternalList method call.
Step 2: The sortInternalList method then sorts the Food objects in the ObservableList<Food> contained in UniqueFoodList, according to the specified lexicographical order defined in the Food class.
During an update command, we do not perform sorting if the user enters a Food object that already has an existing counterpart with an equivalent Name in the UniqueFoodList.
|
Any re-ordering will eventually be reflected in the GUI, facilitated by the following (in the case of an update command) or otherwise something similar:
model.updateFilteredFoodRecord(Model.PREDICATE_SHOW_ALL_FOODS);
4.4.2. Design considerations
Aspect: Frequency of sorting operation.
-
Alternative 1 (current choice): Sort whenever a new
Foodis added or during App start-up.-
Pros:
-
Guarantees correctness of sorting.
-
Saves on computational cost by not sorting during deletion or edits as the order is preserved.
-
Computational cost is not too expensive since the introduced
Foodobjects usually come individually rather than as a collection (except during App start-up).
-
-
Cons:
-
Need to ensure implementations of various commands changing the
Modelare correct and do not interfere with the sorting process. -
May be computationally expensive if there are many unsorted
Foodobjects at once, which is possible when Calgo starts up.
-
-
-
Alternative 2: Sort only when calling the
listcommand.-
Pros:
-
Easier to implement with fewer existing dependencies.
-
Uses less computational resources since sorting is only done when
listcommand is called.
-
-
Cons:
-
Diminishes user experience.
-
May be incompatible with certain
Storagefunctions. -
May lead to bugs in overall product due to incompatible features.
-
-
Aspect: Data structure to store Food objects.
-
Alternative 1 (current choice): Use
UniqueFoodListto store allFoodobjects.-
Pros:
-
Can reuse existing code, removing the need to maintain a separate list-like data structure.
-
Based on existing code, any changes to the
Modelfrom the sorting process are automatically reflected in the GUI. This is very useful for testing and debugging manually.
-
-
Cons:
-
Many of the underlying
ObservableListmethods are built-in and cannot be edited. They are also difficult to understand for those unfamiliar. This can make development slightly trickier, especially in following certain software engineering principles.
-
-
-
Alternative 2: Use a simpler data structure like an
ArrayList.-
Pros:
-
Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-
-
Cons:
-
More troublesome as we require self-defined methods, abstracted over the existing ones. If not careful, these self-defined methods can possibly contain violations of certain software engineering principles, which may introduce regression in the future.
-
May be inefficient in using resources.
-
-
4.4.3. Summary
The UniqueFoodList facilitates the lexicographical ordering of Food objects and hence how their respective entries appear in the GUI Food Record. This can be summarised in the activity diagram below:
4.5. Exporting the current FoodRecord into a portable file
(by Eugene)
This section addresses how the export command works, creating a FoodRecord.txt file showing details of all the Food objects currently stored in the FoodRecord. The information is presently neatly in table form and the file is created in the data/exports folder.
The export command mainly uses an ExportGenerator object to generate the file. All formatting options and methods to write the contents of the file are included in the ExportGenerator class, which extends the DocumentGenerator class.
You may find the report command similar as they both create a new file for the user. You can read more about it here.
|
4.5.1. Implementation
Most of the work in generating the file is done by the generateExport method of ExportGenerator. You can access the class to view its methods for writing the header and footer components, which are relatively easily to understand.
However, the methods for writing the file body is likely where some explaining is required. Here, the formatting of the table body is determined by the following:
private static final int NAME_COLUMN_SIZE = 45;
private static final int VALUE_COLUMN_SIZE = 20;
NAME_COLUMN_SIZE represents the allowed space for the Name. If a Food object has a Name which is too long, the Name will be truncated and continued on the following lines.
Meanwhile, VALUE_COLUMN_SIZE represents the allowed space for each nutritional value of Calorie, Protein, Carbohydrate, and Fat in the table. These are guaranteed to be within a length of 5 characters when parsing, and should not exceed the given space.
The nutritional values will always be shown in the first line of their respective Food object after its (possibly truncated) Name. This is facilitated by the printBody method of ExportGenerator, which calls its printBodyComponent method and subsequently its generateFinalisedEntryString method, which performs the truncation and amendment of the Name as necessary.
Moving on, the sequence diagram below demonstrates how the export command works to create the user copy of the current FoodRecord:
export command: Generating FoodRecord.txt
The lifeline for the ExportCommand object and that of the ExportGenerator object should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
From the above, creating FoodRecord.txt involves the following steps:
Step 1: LogicManager executes the user input, using CalgoParser to realise this is a export command, and a new ExportCommand object is created.
Step 2: LogicManager then calls the execute method of this ExportCommand object. This results in a call to the Model to get the current FoodRecord, which is used to construct a new ExportGenerator object. The ExportGenerator is responsible for creating the FoodRecord.txt file and writing to it.
Step 3: ExportCommand then calls the generateExport method of ExportGenerator, writing the required parts to the file. This returns a boolean indicating whether the file creation and writing are successful.
Step 4: A new CommandResult object indicating the result of the execution is then constructed and reflected in the GUI.
4.5.2. Design considerations
Aspect: Type of file to create.
-
Alternative 1 (current choice): Create a .txt file to represent the
FoodRecord.-
Pros:
-
Satisfies user requirements by allowing editing of the file to include custom entries.
-
-
Cons:
-
Need to define new classes and methods for file writing, which may introduce more dependencies.
-
May be more resource-intensive than other alternatives.
-
New developers may be unfamiliar with
Stringmanipulation and regular expressions.
-
-
-
Alternative 2: Create a .pdf file to represent the
FoodRecord-
Pros:
-
The contents appear to be more legitimate.
-
Can use external libraries for convenience.
-
May be less resource-intensive.
-
-
Cons:
-
May not satisfy user requirements as the file cannot be edited easily.
-
May introduce more bugs, additional dependencies, and become prone to external factors.
-
More difficult to debug due to lack of familiarity with external libraries.
-
May require more space.
-
-
Aspect: Abstraction for ExportGenerator and ReportGenerator.
-
Alternative 1 (current choice): Create
DocumentGeneratorabstract class which bothExportGeneratorandReportGeneratorextends.-
Pros:
-
Good OOP practice, following its principles.
-
Allows for code reuse and neater code.
-
Able to apply concepts of polymorphism, if required.
-
May be now easier to debug.
-
-
Cons:
-
Need to define new class, possibly introducing more dependencies.
-
Need to identify what is common to both
ExportGeneratorandReportGenerator.
-
-
-
Alternative 2: Use an interface which both classes will implement.
-
Pros:
-
Similar to Alternative 1.
-
-
Cons:
-
Does not allow methods to be defined in the interface. (Some exceptions: default methods, etc)
-
May need to repeat definitions which may be the same for both classes.
-
-
-
Alternative 3: Do not use an interface or abstract class.
-
Pros:
-
Requires less effort.
-
Does not introduce additional dependencies.
-
-
Cons:
-
Unable to reap benefits of the above alternatives.
-
-
4.5.3. Summary
In short, this section addresses how users are able to obtain an editable copy of the current FoodRecord using the export command.
The export command largely relies on the ExportGenerator class, which facilitates creating the file and writing to it.
The above can be summarised in the activity diagram below:
4.6. Food consumption management
(by Ruicong)
This section addresses how nom, vomit, and stomach commands work. They are the 3 commands that you will use
to interact with ConsumptionRecord. nom allows you to add Food, vomit allows you to remove Food, and stomach
gives you a way to browse a list of Food within the ConsumptionRecord at a different date. ConsumptionRecord is an important
component because it serves as a backend for features such as goal, report and graph.
The high level idea of how Food consumption is managed is that ConsumptionRecord stores all the Food consumed.
Whenever nom, vomit, or stomach is used, a list will be retrieve and sent to a FilteredList. Such a list consists of DisplayFood objects,
for the purpose of displaying information compiled from each Food.
The FilteredList is an observable, so whenever it is updated, the GUI will be informed and display the contents accordingly.
4.6.1. Implementation
In this section, I will be walking you through the implementation of the ConsumptionRecord, what happens on App startup,
and what happens when a consumption related command is called. I will be talking about the nom command more specifically.
This is because vomit and stomach work very similarly, and you will see that it’s easy to understand once you have read through this
section.
In Calgo, you will find that the GUI ConsumptionRecord use a uniqueDateToLogMap to map each LocalDate to a DailyFoodLog.
As you can guess, LocalDate keys are unique.
Each DailyFoodLog is related to a LocalDate object and contains 2 LinkedHashMap, one to map Food consumed to their portion,
another to map Food to an ArrayList of Integer, which represents the ratings given to that Food item consumed on that day.
On App startup, initModelManager of MainApp class is invoked. This will cause storage to read consumption record data from a .json file
which stores App data. The .json file stores JsonAdaptedDailyFoodLog, which is similar to DailyFoodLog in every way,
but deals with JsonAdaptedFood class instead of Food. Notice that there are a chain of toModelType commands
called as we dive deeper into the method call stack. toModelType is actually the method to return a working counterpart of JsonSerializable and JsonAdapted classes
that will be delivered to the model of Calgo.
So you might ask, what does JsonAdapted mean? Well, JsonAdapted classes are specially formatted versions
of their counterparts that makes it easy for the Jackson API to read and write to.
|
Here is what happens when different classes call toModelType:
-
JsonSerializableConsumptionRecordreturns its equivalent copy ofConsumptionRecord. -
JsonAdaptedDailyFoodLogreturns its equivalent copy ofDailyFoodLog.
Below shows the high level view of the initialization process:
| For the subsequent sequence diagrams in the section, the lifelines for objects should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. |
Now that the ConsumptionRecord has been initialized, the App can start interacting with the user.
Whenever the user enters a nom command into the GUI, a sequence of events occur.
Here is a a step-by-step guide to what happens in such a scenario:
Step 1: UI component MainWindow receives the input as a String. That String is then passed into LogicManager,
which calls the parseCommand of CalgoParser. Suppose the String is "nom n/Apple d/2020-04-12 portion/2 r/7"
CalgoParser detects that this is a nom command. CalgoParser then delegates this job by creating
a new NomCommandParser object which will parse this String.
Step 2: NomCommandParser gets relevant values from Prefixes of input String, and then checks with the ModelManager.
It specifically checks if there exists a DailyFoodLog with the same LocalDate as what was parsed so that it can use existing information if
they are already present. From all these information, a DailyFoodLog object representing the result of consuming a Food is created, and supplied to create
NomCommand. The diagram below shows how this happens:
Step 3: NomCommand updates the ModelManager with the DailyFoodLog obtained during its execution by LogicManager.
Such information cascades down the layers of abstraction until it reaches ConsumptionRecord,
which updates its underlying 'uniqueDateToLogMap' aforementioned here.
Step 4: NomCommand then informs the ModelManager to update its FilteredList, which gets information from the updated ConsumptionRecord.
Since the FilteredList is a wrapper of ObservableList, its update will inform the UI components that utilise JavaFx of changes.
This results in the GUI automatically updating to reflect the changes.
Step 5: A new commandResult object is created an passed back to MainWindow, and displayed in Result Display.
Step 6: Finally, the changes in ConsumptionRecord are saved to StorageManager.
4.6.2. Design considerations
Aspect: How nom executes
-
Alternative 1 (current choice): Create a new
DailyFoodLogto pass intoModelManagerand thenConsumptionRecord.-
Pros:
-
Maintain comprehensive layers of abstraction
-
Allows code to be easily testable.
-
-
Cons: Difficult for newcomers or even existing users to trace because of long execution path.
-
-
Alternative 2: Bypass
ModelManageror even not useConsumptionRecordfor storage of data during runtime by allowing everything to be done from parser.-
Pros: Reduce dependencies on
ModelManagerandConsumptionRecord, and make code contained in a single class file easier to navigate. -
Cons: Violates layers of abstraction set in place by previous structure of AddressBook3. Violates Single Responsibility Principle and reduce cohesiveness of code.
-
Aspect: Data structure to support the consumption commands
-
Alternative 1 (current choice): Use a single
FilteredListto store food for any day by repopulating it each time a consumption related command is used.-
Pros: Only uses a single
FilteredList, so it is clear which list you are using for display. -
Cons: May have performance issue in terms of speed when there are too many entries.
-
-
Alternative 2: Use a
FilteredListfor each date, to store food consumed on that date.-
Pros: Faster retrieval for display of
ConsumedFooditems. However, under practical circumstances, the difference is negligible. -
Cons: May have performance issue in terms of storage because it requires many lists to be stored in addition to
LinkedHashMapinDailyFoodLogfor eachLocalDate.
-
Aspect: Selecting items to delete from ConsumptionRecord using Vomit command
-
Alternative 1 (current choice): Use index to select item to delete.
-
Pros: When the list is short, user can quickly identify the entry to delete.
-
Pros: Convenient for user as he is required to type less.
-
Pros: User need not spend effort remembering names.
-
Cons: When there are too many records, user is required to scroll through records.
-
-
Alternative 2: Use name to select item to delete
-
Pros: Can utilise the
Result Displaysuggestion to improve user experience. -
Cons: User face the hassle of memorising names and typing more.
-
4.6.3. Summary
This section is a summary on all the above discussed. I would do so with the aid of a few activity diagrams so that you are clear about the flow of the processes covered.
The 2 diagrams below serves as (rakes), which shows more details.
4.7. Modifying the FoodRecord
(By Zhang Xian)
This section addresses how the FoodRecord can be modified by the update and delete commands.
The update command allows the user to modify the FoodRecord by either adding a new Food into the FoodRecord or editing the
nutritional values of an existing Food in the FoodRecord.
From the user’s perspective, the update command does either of the adding and editing functions. This implementation of update decides whether to
override an existing Food in the FoodRecord with new values, or create a new Food in the FoodRecord for them.
For better user experience, for all new Food being updated into the FoodRecord with the update command, the Name attribute
will be formatted to proper case. This means that if the user updates a new Food into the FoodRecord with the Name as "char kuay teow", the Food that
is stored in the FoodRecord will be of Name "Char Kuay Teow".
| When a new Food is updated into the FoodRecord, the FoodRecord is sorted in lexicographical order. For more information on how this is implemented, please refer to its relevant section here. |
The delete command allows the user to modify the FoodRecord by deleting a specified Food entry from the FoodRecord.
This command takes in the Name of the Food entry to be deleted.
For both delete and update commands, the Name parameter is implemented to be case-insensitive. This means that n/APPLE and n/apple refers to
the same Food entry with Name stored as Apple.
4.7.1. Implementation
The modification of the FoodRecord is facilitated by UniqueFoodList, which is responsible for storing all the Food entries in the FoodRecord.
Additional abstractions were used by Model and Logic for any operations that results in a modification of the UniqueFoodList.
Both commands require an additional operation, hasFood, in FoodRecord to be implemented. hasFood checks if there is an existing Food in FoodRecord
by checking if there is any Food in the FoodRecord with the same Name. Two Food entries is deemed to be of the same Name if their lowercase variant
is the same.
This operation was exposed in the Model interface as hasFood, allowing UpdateCommand and DeleteCommand this functionality.
Implementation of update command:
For the update command, the hasFood operation decides whether UpdateCommand adds a new Food into UniqueFoodList or
edits the nutritional values of an existing Food in the UniqueFoodList.
The following sequence diagram shows how the update operation works in both cases:
update command
The lifeline for UpdateCommandParser and UpdateCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
How the update command works:
Step 1: LogicManager executes the user input of update n/apple cal/52 p/2 c/14 f/1", using `CalgoParser to realise this is
an update command and creates a new UpdateCommandParser object.
Step 2: UpdateCommandParser then parses the arguments provided by CalgoParser with the parse method.
During this parsing process, UpdateCommandParser calls the covertToTitleCase method on the Name argument, converting it
to proper case.
Step 3: UpdateCommandParser then creates a new UpdateCommand object, which LogicManager calls the execute method with this object
as an argument.
Step 4: UpdateCommand now checks if there exists an existing Food in the FoodRecord by calling Model 's
hasFood method.
Step 5:
-
Scenario 1: If Food already exists in the
FoodRecord:-
Model calls the
getExistingFoodmethod with the user inputtedFoodas a parameter to get the existingFood,existingFoodin theUniqueFoodList. It thens call thesetFoodmethod to replace the existingFoodin theUniqueFoodListwith the newFoodwhich contains new nutritional values.
-
-
Scenario 2: If
Fooddoes not exist inFoodRecord:-
This scenario is handled by the Lexicographical Ordering feature. Please refer to its relevant section here.
-
Model calls the
addFoodmethod with the user inputtedFoodas a parameter to add the newFoodinto theUniqueFoodListinFoodRecord -
After the
Foodis added into theUniqueFoodList, theUniqueFoodListis also sorted in lexicographical order.
-
Step 6: A new CommandResult object is then created and returned back to LogicManager.
Implementation of delete command:
For the delete command, the hasFood operation allows UpdateCommand to check whether the Food that the user requests to be
deleted exists in the UniqueFoodList.
The following sequence diagram shows how the delete command works:
delete command
The lifeline for DeleteCommandParser and DeleteCommand should end at their destroy markers (X) but due to a limitation of PlantUML, the lifelines reach the end of diagram.
|
How the delete command works:
Step 1: LogicManager executes the user input of "delete n/Apple", using CalgoParser to realise this is
an delete command and creates a new DeleteCommandParser object.
Step 2: DeleteCommandParser then parses the arguments provided by CalgoParser with the parse method, before creating a new
DeleteCommand object that is returned back to the LogicManager which calls the execute method with this as an argument.
Step 3: DeleteCommand now checks if there exists an existing Food in the FoodRecord by calling Model’s `hasFood method, which
checks if there is such Food in the UniqueFoodList.
Step 4: Model then calls the getExistingFood method to return the Food object to be removed from the UniqueFoodList. Thereafter,
Model calls the deleteFood method with this Food object as an argument to remove this Food from the UniqueFoodList.
Step 5: A new CommandResult object is then created and returned back to the LogicManager.
4.7.2. Design considerations
Aspect: Updating the FoodRecord when there is an existing Food item in FoodRecord
-
Alternative 1 (current choice): Overrides the existing
Fooditem with the newFooditem-
Pros:
-
No need for an additional command of
editjust for the user to edit an existingFooditem in theFoodRecord.
-
-
Cons:
-
Might not be intuitive for the user since the word "update" is generally assumed to be for editing something only and not necessarily adding something.
-
May result in additional performance overhead.
-
-
-
Alternative 2: Informs the user that there is already an existing
Fooditem, and direct him to use another commandeditto edit the existingFoodinstead.-
Pros:
-
More intuitive for user, since he might not know that he is overriding an existing
Fooditem
-
-
Cons:
-
Additional command has to be created just to handle editing
-
More tedious for user since more steps are required to achieve the same result.
-
-
4.7.3. Summary
In summary, this section explains how commands related to modifying the FoodRecord is implemented.
The update command is a smart command that either updates an existing Food entry in the FoodRecord with new nutritional information,
or updates a new Food item into the FoodRecord
The following activity diagram summarises what happens when a user enters a valid update command:
update commandThe delete command allows the user to remove a Food entry from the FoodRecord by specifying it’s Name as an parameter.
The following activity diagram summarises what happens when a user enters a valid 'delete' command:
delete command4.8. Real-time Suggestions for existing Food in FoodRecord
(By Zhang Xian)
This section addresses how the GUI Result Display suggests Food with similar Name to the user for the commands update, delete and nom.
When the user have many Food entries in the FoodRecord, they may have difficulties finding out if a particular Food exists in the FoodRecord.
For better user experience, this feature listens to the input of the user for these three commands and suggests similar existing Food entries in real time in the GUI’s
Result Display.
This feature listens to the input of the user after the Prefix n/ and checks if there is a Food entry in the FoodRecord with a similar Name.
The Name parameter is case-insensitive and searches the Food entries in the FoodRecord by whether they start with the user input so far after the
Prefix n/.
|
4.8.1. Implementation
To be able to process user’s input in real-time, we set a listener in the CommandBox to listen for the input of any of the three commands: update, delete or nom
This feature is then facilitated by different objects, mainly MainWindow and UniqueFoodList. MainWindow interacts with LogicManager 's method of getSimilarFood which exposes
the FoodRecord, allowing a filtered list of similar Food entries in the UniqueFoodList to be returned back to the user.
A predicate, FoodRecordContainsFoodNamePredicate is also essential in this implementation in ensuring that the correct similar Food items can be filtered from the UniqueFoodList
back to the LogicManager to be displayed by the GUI. The test method of this predicate which is responsible for the above is shown:
public boolean test(Food food) {
boolean foodStartsWithInputFoodName = food.getName().fullName.toLowerCase()
.startsWith(foodName.toLowerCase().trim());
boolean inputFoodNameStartsWithFood = foodName.toLowerCase().trim()
.startsWith(food.getName().fullName.toLowerCase());
return foodStartsWithInputFoodName || inputFoodNameStartsWithFood;
}
Both of the boolean used for this predicate is essential. For instance, if "Laksa is already present" in the FoodRecord:
-
If the user keys in "Lak", the first
booleanfoodStartsWithInputFoodNameensures that "Laksa" will be suggested to the user. -
If the user keys in "Laksa Spicy", the second
booleaninputFoodNameStartsWithFoodensures that "Laksa" will be suggested to the user.
The following sequence diagram will explain how the different objects interact to achieve the Real-time Suggestion Feature.
Based on the above diagram, when a user has already entered any of the CommandWord: update, delete or nom, and also the Prefix n/:
Step 1: CommandBox calls the MainWindow method of getSuggestions with the parameter as the entire String of user input in the CommandBox.
Step 2: MainWindow then parses the user inputted String and calls LogicManager method of getSimilarFood with the parameter foodName which is the entire String after the Prefix n/
Step 3: The Model then does the necessary work by calling methods getFoodRecord and getFoodList. This results in the current UniqueFoodList being returned
Step 4: The UniqueFoodList is then filtered with the Predicate<Food>, FoodRecordContainsFoodNamePredicate which returns a List<Food> of Food objects that have similar Name fields to the user input.
Step 5: Finally, the filtered List<Food> is then parsed into a String for the user by the MainWindow and then displayed in the GUI’s Result Display.
4.8.2. Design Considerations
Aspect: How the suggestions is shown to the user.
-
Alternative 1: (current choice):
ResultDisplaydisplays the names of similarFoodentries inFood Record.-
Pros:
-
Improved user experience, allowing user to still view the unfiltered
FoodRecordin the GUI. -
User can have access to the raw
Stringof theNamesimilarFoodentries for copying and pasting.
-
-
Cons:
-
Additional interacting with
UIcomponents required, instead of just filteringUniqueFoodList -
Cannot reusing existing lexicographical sorting feature of
FoodRecord.
-
-
-
Alternative 2: Filter the GUI’s
Food Recordto show similar Food entries.-
Pros:
-
Feature is limited to minimal interactions with
UI, making use of existingUI-Modelabstractions. -
Compatible with existing code relating to the
FoodRecord, allowing code to be reused.
-
-
Cons:
-
Takes away most of the need for
findandlistfeatures since they achieve mostly the same purpose.
-
-
Aspect: Commands that utilise Real-time Suggestions
-
Alternative 1: (current choice): Only three commands:
update,delete,nom-
Pros:
-
Improves computational performance, since real-time features for every command will be computationally expensive.
-
Keeps the desired outcomes of other features such as
findandlistintact
-
-
Cons:
-
Decrease in user experience, as they might expect this feature to be universal for all commands
-
-
-
Alternative 2 All the commands
-
Pros:
-
Better standardisation of feature across all commands.
-
-
Cons:
-
Additional computational overhead.
-
Not all commands have a
Namefield. -
Additional implementation or significant change in how this feature works is necessary to make it universal.
-
-
4.8.3. Summary
CommandBox listens for any of the three commands as mentioned, allowing LogicManager and FoodRecord to facilitate
the suggestions of similar Food entries from the UniqueFoodList to display in the GUI’s Result Display. This can be summarised
in the activity diagram below:
4.9. Command guide help command
(by Janice)
This section addresses how the help command works.
The help command allows users to reference a summarised version of the User Guide (called the command guide)
containing the usages of the commands and their formats, arranged in alphabetical order. Users may enter an
optional command word that filters the displayed command guide.
command word filters out only commands which contain the command word as a substring. If no commands contain it
as a substring, an error message will be displayed at the top of the GUI component Help Window and the full command guide will be shown.
|
4.9.1. Implementation
To generate a command guide using the help command, a HelpCommand object generates the relevant command guides
based on the provided command word in the input.
The sequence diagram below demonstrates how the help command works, should a command word of "nom" be provided.
Step 1: LogicManager executes the user input, using CalgoParser to realise it is a help command, and thus creates
HelpCommand
Step 2: HelpCommand constructor generates the necessary mapping of command name to the corresponding command guide.
Step 3: LogicManager calls the execute method on the HelpCommand object, which produces the String containing the
relevant command guides. A CommandResult object is produced reflecting the response to the help command.
-
In the above sequence diagram, one possibility shown, where the user provides a
command_word.setFilteredGuidewill attempt to retrieve only relevant command guides, defaulting to a list of all guides if no relevant guides exist. Otherwise, by default a list of all guides will be provided.
Step 4: The CommandResult is eventually passed to the MainWindow class, which then displays the command guide in a separate
window, using the HelpWindow class.
4.9.2. Design considerations
Aspect: How Help is displayed
-
Alternative 1 (current choice): GUI component
Help Windowis displayed as a separate popup.-
Pros:
-
User can refer to the command guide in a window separately from Calgo, keeping it present as they use the App.
-
Command guide can give a more detailed description of command usage and format as it has more space to display in.
-
No internet access is required as all information on commands is stored offline.
-
-
Cons:
-
helpdoes not redirect to a url containing the most up-to-date User Guide. Changes made to the User Guide must be updated inHelpCommandseparately. -
GUI component
Help Windowmight obstruct view of the App upon initially loading it, causing annoyance.
-
-
-
Alternative 2: GUI component
Help Windowis not used, and instead content is displayed as part of GUI componentResult Display.-
Pros:
-
No possibility of a popup blocking the main app.
-
All information is contained within a single window.
-
-
Cons:
-
User must use the
helpcommand every time they require a guide, as GUI componentResult Displaywill be overwritten after other commands.
-
-
Aspect: Command guides can be selectively displayed
-
Alternative 1 (current choice):
helpdisplays all command guides by default. User can selectively filter to display only desired commands by entering an optional keyword afterhelp.-
Pros:
-
Desired command can be more rapidly found.
-
Removes all unwanted commands from GUI component
Help Window, reducing clutter.
-
-
Cons:
-
Filtered
helpdoes not benefit users who don’t know the command they’re looking for.
-
-
-
Alternative 2: Always display all command guides to ensure user will find the guide they require.
-
Pros:
-
No possibility of user being unable to find their desired command after sufficient searching.
-
-
Cons:
-
Relatively large array of commands can be overwhelming to a new user, deterring them from using the App.
-
Can be very frustrating to search through for experienced users.
-
-
4.9.3. Summary
help will produce a popup, displaying a guide on the App’s available commands' purposes and usage format.
4.10. Past seven days calorie data graph
(by Janice)
This section addresses how the graph displaying the user’s past seven day’s daily total calorie consumption works.
Note that the graph counts starting from the date on the current Food Record, and the six days prior to it.
The graph will always display the past seven days' data at the bottom of the app, and will update whenever app data is changed.
If a command changes the date of the Food Record (such as nom or stomach), the graph will update to show data
for the past seven days from that date, inclusive.
|
4.10.1. Implementation
GraphPanel in the Ui component. It contains a LineChart of String date against Number calories, and is populated with
data from an XYChart.series. The data is in turn obtained from the Logic component, which provides only the past seven days'
of DailyFoodLog. The implementation of the GraphPanel class will be further explained.
GraphPanel class implements the following operations:
-
initialiseTreeMap- Sets up the TreeMap that mapsLocalDatedate ofDailyFoodLogto theDoubletotal calorie consumption n that day. -
initialseGraph- Sets up theLineChartwith xAxis aStringrepresenting date, and yAxis aDoublerepresenting total calories consumed on that date. -
updateSeries- Ensures theXYChart.seriesthat populates the graph with data is always updated with the most recent app data. -
makeGraph- Wrapper function that calls the above three methods. -
getGraph- Public accessor function to generate and retrieve theLineChart.
Calgo will display the past seven days' graph automatically, and likewise update automatically. It does so by having the
MainWindow class call getGraph on startup and after execution of commands.
The sequence diagram below demonstrates how the Graph feature works.
Sequence Diagram for Graph feature.
Step 1: MainWindow requests for an instance of GraphPanel.
If no instance exists, a new GraphPanel is created. Otherwise one is retrieved. This ensures that GraphPanel
is a singleton.
Step 2: MainWindow calls GraphPanel again to generate the graph and add it to the GraphPanelPlaceholder inside MainWindow.
Step 3: Inside GraphPanel, a wrapper method makeGraph calls three methods in a row:
First, initialiseTreeMap, which has Logic call the getPastWeekLogs method onto GraphPanel, generating
a TreeMap of String date mapped to Double calories using the past seven days' DailyFoodLog.
Second, initialiseGraph method is called to generate the graph itself.
Third, updateSeries method is called to ensure the data populating the graph is up to date.
After which, the GraphPanel adds the graph to MainWindow.
4.10.2. Design considerations
Aspect: Choice of visuals for past seven days summary
-
Alternative 1 (current choice): Summary is represented using a line graph.
-
Alternative 2: Summary is represented in a table.
Aspect: When graph’s dates are based on
-
Alternative 1 (current choice): Dates are based on past seven days starting from date of
Consumption Record, inclusive. -
Alternative 2: Dates are based on past seven days starting from today’s date, inclusive.
4.10.3. Summary
In summary, this section addresses how the graph obtains information on the past seven DailyFoodLog, and correspondingly
produces a visual graph output onto Calgo’s Main Window GUI component viewable by the user.
The graph requires the LogicManager class to obtain the information, and the MainWindow class to facilitate display
to the user.
4.11. 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
logLevelsetting in the configuration file (See Section 4.12, “Configuration” below) -
The
Loggerfor a class can be obtained usingLogsCenter.getLogger(Class)which will log messages according to the specified logging level -
Currently log messages are output through:
Consoleand to a.logfile.
Logging Levels
-
SEVERE: Critical problem detected which may possibly cause the termination of the App -
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
4.12. Configuration
Certain properties of the App can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).
5. Documentation
Refer to the guide here.
6. Testing
Refer to the guide here.
7. Dev Ops
Refer to the guide here.
Appendix A: Product Scope
Target user profile:
-
Can type fast
-
Is reasonably comfortable using
CLIApps -
Wants to have, or already has, a lifestyle of eating healthy
-
Manages a significant number of
Fooditems -
Prefers desktop Apps over other types of Apps (such as mobile or tablet)
-
Prefers typing over mouse input
Value proposition:
-
Insights: set goals, generate consumption reports and view progress and statistics
-
Hassle-Free Convenience: conveniently handles entry conflicts, tolerates incomplete search inputs and produces fast responses
-
Flexibility: generate Food records as a portable file, tracking wherever, whenever, without a device
-
Efficiency: manage caloric tracking faster than a typical mouse/GUI driven App
Appendix B: User Stories
Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (possible future development) - *
| Priority | As a … | I want to … | So that I can… |
|---|---|---|---|
|
user who does not know what my food is made of |
find out the nutritional composition of a particular food by name |
locate details of the entry without having to go through the entire record. |
|
new user |
see usage instructions |
refer to instructions when I forget how to use the App. |
|
user |
have a portable, editable, and readable file to store the relevant values for each entry made |
edit, share, or print my personal entries. |
|
user who may not be able to access his laptop at some time |
have a copy of my past entries |
use it for physical reference. |
|
user who dislikes sieving through information and prefers to have only the relevant information presented |
have a way to easily find the entries I want in the record |
save time and effort and not get annoyed. |
|
lazy user who does not like typing too many tedious characters |
find entries using incomplete words or phrases |
obtain the same intended results for a search through the entries as in the case of typing fully and correctly. |
|
user who dislikes memorising things |
have an option to see the entire record |
know what entries currently exist in the records. |
|
user who has many entries |
view entire record in lexicographical order |
easily navigate to the entry I want in the record. |
|
user who is forgetful |
be able to edit the nutritional value of a previously saved entry in the record |
edit the entry if I remembered a nutrition value wrongly previously. |
|
user who is busy |
be able to create a list of goto Food with nutritional values |
quickly choose a Food Item with preset values and add it to my calorie tracker. |
|
user who doesn’t like redundant things |
override a Food entry in the Food Record which already exists |
save time and effort and not create a duplicate item in the Food Record. |
|
user who gets bored of food easily |
deletpe a Food item that I no longer want to eat in future from my Food Record |
avoid having so many Food items in the Food Record that I no longer eat. |
|
user who is a foodie |
find out the statistics of the food that I have been consuming each day |
systematically cut down on overeaten food and monitor progress. |
|
user who cannot decide on what to eat |
obtain a list of personalised food recommendations that still align with my dietary goals |
avoid wasting time deciding what to eat nor will I give in to impulse and eat junk food. |
|
user who is interested to lose weight |
find out the number of calories I have consumed each day |
check which days I have exceed my desired number of daily calorie and exercise more to compensate. |
|
user who is busy |
obtain an easy-to-understand consumption report |
quickly understand my food consumption patterns and make plans to rectify them accordingly. |
|
user who remembers the big picture but not the specifics |
search for a particular part of a guide |
not be bothered by unnecessary information. |
|
user who values visuals |
curated information expressed in a well organised graph |
intuitively understand information. |
|
user who values opinions |
have some suggestions based on my goals and consumption patterns |
know my options when I am indecisive on what to eat. |
|
user who cannot fully remember the name for a particular entry |
view all entries which have the nutritional value I happen to remember |
obtain a list of possible Food entries that are relevant. |
|
forgetful user |
be able to lookup exact command formats |
so that I won’t need to go through the trouble of memorising commands. |
As you continue developing the Calgo, feel free to add more user stories here.
Appendix C: Use Cases
For all use cases below, the System is the Calgo application and the Actor is the user, unless specified otherwise.
Also note that the term MSS refers to the Main Success Scenario for each Use Case.
Use case: obtain reference for app’s commands
MSS
-
User wants to find the command guide for the commands in Calgo.
-
User enters
helpcommand with no additionalcommand_word. -
Calgogenerates a popup, displaying a list of all command guides in the popup.Use case ends.
Extensions
2a. User enters a command_word after help, such as foe example help nom.
2a1. Calgo filters out only command guides containing the command_word "nom".
2a2. Calgo generates a popup, displaying this filtered list of command guides.
2b. User enters a command_word after help that has no corresponding command guides.
2b1. Calgo tries to filter out only command guides containing the command_word but fails to find any guides. Thus
it defaults to display all command guides.
Use case resumes from Step 2.
Use case: find Food item by Name or Tag keyword (which can be an incomplete word)
MSS
-
User wants to find a
Foodentry by a specific keyword inNameorTag. -
User enters
findcommand with theNamePrefix, or theTagPrefix, accordingly. -
Calgo shows a list of
Foodentries which contains the substring indicated in any part of theNameorTagof theFoodentries respectively.Use case ends.
Extensions
2a. User enters invalid input for particular Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.
3a. The FoodRecord is empty
3a1. A message is shown indicating that there are zero matching Food items and prompts users to make new entries.
Use case ends.
Use case: find Food item by nutritional value
MSS
-
User wants to
findaFooditem by a single nutritional value of eitherCalorie,Protein,Carbohydrate, orFat. -
User enters
findcommand with appropriatePrefix. -
Calgoshows a list ofFoodentries which has the same nutritional value.Use case ends.
Extensions
2a. User enters invalid input for particular Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.
3a. The FoodRecord is empty
3a1. A message is shown indicating that there are zero matching Food items and prompts users to make new entries.
Use case ends.
Use case: export current FoodRecord
MSS
-
User wants to
exportthe currentFoodRecord. -
User enters the
exportcommand intoCalgo. -
Calgo creates a user-friendly text file
FoodRecord.txtcontaining allFooditem details in thedata/exportsfolder.Use case ends.
Extensions
3a. User’s system prevents the file from being created
3a1. A message is shown indicating that the file is unable to be created.
Use case ends.
Use case: list all current Food entries
MSS
-
User wants to
listall currentFoodRecordentries. -
User enters the
listcommand intoCalgo. -
Calgoshows a list of allFoodentries in the GUI’sFood Record.Use case ends.
Extensions
3a. The FoodRecord is empty.
3a1. Calgo shows a message indicating that all entries are shown, with the GUI showing an empty Food Record. User is also prompted by this message to make new entries.
Use case ends.
Use case: update current FoodRecord with a new Food item
MSS
-
User wants to add a new
Foodentry in theFoodRecord. -
User begins to type in an
updatecommand withNamePrefix. -
Calgoshows that there are no similarFoodentries in GUIResult Display. -
User completes typing in remaining
PrefixesofCalorie,Protein,Carbohydrate,FatPrefixesaccordingly and enters it inCalgo. -
Calgoadds a newFoodentry intoFoodRecordwith paramaters as specified by User.Use case ends.
Extensions
3a. There are similar Food entries in the FoodRecord
3a1. Calgo the similar Food entries in the GUI’s Result Display
Use case resumes from Step 3.
5a. Calgo’s FoodRecord already contains the same `Food entry
5a1. Calgo overrides this existing Food entry with the new Food entry
Use case ends.
Use case: delete an existing Food item in current FoodRecord
MSS
-
User wants to delete a Food entry from the
FoodRecord -
User types in an delete command with the
NamePrefix. -
Calgoshows that theFoodentry that the User wishes to delete exists in one of the similar Food items message in the GUI Result Display. -
User enters the command into
Calgo -
Calgodeletes theFoodentry from theFoodRecord.Use case ends.
Extensions
3a. The Food entry that the User wishes to delete does not exists in the FoodRecord.
3a1. GUI Result Display shows that there are no similar Food items in the FoodRecord.
Use cases resumes from Step 3.
Use case: set a daily goal
MSS
-
User enters the
goalcommand with the intended value. -
Calgo updates the user’s
goalto the new value provided by the user.
Use case ends.
Extensions
1a. User enters an invalid input for the goal.
1a1. Calgo shows a message indicating the acceptable range of values for the goal command.
1a2. User enters goal command with a new value.
Steps 1a1 and 1a2 are repeated until user enters a valid input.
Use case resumes from step 2.
Use case: generate a report on a specific date.
MSS
-
User enters the
reportcommand with a particulardate. -
Calgo analyses the
Foodconsumed on thatdateand generates areporttext file in thedata/reportsfolder for the user.
Use case ends.
Extensions
1a. There is no Food consumed on the inputted date.
1a1. Calgo shows a message indicating that there was no Food consumed on the given date.
Use case ends.
1b. Inputted date in wrong format.
1b1. Calgo shows a message indicating the correct format for the date.
1b2. User enters report command with the date in the correct format.
Use case resumes from step 2.
1c. User enters report command without setting a daily calorie goal.
1c1. Calgo generates a report without the sections related to the goal.
Use case ends.
Use case: consuming food on a on a specific day with nom.
MSS
-
User wants to record their
Foodconsumption on a particular day. -
User enters
nomcommand with the appropriatePrefixesand values. -
Calgo processes the command and update display.
Extensions
2a. User misspells the command or Prefix
2a1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.
2b. The Food does not exist in FoodRecord
2b1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.
2c. User enters invalid value for particular Prefix
2c1. A message prompting the user to enter a valid input is shown.
Use case resumes from Step 2.
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 1000
Fooditems 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.
-
Calgo should work on both 32-bit and 64-bit environments.
-
The minimum screen size for the App window to fully display its GUI is 1250 x 600.
-
Calgo should be designed for a single-user (i.e. Calgo should not be a multi-user App).
-
The product should be developed incrementally over the project duration.
-
The software’s codebase should adhere to OOP.
-
The product should have minimal network usage. Therefore, it is expected that users will find out about the respective nutritional values of a
Foodentry whenever they want toupdateit into theFoodRecordfor the first time.
Appendix E: Glossary
- Application User Interface (API)
-
A set of tools for building software application.
- Command Line Interface (CLI)
-
Text-based user interface used to view and manage computer files.
- Food
-
Fooditems entered by the user to represent a real life Food. This contains nutritional values of each of theirCalories, number of grams ofProteins,Carbohydrates andFats. They can also contains a series ofTags. - Food Entry
-
An entry in the GUI’s
Food Recordbox, which shows all details for oneFoodobject. - FoodRecord
-
The accumulated list of all
Foodobjects entered by the user. - Food Record
-
The GUI’s
Food Recordbox, which shows all details for everyFoodentry. - GUI
-
The Graphical User Interface of Calgo.
- Mainstream OS
-
Windows, Linux, Unix, OS-X.
- Nutritional Information
-
Refers to
Calories,Proteins,Carbohydrates andFats. - OOP
-
Objected-Oriented Paradigm.
- Prefix
-
A set of characters placed before a parameter when entering a command.
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
-
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 contacts. The window size may not be optimum. -
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the App by double-clicking the jar file.
Expected: The most recent window size and location is retained.
F.2. Getting help
-
Displays a guide for all commands. Can also display only commands containing the given command word.
-
Prerequisites: Launch
Calgosuccesfully. -
Test case:
help
Expected: A help window pops up and shows you how to use each command. -
Test case:
help abcd
Expected: A help window pops up and shows you how to use each command anyway, but tells you abcd does not exist.
F.3. Creating a Food
-
Adding a
Fooditem to theFoodRecord -
Prerequisites: Launch
Calgosuccessfully -
Test case
update n/Apple cal/50 p/3 c/2 f/45 -
Expected: Updated all foods into Food Record: Apple Calories: 50 Proteins (g): 3 Carbohydrates (g): 2 Fats (g): 45
-
Test case:
update x -
Expected:
Invalid command format. update: Updates the food entered into the Food Record. If the food entered already exists, it will be overwritten by input values. Parameters: n/NAME cal/CALORIES p/PROTEIN c/CARBOHYDRATE f/FAT [t/TAG]… Example: update n/Kiwi cal/150 p/2 c/25 f/3 t/Green t/Sweet -
Other incorrect commands to try:
update n/Apple c/2 f/45(where parameters are missing)
Expected: Similar to previous.
F.4. Editing a Food
-
Editing a
Fooditem in theFoodRecord -
Prerequisites: Launch
Calgosuccessfully andFoodalready exists inFoodRecord. -
Test case
update n/Apple cal/53 p/3 c/3 f/45 -
Expected:
Updated all foods into Food Record: Updated existing food item in Food Record: Apple Calories: 50 Proteins (g): 3 Carbohydrates (g): 3 Fats (g): 45 -
Test case:
update x -
Expected: Invalid command format. update: Updates the food entered into the Food Record. If the food entered already exists, it will be overwritten by input values. Parameters: n/NAME cal/CALORIES p/PROTEIN c/CARBOHYDRATE f/FAT [t/TAG]… Example: update n/Kiwi cal/150 p/2 c/25 f/3 t/Green t/Sweet
-
Other incorrect commands to try:
update n/Apple c/2 f/45(where parameters are missing)
Expected: Similar to previous.
F.5. Deleting a Food
-
Deleting a
Fooditem from theFoodRecord-
Prerequisites: Launch Calgo successfully and a
Fooditem Apple already exists inFoodRecord -
Test case:
delete n\Apple
-
-
Test case:
delete 0
Expected: No food is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
delete,delete n/Banana(whereFoodbanana does not exists inFoodRecord)
Expected: Similar to previous.
F.6. Listing all Food entries
-
Listing down all entries, regardless of previous commands
-
Prerequisites: Launch
Calgosuccessfully. -
Test case:
list
Expected: The GUI will show allFoodentries existing in theFoodRecord.
F.7. Navigating Food record
-
Searches through the Calgo’s Food entries and displays relevant ones based on the specifications entered.
-
Prerequisite: Launch
Calgosuccesfully, andFoodbeing searched exists inFood Record -
Test case:
find n/Apple
Expected:Foodhaving name that partially match "Apple" will be displayed -
Test case:
find t/sWeethaving tag that partially match "sweet" will be displayed
Expected: `Food -
Test case:
find t/swEeT n/Apple
Expected: Please specify 1 and only 1 correct parameter for filtering using the find command. -
Other incorrect commands to try:
find n/
Expected: Names should only contain alphanumeric characters and spaces, and it should not be blank.
F.8. Adding Food to consumption record
-
Adds a Food to a specific day’s Consumption Record.
-
Prerequisite: Launch
Calgosuccesfully, andFoodbeing consumed exists inFood Record -
Test case:
nom n/chicken d/2020-03-04 portion/1.5 r/8
Expected: Successfully consumed Chicken Calories: 32 Proteins (g): 20 Carbohydrates (g): 1 Fats (g): 11 -
Test case:
nom n/chickn d/2020-03-04 portion/1.5 r/8
Expected: You can’t eat that because it does not exist in food record. -
Other incorrect commands to try:
nom n/chicken d/2020-03-04 portion/-1 r/8
Expected: Portion should be a positive number.
F.9. Removing a portion of Food from consumption record
-
Deletes a portion of a specific Food from the Consumption Record.
-
Prerequisite: Launch
Calgosuccesfully, andFoodbeing consumed exists inConsumption Record -
Test case:
vomit num/1 d/2020-03-04
Expected: Successfully throw up Chicken Calories: 32 Proteins (g): 20 Carbohydrates (g): 1 Fats (g): 11 -
Test case:
vomit num/
Expected: Position should be a positive integer! -
Other incorrect commands to try:
vomit num/-1
Expected: Position required an integer within range of list!
F.10. Navigating Consumption Record
-
Deletes a portion of a specific Food from the Consumption Record.
-
Prerequisite: Launch
Calgosuccesfully, and have eaten something on the day you want to browse. -
Test case:
stomach d/
Expected: Display all food consumed. (As long as you ate on that day before, applies even if your record is empty due to using vomit) -
Test case:
stomach d/1930-04-01
Expected: Your consumption record is empty because you have not consumed food on 1930-04-01 before
F.11. Setting a goal
-
Sets your daily calorie goal.
-
Prerequisite: Launch
Calgosuccesfully. -
Test case:
goal 69
Expected: That is a really low goal to set. Warning: You may suffer from malnutrition. We’ll accept this now because Calgo will eventually help you to reach a daily calorie count of 1200, which is the minimum calories you should eat to stay moderately healthy. -
Test case:
goal 0
Expected: Please key in a whole number that is at least 1 calorie and at most 99999 calories.
F.12. Generating report for the day
-
Generates consumption report for a given date.
-
Prerequisite: Launch
Calgosuccesfully, and have eaten something on the day you want to generate report on. -
Test case:
report d/
Expected: Successfully generated a report in the data/reports folder for the following date: 2020-04-13. -
Test case:
report d/2070-04-12
Expected: Did not manage to generate report. There was no food consumed on 2070-04-12.
F.13. Generating a copy of your Food Record
-
Generates a neat and editable file containing the current Food entries.
-
Prerequisite: Launch
Calgosuccesfully -
Test case:
export
Expected: Successfully generated FoodRecord.txt in the data/exports folder.
F.14. Clearing Food Record data
-
Clears all food entries from Calgo. Note that data in Consumption Record is not deleted.
-
Test case:
clear
Expected: Food Record has been cleared! Use the update command to add new food into your Food Record.
F.15. Saving data
-
Dealing with missing/corrupted data files.
-
Calgo will start from a fresh state if your files are corrupted.
-
Warning: You can edit the
.jsonfiles in the/datafolder. Be careful, if the files you edit ends up with invalid format, you risk losing all existing data.
Appendix G: Effort
G.1. Challenges and Difficulties
At the start, all of us were very new to software engineering projects. Hence, the learning curve was very steep. Because of this, most of the time, we were very confused. However, we demonstrated good teamwork because we always met often and helped each other out by explaining frameworks and teaching each other on software development tools like Git, Intellij and JavaFX.
Due to the COVID-19 situation, there was a lot of uncertainty and our style of meetings were significantly affected. However, everyone demonstrated good attitude and the team was full of good sports, so we covered each other’s weaknesses and supported one another, therefore being able to realise a strong team potential.
G.2. Effort put in by the team
-
3-4 meetings weekly on average
-
Many unrecorded hours were put in for self-learning and managing the project.
-
We placed a lot of emphasis on brainstorming our features and implementing them to make it user-centric. For e.g. a real-time suggestion feature
G.3. Achievements
-
Product Design
-
Our team successfully morphed AB3 and its relevant tests into the Calgo you see today.
-
Our team’s project idea was validated and appreciated by peers and tutors, most notably from our CS2101 presentation, CS2103T demo and PE-Dry Run testing. It also has potential to be collaborated with other peer projects such as FitBiz (Group F11-2).
-
-
Implementation
-
Ambitious in experimenting with new interesting features. For e.g. intelligent insights and graphs.
-
Implemented features consistently and incrementally, allowing us to make changes to past features and
-
Experimented with new workflow before deciding on one, Agile(Scrum), which we liked the most.
-
Put in additional effort in making the GUI different and novel, even though it is not part of the grading rubric. This is another example of how we go the extra mile to make our product more user-centric. For e.g. we tried out new JavaFX APIs like LineChart and TableView.
-
-
Project Management
-
Predominantly followed the forking workflow.
-
Diligently created issues and assigned them on GitHub, while also consistently communicating with each other on Telegram.
-
Planned and incrementally implemented our user stories throughout different milestones.
-
Regularly reviewed each other’s code on and off Github.
-