By: Team-F13-2 Since: Sep 2019 Licence: MIT

1. Introduction

$aveNUS is a desktop financial planning app that allows NUS students to plan their meals according to the amount of budget they set aside per meal in NUS. With $aveNUS, you can more effectively manage your expenses, save your favourite meal options within NUS, edit meal options, and even receive recommendations for meals that suit your budget.

This Developer Guide allows other developers to understand the design principles as well as implementation decisions behind $aveNUS. With this guide, developers can understand and contribute to $aveNUS easily.

The table below provides a quick summary of the symbols and formatting used in the guide.

code

Command that can be typed into the command box

Success execution of command

Tips that might be useful

Additional information that is good to know

Important pointers to take note

2. Setting up

2.1. Prerequisites

  1. JDK 11 or above

  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

2.2. Setting up $aveNUS in your computer

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings.

2.3. Verifying the setup

  1. Run the seedu.savenus.Main and try a few commands

  2. Run the tests to ensure they all pass.

2.4. Configurations to do before writing code

2.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

2.4.2. Updating documentation to match your fork

After forking the repo, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level3 repo.

If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level3), you should do the following:

  1. Configure the site-wide documentation settings in build.gradle, such as the site-name, to suit your own project.

  2. Replace the URL in the attribute repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

2.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your Personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

2.4.4. Getting started with coding

When you are ready to start coding, we recommend that you get some sense of the overall design by reading about $aveNUS’s architecture.

3. Design

3.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram

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

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

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

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

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

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

Each of the four components

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

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

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

How the architecture components interact with each other

The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete 1 command

The sections below give more details of each component.

3.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, FoodListPanel, PurchaseListPanel, SavingsHistoryPanel, StatusBarFooter, etc. All these, including the MainWindow, inherit from the abstract UiPart class.

There are pop-up windows that appear when user calls the InfoCommand or HelpCommand which is a new window with their own stages. The CSS files for each pop-up windows is taken from the MainWindow which ensures that they always match the theme of the MainWindow should the user decide to switch the theme while either pop-up window is open.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Responds to the user’s keyboard and mouse input.

  • Pop-up windows respond to the buttons on the menu tab.

  • Button on the menu tab which acts as substitute for keyboard input for single-word commands.

  • Each usage of the menu tab buttons will update the Food list accordingly.

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

3.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

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

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

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

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

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

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

3.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Menu data.

  • stores the Wallet data.

  • stores the Info data.

  • stores the SavingsAccount data.

  • stores the PurchaseHistory data.

  • exposes the RemainingBudget and DaysToExpire that can be 'observed' e.g. the UI can be bound to these values so that the UI automatically updates when the data in the Wallet change.

  • exposes the Savings that can be 'observed' e.g. the UI can be bound to these values so that the UI automatically updates when the data in the SavingsAccount changes.

  • exposes an unmodifiable ObservableList<Food>, ObservableList<Savings> and ObservableList<Purchase> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in either of the lists change.

  • does not depend on any of the other three components.

3.4.1. Food component in Model

The Food,

  • stores information on Name, Price and Category which are compulsory fields.

  • stores information on Description, Opening Hours and Restrictions which are optional fields.

  • stores information Tag which are retrieved from the UniqueTagList.

Structure of the Food Component

BetterModelClassDiagram

As a more OOP model, we can store a Tag list in Menu, which Food can reference. This would allow Menu to only require one Tag object per unique Tag, instead of each Food needing their own Tag object.

When implementing Food-related models, it is important not to add duplicate foods. As the user only requires to enter the name, price and category as they are compulsory fields, we need to ensure that this does not happen.

For example, you would not want to add Chicken Rice twice into the food list. As a result, our team went through several alternatives to deal with this consideration.

Table 1. Design considerations of the for Food model
Alternative 1 (Chosen Implementation) Alternative 2

equals(Food food) method should rely on Name, Category, and have an additional check for equality of Location, Opening Hours and Restrictions

  • Pros:

    • Multiple foods with different Location, Opening Hours and Restrictions are allowed.

    • Multiple food with same Price and Category but different Name are also allowed.

  • Cons:

    • Harder to Implement.

equals(Food food) method should return true if the foods have same name, price or category.

  • Pros:

    • Easier Implementation

    • Ensures that the compulsory fields are always distinct

  • Cons:

    • Multiple foods with different Location, Opening Hours and Restrictions are not allowed.

    • Multiple foods with same Category or Price are not allowed too.

We chose alternative 1 because there can be multiple instances of Food with different Name but same Category. For example, an instance of Chicken Rice from a Chinese stall may be different from an instance of Chicken Rice from an Indian stall. As a result, we would want to be able to add these 2 food items into the food list.

3.5. Storage component

StorageClassDiagram
Figure 8. Structure of the Storage Component

API : Storage.java

The Storage component,

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

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

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

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

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

3.6. Common classes

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

4. Implementation

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

4.1. Logging

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

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

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

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

Logging Levels

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

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

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

4.2. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

4.3. Sorting feature

The Sorting feature allows users to sort their food items based on certain FIELD and DIRECTION. The FIELD and DIRECTION are as followed:

  1. FIELD : NAME, DESCRIPTION, PRICE, CATEGORY, LOCATION, OPENING_HOURS, RESTRICTIONS.

  2. DIRECTION : ASC or DESC.

The FIELD and DIRECTION can be entirely in Upper or Lower Case.

First and foremost, users will be able to sort the food items based on their default ordering. The default ordering is based on ascending price, name and then category. This is done using the default command.+

Not only that, they will be able to implement their own custom comparator using makesort. From this, they should be able to use customsort and autosort`.
customsort sorts the food items based on the custom comparator, where autosort sorts the food items every time there is an edit to the food list.

4.3.1. Classes for sorting feature in Model

The Sorting feature was implemented with a new set of classes introduced to the Model.

SortingClassDiagram
Figure 9. Sorting class diagram in Sort component.

From the model, the CustomSorter stores the comparator for autosort and makesort.

From which, you will need to call makesort FIELD DIRECTION to create the custom comparator. The CustomSorter contains a FoodComparator.

The FoodComparator stores fields which will be needed for various Food to be compared. The DefaultComparator helps to sort food items based on their natural ordering. This is called via the default command. When default is called, the food list will be sorted according to ascending category, ascending name and ascending price.

Detailed below are the design considerations taken into account when engineering the DefaultComparator and FoodComparator classes.

Table 2. Design considerations of `
Alternative 1 (Chosen Implementation) Alternative 2

DefaultComparator should extend from FoodComparator

  • Pros:

    • Duplicate Code is not needed.

    • This from of relationship follows Liskov Substitution Principle, as DefaultComparator can be easily substituted by an instance of FoodComparator.

  • Cons:

    • Necessary to specify fields while constructing the DefaultComparator.

DefaultComparator and FoodComparator should be standalone classes.

  • Pros:

    • Both can implement their own compareTo() methods.

  • Cons:

    • This Implementation is wasteful as duplicate code is written.

    • Makes it complicated for future developers when they wish to change the DefaultComparator.

As a result, we have chosen alternative 1. By specifying the specific fields in the body of constructor in the DefaultComparator, it makes it easier for future developers to create their own form of DefaultComparator. Not only that, it saves time and reduces duplicate code.

4.3.2. Creation of new Custom Comparator

The user may wish to create a new custom comparator. This can be done with the makesort command.

The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the makesort command is shown below.

MakeSortSequenceDiagram
Figure 10. Sequence diagram for sample makesort command

The user may want to create a new custom comparator. This can be done with the makesort command.

The fields are stored in the FoodComparator in the CustomSorter object, which stores the necessary fields as a list.

The current implementation for creating a new CustomSorter is done by overwriting the existing CustomSorter, with a new CustomSorter with the desired fields.

The command is read as a text string from the command box in the UI and then is executed by calling MainWindow#executeCommand(), which passes this string (named commandText) to the Logic component by calling Logic#execute(Model model).

The following activity diagram below summarizes how the save command works

MakeSortActivityDiagram
Figure 11. Activity diagram for a sample save command

If fields are given, the original CustomSorter is overridden by a new CustomSorter with new fields. Else, the original CustomSorter is overridden by a new CustomSorter with no fields.

4.3.3. Sorting of the Food list

After the customization of the Custom Comparator, the user can now call the customsort command to sort the food items based on the fields specified. For example, if the fields specified are PRICE ASC NAME DESC, the system will sort the food items in order of ascending price. If two items have the same price, they will be ranked according to their names using lexicographic comparison.

SortingActivityDiagram
Figure 12. Activity diagram for a sample sort command

If two foods have the same price, they will compared using lexicographical ordering based on their names. If they are lexicographically similar, their ranks do not change.

Table 3. Ranking of food items

Name

Price ($)

Ranking (Price)

Overall Ranking

Chicken Rice

2.80

2

2

Nasi Lemak

2.80

2

3

Fried Rice

4.00

4

4

Dim Sum

1.80

1

1

From the table above, we can see Dim Sum will be ranked at the top as it is the cheapest. On the other hand, Fried Rice will be ranked at the bottom as it is the most expensive food item.

As Chicken Rice and Nasi Lemak have the same price, they will compared using lexicographical ordering based on their names. Chicken Rice still has a higher ranking than Nasi Lemak as Chicken Rice is lexicographically smaller than Nasi Lemak.

4.4. Savings feature

The Savings feature encompasses a Savings Account feature and a Savings History feature, both of which work together to allow the user to track his/her savings effectively.

The Savings Account feature allows users to save a specified amount of money from their wallet into their savings account using the save AMOUNT command, where AMOUNT is a user defined amount of money to deposit into the user’s savings account. The feature also allows the user to withdraw money from his/her savings account, direcly into his/her $aveNUS wallet. This is accomplished using the withdraw AMOUNT command.

Meanwhile, the Savings History feature is also introduced to allow $aveNUS users to keep track of all their savings and withdrawals. This is available by clicking the "Savings" tab located below the Savings Account and Wallet display.

Also, users can choose to view their savings or withdrawals only, by calling the show TO_DISPLAY command. This allows the user to have a quick overview of his savings/withdrawals history to decide if he/she has been saving enough or not. TO_DISPLAY must either be savings/ withdrawals/both, whereby the TO_DISPLAY input is case-insensitive, i.e. show SaVinGs and show savings are valid inputs.

4.4.1. Classes for Savings Account feature in Model

The Savings Account feature was implemented with the introduction of a new set of classes into the Model. A new SavingsAccount class encapsulates all the methods and classes related to this feature. The SavingsAccount object is stored in $aveNUS and it exposes only a read-only interface, ReadOnlySavingsAccount, to allow other components to retrieve the retrieve information within the user’s SavingsAccount, while maintaining data integrity.

SavingsAccount contains a single instance of the CurrentSavings class, a class we implemented to allow greater abstraction of the user’s current savings value.

CurrentSavings is only called once throughout the application and is modified with package-protected methods within the class itself. This allows the CurrentSavings itself to be unmodifiable in other components in the application except within the SavingsAccount itself.

Within CurrentSavings, there is an unmodifiable Money class. Once a CurrentSavings object is created with a fixed value, it cannot be changed.

The SavingsAccount Class Diagram below shows the relationship between the classes mentioned above.

SavingsAccountClassDiagram

4.4.2. Classes for Savings History feature in Model

Also, the Savings History feature was implemented with another set of classes introduced to the Model. It includes a new SavingsHistory class, which contains all the methods related to this feature. The SavingHistory object exposes only a read-only interface, ReadOnlySavingsHistory, to allow other components within the application to retrieve the savings history of the user.

Within the SavingsHistory object, it contains all the methods to interact with the SavingsHistoryList object. Hence, Each SavingsHistory object only has ONE unmodifiable SavingsHistoryList object.

The SavingsHistoryList class acts as an abstraction of a list, which sole purpose is to store all Savings made by the user.

Withdrawals and savings made by the user are both objects of the class Savings. Within the Savings class, there are 3 unmodifiable attributes:

  • Money which is a class that contains the value of the Savings made. It is a negative value if a withdrawal is made, and a positive value if a saving was made.

  • TimeStamp which is a class that contains the time in which the Savings object was created, i.e. what time the saving/withdrawal was made by the user.

  • isWithdraw which is a boolean that is true if the Savings object is a withdrawal, and false if the object is a saving.

The SavingsHistory Class Diagram is shown below to reflect the interactions above.

SavingsHistoryClassDiagram

4.4.3. Making a saving in $aveNUS

When a user requests for a saving to be made in the application by calling the save AMOUNT command, the following processes will occur internally within the application:

  • The AMOUNT specified by the user is deducted from the user’s wallet.

  • The AMOUNT specified by the user is added into the user’s saving account.

  • The saving is reflected in the savings history.

If the user enters an AMOUNT that is negative, a ParseException is thrown. This exception is thrown from ParserUtil#parseSavings(savings).
Deduction of savings amount from user’s wallet

A Savings object is instantiated in the SaveCommand with the user’s input. It contains information about how much Money was saved, and the TimeStamp of the savings made. The Money object contains the amount of savings made, and the TimeStamp object contains the time and date the saving was made by the user.

When SaveCommand#execute() is called, model#depositInSavings(savingsAmount) is called. Within the Model of $aveNUS, the user’s wallet is checked. If the user attempts to save more money than he/she has in his/her wallet, they will be prompted that the command is invalid.

If the check passes, the saving amount is deducted from the user’s account within the Model.

Adding of savings amount into user’s savings account.

Subsequently after the savings amount is deducted from the wallet, savingsAccount#addToSavings(savings) is called in the Model class. This method call will result in the addition of the savings into the SavingsAccount.

Adding of the savings into the user’s saving history

After the savings is added into the user’s savings account, model#addToHistory(savingsAmount) is called, and the details of the savings are added into the SavingsHistoryList, found within the SavingsHistory object.

The following sequence diagram shows how the making of a saving works:

SaveSequenceDiagram

The following activity diagram summarises what happens when a user executes a SaveCommand:

SaveActivityDiagram
Design Considerations

Detailed below are the design considerations taken into account when engineering the model#depositInSavings(savingsAmount) function.

Table 4. Design considerations of the model#depositInSavings(savingsAmount) function.
Alternative 1 (Chosen Implementation) Alternative 2

Make CurrentSavings a standalone class that does not extend Savings class

  • Pros:

    • CurrentSavings can have its own set of methods that make it unmodifiable. Savings on the other hand is modifiable internally.

    • More efficient as the number of method calls to retrieve the value of the user’s savings is reduced.

  • Cons:

    • Does not maintain good abstraction of CurrentSavings since logically, it is a type of savings.

Make CurrentSavings extend the class Savings

  • Pros:

    • Maintains better abstraction since in reality, a current saving is a type of savings.

  • Cons:

    • Decreases the efficiency of getting the value of the savings, since it would mean increase coupling of classes, i.e. calling methods from Savings and Money classes to obtain the savings amount.

    • Savings is a modifiable class internally. Better data integrity will be ensured if the CurrentSavings remain a view-only class.

4.4.4. Making a withdrawal from user’s savings account.

Similar to making a saving, the following steps occur when the user requests for a withdrawal.

  • The AMOUNT specified by the user is deducted from the user’s savings account.

  • The AMOUNT specified by the user is added into the user’s wallet.

  • The withdrawal is reflected in the savings history.

If the user enters an AMOUNT that is negative, a ParseException is thrown. This exception is thrown from ParserUtil#parseSavings(savings).
Deduction of withdrawal amount from user’s savings account.

A Savings object is instantiated in the WithdrawCommand with the user’s input. Because a withdrawal is subtracted from the savings account, the withdrawal is a Savings object recorded with a negative value.

It contains information about how much Money was withdrawn, and the TimeStamp of the withdrawal made. The Money object contains the amount to withdraw, and the TimeStamp object contains the time and date the withdrawal was made by the user.

When WithdrawCommand#execute() is called, model#withdrawFromSavings(savings) is called. Within the Model of $aveNUS, the user’s savings account is checked. If the user attempts to withdraw more money than he/she has in the savings account, they will be prompted that the command is invalid.

Once the check is passed, a check is done in the wallet described in the section below.

Adding of savings amount into user’s wallet.

Before the savings amount is deducted from the savings account, a check is done to make sure that the addition of the withdrawal into the user’s wallet will not result in the budget amount in the wallet to be great than $1,000,000.

Only when this check is passed, the savings amount is added into the user’s wallet, using the command wallet#setRemainingBudget(newRemainingBudget). The savings amount is subsequently deducted from the savings account using the command savingsAccount#deductFromSavings(savings). Note again that the savings is of a negative value since this is a withdrawal.

Adding of the withdrawals into the user’s saving history

After a successful deduction from the savings account, model#addToHistory(savingsAmount) is called, and the details of the savings are added into the SavingsHistoryList, found within the SavingsHistory object.

Design Considerations

Detailed below are the design considerations taken into account when engineering the Savings class.

Table 5. Design considerations of the Savings class.
Alternative 1 Alternative 2 (Chosen Implementation)

Make a Savings class and another Withdrawals class, both extending an abstract class called SavingsItem.

  • Pros:

    • Maintains better abstraction since Savings and Withdrawals should implement similar methods and have similar attributes. They are simply opposites of each other.

    • Allows Money to always be non-negative, since that is reasonable as in reality, there is no negative Money object, i.e. there are no negative dollar bills.

  • Cons:

    • Implementation of the SavingsHistory and SavingsHistoryList becomes complicated since both Savings and Wtihdrawals have to be included within the same SavingsHistoryList.

    • Converting the user’s savings history from the the Jackson File to a SavingsHistory object would be more complicated, because through this implementation, we were trying to maintain a non-negative money object, but the withdrawal is saved with a negative value.

    • To read back the withdrawal from the Jackson file (savings.json) for savings account, an additional boolean might have to be saved persistently into the user’s hard disk to differentiate savings from withdrawals. This is necessary since the value saved will always be positive in this implementation.

    • Implementation of JsonAdaptedWithdrawals and JsonAdaptedSavingsItem would be required, and complicated conversions between SavingsItem to the subclasses of Savings and Withdrawals have to be done.

Make a Savings class that has a Money object which can be modified internally.

  • Pros:

    • Easier implementation since only Savings will be added to the SavingsHistoryList.

    • Easier to identify withdrawals in the Jackson file for the user’s savings account, since the withdrawal is simply the saving with a negative value in it’s savingsAmount field.

    • No need to maintain 2 separate list for the user’s savings history.

    • Reading back from Jackson file for savings account is more direct as everything is converted back to a JsonAdaptedSavings object, which is then used to create a new Savings object within the application.

    • We can still add a boolean field within the Savings to identify if it is a savings/withdrawal efficiently, however, there is no need to save this boolean persistently in the Jackson File.

  • Cons:

    • Does not maintain abstraction because in reality a withdrawal is not a saving.

    • Methods have to be called to convert the withdrawal amount to a positive amount in order to pass the checks that have been put in place to identify if a Money object has a valid value. We cannot change these checks since the Price object (introduced in Section 4.5, “Budget Tracking feature”) and did not intend to implement new ones just for the withdrawal.

    • Money should not be of a negative value since in reality that is illogical.

4.4.5. Viewing only the savings/withdrawals made by the user.

Upon the request of the user to display only the savings he/she makes using, for example, show savings, a ShowCommand object is created. It is then executed in the application’s Logic, and this returns a CommandResult which is instantiated with two booleans: showSavingsOnly and showWithdrawalsOnly.

Within the MainWindow class of the application, these booleans are checked upon the calling of mainWindow#executeCommand(commandText). In the example of the show savings input by the user, showSavingsOnly will be true and showWithdrawalsOnly will be false.

This leads to the call to ReadOnlySavingsHistory#getSavingsOnly() method within the ReadOnlySavingsHistory class. This call returns an unmodifiable list of savings made by the user thus far.

Finally, the savings history panel in the MainWindow class is updated with this unmodifiable list. The result is that the user will see only his savings being displayed within the application’s savings history panel.

4.5. Budget Tracking feature

The Budget Tracking feature allows users to manage their budget for food expenditure. It keeps the user updated with regards to the amount as well as the duration left for their current budget. Budget information is also used in the application’s recommendation system to suggest appropriate food items within a user’s budget.

4.5.1. Classes for Budget Tracking feature in Model

The Budget Tracking feature was implemented with the following classes.

WalletClassDiagram
Figure 13. Wallet-related class diagram in Model component.
Wallet

Wallet is a class containing the user’s current budget, i.e., storing the user’s budget amount and budget duration.

RemainingBudget

RemainingBudget stores a user’s remaining budget amount. It contains an ObservableValue which allows the UI to track of the amount of money left in the current budget after changes are made to it. The maximum budget amount has been set to 1 million dollars.

Also, in order to avoid possible loss of precision errors, a Money class is used. This design decision is discussed below.
DaysToExpire

DaysToExpire stores a user’s remaining budget duration. It contains an ObservableValue which allows the UI to track of the number of days before the current budget expires after changes are made to it. The maximum budget duration has been set to 1 year (365 days).

When a DaysToExpire object is instantiated, it stores the system time as a field. The budget duration is correspondingly updated based on the difference between this saved time and the actual system time.

Due to development constraints, we have decided to only update the budget duration every time the application is started up instead of constantly monitoring the actual time to update this property.
Design considerations

Below are some design considerations when implementing Budget-related models.

Table 6. Design considerations of the for RemainingBudget model
Alternative 1 (Chosen Implementation) Alternative 2

Creating a Money class which makes use of Java’s BigDecimal class to store budget amount

  • Pros:

    • Avoid precision loss when adding or subtracting from the budget amount

    • Money class serves as a reusable class to store money amounts and can be used in other parts of the application such as in the Savings feature

  • Cons:

    • Increased coupling due to the additional classes used.

Making use of Java’s primitive double to store budget amount

  • Pros:

    • Easier implementation

    • Decreases the number of dependencies of RemainingBudget

    • Can make use of inbuild DoubleProperty to display to GUI

  • Cons:

    • Loss of precision when adding or subtracting from the budget amount

We chose alternative 1 mainly due to the fact that the loss of precision is extremely apparent to the user. From our testing, after the user buys 3 or 4 items, the precision error adds up resulting in a difference of 1 cent between the expected and actual budget amount. This may confuse the user and is a bug that we wish to avoid.

4.5.2. Setting a Budget

A budget can be set using the budget command which takes a budget amount, and an optional budget duration.

For example, budget 100 10 will set the user’s budget, with $100.00 for 10 days.

The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the buy 1 command is shown below.

BudgetSequenceDiagram
Figure 14. Sequence diagram for budget 100 10 command

The following activity diagram below summarizes how budget commands works

BudgetActivityDiagram
Figure 15. Activity diagram for a budget command

4.6. Purchase Tracking feature

The Purchase Tracking feature allows users to monitor their food expenditure by keeping track of their purchase history. Whenever a user’s executes the buy command, the corresponding food will be added as a purchase to the purchase history. A user’s purchase history is displayed in the UI, similar to the food menu, and is updated every time a change is made to it.

4.6.1. Classes for Purchase Tracking feature in Model

PurchaseHistoryClassDiagram
Figure 16. Purchase-related class diagram in Model component.

The Purchase Tracking feature was implemented with the following classes.

ReadOnlyPurchaseHistory

ReadOnlyPurchaseHistory is an interface implemented by PurchaseHistory to allow the other components to retrieve the purchase history data of the user while maintaining data integrity.

PurchaseHistory

PurchaseHistory contains a PurchaseHistoryList and serves as an encapsulation for Purchase-related methods and fields.

PurchaseHistoryList

PurchaseHistoryList contains an ObservableList of Purchase made by the user.

Purchase

Purchase contains a Food object representing the purchased food item, and a TimeOfPurchase representing the time of purchase.

TimeOfPurchase

TimeOfPurchase stores the time the Purchase was made by the user.

Design considerations

Below are some design considerations when implementing Purchase-related models.

Table 7. Design considerations of the for Purchase model
Alternative 1 (Chosen Implementation) Alternative 2

Storing the purchased food item as a Food object in the Purchase object

  • Pros:

    • Easier to implement

    • No need to do additional input validation to ensure validity of Purchase and can instead rely on the input validation provided by Food.

    • Allow for future functionality extension by storing all the information of the purchased Food to be accessed when needed.

  • Cons:

    • Increased coupling due to the dependency created between Purchase model and Food model.

Creating an additional immutable PurchasedFood class to store information regarding the purchased Food object.

  • Pros:

    • Can choose to only include relevant information such as the food name and price while cutting out other irrelevant parts.

    • Remove the dependency between Purchase model and Food model

  • Cons:

    • Creates another dependency between Purchase and PurchasedFood

    • Harder to implement, additional classes would have to be written to allow for serialisation as well.

We chose alternative 1 due to the fact that Food is implemented as a immutable class. Therefore, we are able to get the required from the Food object the user selected to construct the new purchased Food object. And at the same time, ensure the data integrity of that information. Also, by encapsulating the Food object within the Purchase object and making it a private field, we are able to ensure that the purchased Food is not modified in an unintended manner.

4.6.2. Buying a Food Item

The user may want to buy a food item and record the purchase. This can be done with the buy command.

After the users enters a buy command, for example, buy 1 which buys the first item in the displayed list.

The sequence diagram for interactions between the Logic, Model and Storage components when a user executes the buy 1 command is shown below.

BuySequenceDiagram
Figure 17. Sequence diagram for buy 1 command

The following activity diagram below summarizes how buy commands works

BuyActivityDiagram
Figure 18. Activity diagram for a buy command

4.7. Recommendation feature

The recommendation feature allows users to generate a list of personalized recommendations using the recommend command. The recommendations are tailored to each user based on the factors below:

  1. The user’s likes and dislikes

  2. The user’s current budget and date to expiry of budget

  3. The user’s purchase history

Users are able to add their liked and disliked categories, tags and locations into the app using the like and dislike command. The recommendation system will then take into account these preferences, in addition to their purchase history, to generate a more accurate list of recommendations. Users will then able to find the food that they are likely to enjoy more accurately.

4.7.1. Classes for recommendation feature in Model

With the addition of the recommendation feature, a new set of classes were implemented to support the feature.

A singleton class RecommendationSystem encapsulates the methods and classes related to this feature. This RecommendationSystem class implements the interface Recommender which specifies the behaviour of the system, and include the ability to generate recommendation values.

Additionally, the RecommendationSystem class also contains a class UserRecommendations, which include the likes and dislikes of the user. The functionality of UserRecommendations is specified by the Recommendations interface, encompassing the ability to add and remove likes and dislikes, among other features.

The UserRecommendations object contain 6 sets, namely:

  1. A set of user’s liked categories

  2. A set of user’s liked tags

  3. A set of user’s liked locations

  4. A set of user’s disliked categories

  5. A set of user’s disliked tags

  6. A set of user’s disliked locations

The items stored in the sets are first converted to lowercase before adding them so that a case-insensitive comparison can be done more easily.

The class diagram below illustrates the relationship between the classes.

RecommendationClassDiagram
Figure 19. Recommendation class diagram in Model component

4.7.2. Adding likes and dislikes

The feature of adding the user’s liked and disliked categories, tags and locations was introduced to support the recommendation system.

The UserRecommendations class stores the list of user likes and dislikes as a set in lowercase to prevent duplicates. Furthermore, the user’s likes and dislikes are integrated with Storage for the user’s convenience.

The sequence diagram below shows how a sample like command is executed:

LikeSequenceDiagram
Figure 20. Sequence diagram for a sample like command

The following activity diagram below summarizes how the like command works:

LikeActivityDiagram
Figure 21. Activity diagram for a sample like command

A similar execution sequence is performed for a dislike command by replacing instances of like with dislike and vice versa.

4.7.3. Displaying recommendations

After customizing the user’s likes and dislikes, users can obtain a personalized list of recommendations using the recommend command.

Each food is passed to the recommendation system and it calculates the recommendation value based on the tables below:

Table 8. Bonuses applied by the recommendation system
Condition 1 Condition 2 Bonus

Food tags match the user’s liked tags

1 or more tags

+0.05 and +0.03 per matching tag

3 or more tags

+0.10 and +0.03 per matching tag

5 or more tags

+0.25 and +0.03 per matching tag

Food category matches the user’s liked categories

N/A

+0.15

Food location matches the user’s liked locations

N/A

+0.10

Food tags match the user’s tags in purchase history

N/A

+0.01 per matching tag

Food category matches the user’s categories in purchase history

N/A

+0.02

Food location matches the user’s locations in purchase history

N/A

+0.03

Food purchase is found in user’s purchase history

2 or more purchases

+0.10

5 or more purchases

+0.30

10 or more purchases

+0.50


Table 9. Penalties applied by the recommendation system
Condition 1 Condition 2 Penalty

Food price is out of the user’s current budget

N/A

Removes the item from the recommendation results

Food tags match the user’s disliked tags

1 or more tags

-0.10 and -0.10 per matching tag

2 or more tags

-0.30 and -0.10 per matching tag

3 or more tags

-0.50 and -0.10 per matching tag

Food category matches the user’s disliked categories

N/A

-0.40

Food location matches the user’s disliked locations

N/A

-0.30

Food purchase is found in user’s purchase history

Within a time period of <2 days

Applies a decreasing penalty from -10 which diminishes to 0 after 2 days

The activity diagram below shows how a sample Food is passed into RecommendationSystem to output a recommendation value:

RecValueActivityDiagram
Figure 22. Activity diagram for RecommendationSystem#calculateRecommendation

The calculated recommendation values are used to sort the food items when the recommend command is executed. Food with similar recommendation values are sorted based on their price.

4.7.4. Design Considerations

Detailed below are the design considerations taken into account when engineering the Recommendation System.

Design Considerations of RecommendationSystem class

Explained below are our considerations when designing the RecommendationSystem class.

Table 10. Design considerations of RecommendationSystem class
Alternative 1 (Chosen Implementation) Alternative 2

Implement RecommendationSystem a singleton class

  • Pros:

    • Versatile and easy to implement

    • Enforces the Singleton property

  • Cons:

    • Testability of class decreases

    • Hidden dependencies are introduced into the class

Implement RecommendationSystem as a normal class

  • Pros:

    • Testability of class would be easier

  • Cons:

    • Hard to bring together the classes required by RecommendationSystem

We decided to go with alternative 1 and implement RecommendationSystem as a singleton because of the fact that it exists as a global class, encapsulating many seemingly unrelated classes such as Budget, PurchaseHistory and UserRecommendations in order to produce the required recommendation value.

Implementing RecommendationSystem as a singleton would be much simpler than linking together these seemingly unrelated classes as discussed in alternative 2.

Additionally, there should only be one unique RecommendationSystem and UserRecommendations in the application due to our design requirements. To ensure the above requirements are met, we have decided to implement RecommendationSystem as a singleton class.

Furthermore, we have thoroughly tested the implementation of RecommendationSystem and UserRecommendations to mitigate the downsides introduced when implementing a singleton.


Design Considerations of RecommendationSystem#calculateRecommendation() function.

Explained below are our considerations when designing the RecommendationSystem#calculateRecommendation() function.

Table 11. Design considerations of the RecommendationSystem#calculateRecommendation() function
Alternative 1 (Chosen Implementation) Alternative 2

Declare a static method calculateRecommendation to calculate the recommendation value of each Food before sorting them.

  • Pros:

    • No extra field required for each Food item

    • Easier implementation and more straightforward

  • Cons:

    • Sorting by recommendation value may be less efficient as the recommendation system has to calculate the recommendation value of each Food item when the comparator is called

Have a field (e.g. recommendationValue) for each Food item

  • Pros:

    • Sorting by recommendation value would be more efficient as the recommendation value would be stored in each item

  • Cons:

    • The recommendation value of each Food must be updated before every command to ensure validity of the recommendation value

    • An extra field recommendationValue has to be added to each Food

We chose alternative 1 because of its simplicity of implementation. Adding a hidden recommendationValue field for each Food might not be the best solution in the design of our application. A recommendationValue might not make sense in the context of a Food object and thus it may not be justifiable to force a connection between the two for the sake of efficiency.

Furthermore, we do not have to manually update the recommendation of each Food item before every command with this design.

4.8. Alias feature

The alias feature allows users to generate their own unique aliases for commands in $aveNUS. This can be done using the alias command.

4.8.1. Classes for alias feature in Model

With the addition of the alias feature, a new set of classes were implemented to support the feature.

A class AliasList encapsulates methods and classes related to this feature. It contains a list of AliasPair which stores the alias word, if any, mapped to the command word in the AliasList.

Additionally, an AliasChecker is implemented to help check the validity of the AliasPair in the AliasList. Finally, a CommandWordAdder was implemented such that on the creation of a new AliasList, all command words in $aveNUS will have their mapped alias words removed, being replaced by an empty String.

The class diagram below illustrates the relationship between the classes.

AliasClassDiagram
Figure 23. Class Diagram for Alias feature.

4.8.2. Mapping an alias word to a command word.

The main command behind the alias feature is alias. The user can assign an alias word to a command word in $aveNUS, as long as the alias word is alphanumeric.

For example, alias sort s sets the alias word for the command word sort to become s.

The sequence diagram below shows how a sample alias command is executed.

AliasSequenceDiagram
Figure 24. Sequence Diagram for sample alias command.

The following activity diagram below shows how the alias command works.

AliasActivityDiagram
Figure 25. Activity Diagram for sample alias command.

4.9. Info Feature

The info feature allows users to view more information about a particular command. This can be done through calling the info command.

4.9.1. Classes for info feature in Model

In order to support the info command, new classes were added that correspond to each command in the app. This is to allow for easy access and also to better organize the information to be displayed for each commands.

These classes only has public fields without any methods as it is only needed by InfoWindow to access the information

Here are some example classes: AddInfo, AliasInfo, AutoSortInfo, BudgetInfo

Each of these classes contains these fields:

  1. Command Word

  2. Information

  3. Usage example

  4. Expected Output

Each of these fields have to be manually typed as it acts like an in-built user guide for the users who do not have access to internet connection, but would still like to know about the command.

4.9.2. Opening info card about a command

Command word to open up a new InfoWindow is info. Users are able to view the InfoWindow for each and every command as long as the command exist within the app (There exist the class for such info card in the model).

For example, info add will open up a new InfoWindow about the command add.

The sequence window below shows how a sample info command is executed:

InfoSequenceDiagram
Figure 26. Sequence diagram for sample info command

The following activity diagram shows how the info command works.

InfoActivityDiagram
Figure 27. Activity diagram for sample info command

4.9.3. Design Considerations

Detailed below are our considerations when creating the Info feature.

Design considerations of InfoCommand class

Explained below are our consideration when designing the InfoCommand class.

Table 12. Design considerations of the InfoCommand class
Alternative 1 (Chosen Implementation) Alternative 2

Returning a specific message for each input command to create specific CommandResult which can be handled accordingly in MainWindow.

  • Pros:

    • This greatly decreases chances of different input command returning the same CommandResult due to standardized success message, output message, etc.

    • This enables similar commands to have similar or standardized output message without having to worry about causing a bug with the info feature.

  • Cons:

    • Accommodating newly-added command might be messy and complicated as new field will have to be created plus new object to be returned when execute() is called.

Simply returning the input command’s output message.

  • Pros:

    • Makes it easier to accommodate newly-added commands as no new field or object needs to be created.

  • Cons:

    • Might cause bug where different command open the same info card due to equal output message.

    • Will have to ensure that every command have a different output message.

    • Similar commands will not be able to have standardized output message.

We decided to go with alternative 1 and implemented InfoCommand class to just return a specific message for each and every input command. Not only that this is much safer but this also allows similar commands to have standardized output message e.g. like command and dislike command.

The hassle of having to create new field and object to be returned for every new command added is mitigated by the fact that we will not have to worry about similar output message for the commands resulting in a bug in the info feature.

Additionally, this implementation also allows a unique output message to be displayed on the ResultDisplay which definitely adds more personality to the program and allows a better, more informative feedback to user to be displayed.

Basically the benefits of this implementation as mentioned, greatly outweighs the cost it takes to implement it and subsequent command addition would be much easier as it’s just repeated routine.

Design considerations on how to display the command’s information

Explained below are our consideration when designing on the best way to display the command’s information.

Table 13. Design considerations of the way to display information
Alternative 1 (Chosen Implementation) Alternative 2

Creating InfoWindow that is opened every time the info command is used.

  • Pros:

    • Allow more information to be displayed about a particular command.

    • Allow developers to design how they want the information to be displayed.

  • Cons:

    • Need to create new window and carefully engineer the proportion to prevent truncation, etc.

    • User might not like the app opening up a new window every time the info command is called.

Displaying the information on the ResultDisplay.

  • Pros:

    • Safer as it eliminated bugs from having to create a new window.

    • Makes the application less-messy as there are less windows opened.

  • Cons:

    • The small ResultDisplay component means that not much information can be displayed.

    • Displaying a large amount of information will require the user to slowly read it through the small ResultDisplay tab which will not be comfortable to the users.

    • This implementation prevents us from freely designing the information displayed as we are only limited to designing the information in text form.

We decided to go with alternative 1 where we just create a new window called InfoWindow to display the information. Firstly, this allows us to format the information in a way to make it easy for the users to understand. Secondly, we are able to fit so much more information in the new window and even possibly image guides which would definitely help users understand the commands better.

Displaying the information on the ResultDisplay simply would not be helpful enough to the users especially with information-heavy commands such as add and edit which would definitely not fit the small ResultDisplay screen.

4.10. Theme Feature

The theme feature allows users to change the look of the app. This can be done through calling the theme command.

4.10.1. Changing the theme of the app

Command word to change the theme is theme. Currently there are only two themes available which are light and dark theme. As such, the only valid commands are theme dark or theme light.

The sequence window below shows how a sample theme command is executed:

ThemeSequenceDiagram
Figure 28. Sequence diagram for sample theme command

The following activity diagram shows how the theme command works.

ThemeActivityDiagram
Figure 29. Activity diagram for sample theme command

4.10.2. Design Considerations

Detailed below are our considerations when creating the Theme feature.

Design considerations on how to implement the theme change

Explained below are our consideration when designing method to change the theme.

Table 14. Design considerations of the theme change feature
Alternative 1 (Chosen Implementation) Alternative 2

Using CSS styling and just simply changing the StyleSheet for every theme call

  • Pros:

    • Adds more customization and much easier implementation.

    • Minimize the chance of missing an UI component as the entire CSS StyleSheet can just be easily copy-pasted.

    • Better organization of the different themes.

  • Cons:

    • Many components of the CSS StyleSheet are repeated and hence taking up unnecessary space.

Creating a class that automates the theme change according to color scheme.

  • Pros:

    • Can easily add more themes as we only need to input the color schemes.

  • Cons:

    • Less customization ability.

    • More prone to bugs.

    • Different themes might look very similar due to similar implementation.

We decided to go with alternative 1 and utilize StyleSheet instead of creating a class that automates the theme change. This greatly improve the customization between themes and much easier to implement. THe greater customization option allows for different themes to look distinct and also look better.

This implementation also allows for better organization of the themes which makes it easy to keep track of the different themes available. The space taken up by repeated components is not that heavy since the app itself is light.

5. Documentation

5.1. Introduction

We use asciidoc for writing documentation.

We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

5.2. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

5.3. Editing Diagrams

See UsingPlantUml.adoc to find out how to create and update the UML diagrams in the developer guide.

5.4. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

5.5. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 30. Saving documentation as PDF files in Chrome

5.6. Site-wide Documentation Settings

The build.gradle file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.

Attributes left unset in the build.gradle file will use their default value, if any.
Table 15. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items.

not set

5.7. Per-file Documentation Settings

Each .adoc file may also specify some file-specific asciidoc attributes which affects how the file is rendered.

Asciidoctor’s built-in attributes may be specified and used as well.

Attributes left unset in .adoc files will use their default value, if any.
Table 16. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

5.8. Site Template

The files in docs/stylesheets are the CSS stylesheets of the site. You can modify them to change some properties of the site’s design.

The files in docs/templates controls the rendering of .adoc files into HTML5. These template files are written in a mixture of Ruby and Slim.

Modifying the template files in docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

6. Testing

6.1. Running Tests

There are two ways to run tests.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean test (Mac/Linux: ./gradlew clean test)

See UsingGradle.adoc for more info on how to run tests using Gradle.

6.2. Types of tests

We have three types of tests:

  1. Unit tests targeting the lowest level methods/classes.
    e.g. seedu.savenus.commons.StringUtilTest

  2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
    e.g. seedu.savenus.storage.StorageManagerTest

  3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
    e.g. seedu.savenus.logic.LogicManagerTest

6.3. Troubleshooting Testing

Problem: Keyboard and mouse movements are not simulated on macOS Mojave, resulting in GUI Tests failure.

  • Reason: From macOS Mojave onwards, applications without Accessibility permission cannot simulate certain keyboard and mouse movements.

  • Solution: Open System Preferences, click Security and PrivacyPrivacyAccessibility, and check the box beside Intellij IDEA.

testfx idea accessibility permissions
Figure 31. Accessibility permission is granted to IntelliJ IDEA

7. Dev Ops

7.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

7.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

7.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

7.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

7.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

7.6. Managing Dependencies

A project often depends on third-party libraries. For example, $aveNUS depends on the Jackson library for JSON parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives:

  1. Include those libraries in the repo (this bloats the repo size)

  2. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

  • Has a need to manage a significant number of food items

  • Has trouble tracking expenditure over a period of time

  • Wants to know how much money they have saved

  • Wants meal recommendations within their specified budget

  • Prefer desktop apps over other types

  • Can type fast

  • Prefers typing over mouse input

  • Reasonably comfortable using CLI apps

  • Able to read graphs and data.

Value proposition: manage expenditure and get recommendations faster than a typical mouse/GUI driven app

Appendix B: User Stories

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

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

* * *

forgetful user

track my expenditure for the day using the app

know how much I have already spend in the day and plan for the remaining meals of the day.

* * *

greedy user

have reminders when I am about to hit the limits I set for my expenditure

plan my budget better for the weeks ahead.

* * *

meticulous user

sort the food items based on categories

see which items are the best or worst based on certain categories.

* * *

new user

view more information about command

learn how to use the app and its features.

* * *

user

add a food item by specifying the item, price, description and category

update the food items that are available to me if I find new food item options within NUS.

* * *

user

know the total amount I have spent

plan my finances for the remaining days of the month.

* * *

user

only be able to see what I can afford for meals and beverages

save time scrolling through meals that fit my budget.

* * *

user

possess the ability to update existing food entries

update the existing food entries if there are changes in their prices/descriptions.

* *

advanced user

shorten my commands

type faster and more efficiently.

* *

careful user

have a calendar function

keep track of the progress of my spending for the current month

* *

japanese food lover

prioritise Japanese food options over other similarly priced products

find specific food types of our choice/cravings we have.

* *

lazy user

have autocomplete

find food items without having to type long keywords.

* *

lazy user

load and save data from other computers

transfer data to an application onto another desktop.

* *

lazy user

obtain a recommended meal plan according to a specified daily allowance

save the time of having to plan for my meals for the day.

* *

non-tech savvy user

have an easier way to understand how the works eg. through a video

use the app effectively without having to read long user guides.

* *

slow user

a guided tutorial to bring me through the basic functionality of the program

become more familiar with the program before I start using.

* *

smart user

put aliases to the commands available in the application

personalise the app and use it more effectively.

* *

user

add my savings of the month into a customised fund

purchase rewards/gifts/items that I require when I have saved enough for them.

* *

user

know the opening and closing timings of the food stores in NUS

closed shops are not recommended to me to prevent me from wasting time to travel to these shops.

* *

user with dietary/religious restrictions

exclude meals that do not fulfil my dietary requirements

reduce my options to only meals that I can consume.

*

dyslexic user

an app with easy to read font

use the app comfortably with being hindered by my reading disabilities.

*

forgetful user

save specific meal sets to reuse

save time on inputting my meals daily.

*

user

add the birthdays of my friends

set up reminders to buy gifts for my friends.

*

user

receive sample suggestions and examples to understand how to use the program.

understand how to user the application effectively.

*

user who hates travelling

sort places from the nearest to the furthest from my current location

find food places that are easy for me to get to.

*

user who loves to customize things

have a theme changing function of the app from a list of themes available

personalise the app to a theme that I like.

*

user with ADHD

an app with simple commands and UI

use it comfortably without major distractions.

Appendix C: Use Cases

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

Use case: Add food item

MSS

  1. User requests to add food item, providing details such as price, description, category, location and so on.

  2. $aveNUS adds the food item.

    Use case ends.

Extensions

  • 1a. The details are given in the wrong format or mandatory fields are omitted.

    • 1a1. $aveNUS diplays an error message.

      Use case resumes at step 1.

Use case: Delete food item

MSS

  1. User requests to delete a specific food item in the observable food list.

  2. $aveNUS deletes the food item.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. $aveNUS shows an error message.

      Use case resumes at step 2.

Use case: Search food item

MSS

  1. User requests to search for a food item, providing a search query.

  2. $aveNUS shows a list of filtered food items according to their provided query.

    Use case ends.

Extensions

  • 2a. User requests to add food item to their expenditure from the search results.

    • 2a1. $aveNUS adds the food item to their expenditure.

      Use case ends.

  • 2b. User requests to search without providing any query.

    • 2b1. $aveNUS displays the normal ordering of food items.

      Use case ends.

Use case: Add likes / dislikes

MSS

  1. User requests to add likes or dislikes, providing the list of liked categories, tags and locations.

  2. $aveNUS adds the likes or dislikes into the system.

    Use case ends.

Extensions

  • 1a. The user attempts to add a like that already exists as a dislike, or vice versa.

    • 1a1. $aveNUS shows an error message.

      Use case ends.

  • 1b. The given categories, tags or locations are invalid.

    • 1b1. $aveNUS shows an error message.

      Use case ends.

Use case: Display recommendations

MSS

  1. User requests to list recommendations.

  2. $aveNUS shows a list of recommended food items based on their specified budget.

  3. User requests to add a specific food item in the list into purchased food items.

  4. $aveNUS adds the food item into list of purchased food items.

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. $aveNUS shows an error message.

      Use case resumes at step 2.

Use case: Save money into a savings account

MSS

  1. User requests to add savings to Savings Account.

  2. $aveNUS adds the list of savings in the Savings History display tab.

    Use case ends.

Extensions

  • 1a. The wallet is empty.

    • 1a1. $aveNUS shows an error message to tell the user to topup the wallet.

    • 1a2. $aveNUS displays Savings History display tab.

    • 1a3. $aveNUS displays an empty command box.

      Use case ends.

Use case: Set budget

MSS

  1. User requests to set budget.

  2. $aveNUS calculates the daily budget based on the specified weekly budget.

    Use case ends.

Extensions

  • 1a. The budget set is invalid (such as a negative number).

    • 1a1. $aveNUS shows an error message.

      Use case resumes at step 1.

Use case: Set Aliases

MSS

  1. User requests to set an alias, providing a command word and an alias word.

  2. $aveNUS checks if the command and alias words are valid, and sets the alias word of the command word.

    Use case ends.

Extensions

  • 1a. The alias word given happens to be a command word.

    • 1a1. $aveNUS shows an error message.

      Use case resumes at step 1.

  • 1b. The command word given is not a valid command word of $aveNUS.

    • 1b1. $aveNUS shows an error message.

      Use case resumes at step 1.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS that has JDK 11 installed.

  2. Should be usable by user with novice computing experience.

  3. Should be able to respond to user input within 2 seconds.

  4. Should be able to run fullscreen without any UI issues.

  5. Should be able to read easily by users.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

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

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

  2. Saving window preferences

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

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

F.2. Adding a Food Item

  1. Adding a Food Item

    1. Prerequisites: The food list in the menu has to be empty.

    2. Test case: add n/Chicken Rice p/5.00 c/Chinese
      Expected: A new food item is shown in the food list. Details of the food item will be shown to the user.

    3. Test case: add n/Chicken Rice p/5.00 c/Chinese
      Expected: An error message will appear. The duplicate food item will not be added as the food item has been already added from the first test case.

    4. Test case: add n/Chicken Rice p/5.00
      Expected: An error message will appear. The food needs to have the compulsory field of CATEGORY.

F.3. Deleting a Food Item

  1. Deleting a Food Item

    1. Prerequisites: There has to be a food item in the menu for the user to delete.

    2. Test case: delete 1
      Expected: First food item is deleted from the food list. A confirmation message will be shown to indicate successful deletion.

    3. Test case: delete 0
      Expected: No food item is deleted. Error details shown in the status message.

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

F.4. Adding like and dislikes

  1. Adding likes and dislikes

    1. Test case: like c/chinese t/spicy l/location
      Expected: The provided categories, tags and locations are added into the like list. Details of the list of likes and dislikes are shown in the status message.

    2. Test case: dislike c/chinese (after running the above command)
      Expected: The dislike is not added because the category chinese already exists in the user’s likes.

F.5. Removing likes and dislikes

  1. Removing likes and dislikes

    1. Prerequisites: There has to be a like or dislike in the system for the user to remove.

    2. Test case: removelike c/chinese
      Expected: The category chinese is deleted from the user’s likes. Details of the list of likes and dislikes are shown in the status message.

    3. Test case removelike c/chinese (after running the above command)
      Expected: The command fails because there is no more chinese category present in the user’s likes.

F.6. Clearing likes and dislikes

  1. Clearing likes and dislikes

    1. Prerequisites: Optimally, there should be likes or dislikes present in the system for the user to remove. However, the command still works even if there are no likes or dislikes present.

    2. Test case: removelike ALL
      Expected: All likes are cleared.

    3. Test case removedislike ALL
      Expected: All dislikes are cleared.

F.7. Obtaining a list of recommendations

  1. Obtaining a list of recommendations

    1. Prerequisites: Budget and days to expiry of the budget are set by the user. The budget should also be set high enough such that the user is able to purchase food items when the recommendation system calculates the optimal daily budget.

    2. Test case: recommend
      Expected: A list of recommendations tailored to the user will appear, taking into account factors such as the user’s liked and disliked categories, tags and locations and so on. All factors affecting the recommendation system’s recommendation value are documented in the User Guide and Developer Guide.