By: AY1920S2-CS2103T-F11-2 Since: Feb 2020 Licence: MIT

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 1. Architecture Diagram

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

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

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

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

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

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

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

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

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

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

Each of the four components

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

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

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

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

How the architecture components interact with each other

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

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete-c 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

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

GraphWindow is a separate Stage from MainWindow. MainWindow creates a GraphWindow through a graph command.

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

The UI component,

  • Executes user commands using the Logic component.

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

2.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the FitBizParser 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 or deleting a client).

  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 the list of clients or exercise graphs.

Given below is the sequence diagram for interactions within the Logic component for the execute("delete-c 1") API call mentioned previously.

DeleteSequenceDiagram
Figure 6. Interactions Inside the Logic Component for the delete-c 1 Command

2.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

The Model,

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

  2. stores a ClientInView to represent the current selected Client selected by the view-c command

  3. stores FitBiz

  4. stores Client, Sports, Tag, Schedule and Exercise packages, where Client utilises the latter packages as attributes

  5. exposes an unmodifiable ObservableList<Client> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.

As a more OOP model, we can choose to store a Tag list in FitBiz, which Client can reference. This would allow FitBiz to only require one Tag object per unique Tag, instead of each Client needing their own Tag object. An example of how such a model may look like is given below. For simplicity, we have omitted the rest of the attributes that Client has. Refer back to the diagram above for more details.

BetterModelClassDiagram

2.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 Client, Exercise and Schedule data in json format and read it back.

2.6. Common classes

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

3. Implementation

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

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

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

3.3. Command History --- Aaron Choo

This feature serves to improve the user experience by allowing users to browse and retrieve their previously entered commands using the and arrow keys, similar to what most modern CLIs offer.

3.3.1. Implementation

This command history mechanism is facilitated by the logic class CommandHistory, which controls both the model class CommandHistoryState and the storage utility class StorageReaderWriter.

Behaviour of this feature

The behaviour of this feature has been implemented to mimic most modern CLIs, namely:

  1. The empty string, "", should not be stored in the history

  2. Commands that are similar to the most recently stored command in the history should not be stored (ie. duplicate commands will not be stored)

  3. All other user input, be it valid or invalid commands, should be stored

  4. Number of commands should only be stored up to a well-defined maximum number (100 in this case, for performance reasons discussed in the later section)

  5. Pressing the arrow key should browse backwards towards the least recently entered commands

  6. Pressing the arrow key should browse forwards towards the most recently entered commands

  7. The caret position should be at the end of the command string when browsing the history

  8. Persistent storage of the command history should be supported (ie. a user can quit the app and come back to the same history as his previous usage of the app)

How this feature works

Since all user inputs, be it valid or invalid commands, should be stored, and since detection of the and arrow keys must occur in the JavaFX’s TextField class found in CommandBox, we have decided to let CommandBox directly interact with CommandHistory. In other words, CommandBox will be responsible for calling CommandHistory#addToHistory, CommandHistory#getNextCommand, and CommandHistory#getPreviousCommand. A simplified class diagram of the classes involved in this feature is given below:

CommandHistoryClassDiagram
Figure 9. Class Diagram for Command History
CommandHistory depends on FileUtil only because it uses the static method FileUtil#writeToFile.

In the following sequence diagram, we trace the execution of the classes involved in storing the user command into the command history. For this example, we assume the user is entering the command list-c:

CommandHistorySequenceDiagram
Figure 10. Sequence Diagram for Saving a User Entered Command

When the CommandBox#handleCommandEntered method is called, CommandBox simply gets and passes the user input text from TextField to CommandHistory. CommandHistory then adds this text to the internal list within CommandHistoryState, retrieves the full internal list, converts it to a text-based format, and finally requests FileUtil to save the text-based command history to storage.

How the Command History is persisted on storage

Each command that the user enters is essentially just a normal string. We simply use the utility class FileUtil to write these lines of text to a text-based file command.txt. Note that each new line of text in command.txt represents one single command.

Whenever FitBiz first launches, we will then try to open and read from this same command.txt file. If no such file exists, an empty new file will be created for use in the future.

Even if the storage component somehow fails to work, the command history will still be guaranteed to work, albeit without the storage features. In other words, the CommandHistoryState model will continue to function since it is not dependent nor have any association with the utility class FileUtil. This ensure that the command history for the current usage can at least be used.

3.3.2. Design Considerations

In designing the model CommandHistoryState, we had to decide on the underlying data structure to store the user’s command history. We currently use the Java native ArrayList<String>, where each line of command is stored as an individual entry. Another alternative that we have considered is to store the commands in a LinkedList<String>:

Considerations ArrayList (chosen) LinkedList

Time Complexity

Inserting to the list is O(1).

Removal of the first item is O(n).

Retrieval of any item is always O(1).

Inserting to the list is O(1).

Removal of the first/last item is O(1).

Retrieval of an item that is not the first/last item will require traversal of the list and will be more expensive than O(1).

Ease of Implementation

Indices are concrete numbers and thus, are much easier to manipulate than pointers.

The use of indices are enough to support the retrieval operations needed by this feature and is efficient since retrieval is always O(1).

Pointers are arguably harder to keep track of and might be more difficult to implement.

A custom linked list (as opposed to just using the native Java LinkedList) may have to be developed in order to support the retrieval operations that this feature requires while still keeping the retrieval time complexity to O(1).

In the interest of saving developement time and better code readability, we decided to use an ArrayList to store the commands. Since we have decided to cap the maximum size of the list, should this limit be exceeded, we would then need to remove the first item (or the zeroth index) from the list to free up space. Of course, doing a remove(0) on a n-item ArrayList will require that all remaining items in the list be reassigned to new indices, and thus incur an O(n) time operation. However, we found out through extensive testing that this causes no observable nor significant lag when the maximum capacity is reached.

Moreover, there is also a need to overwrite the whole storage file command.txt whenever this maximum size is reached. Before this maximum size is reached, we can easily append to the existing file the new command that the user has just entered. However, after this limit is exceeded, we must remove the first line stored in command.txt, shift all remaining lines up, and then append that new line. Hard disk operations like writing to storage is many order of magnitudes slower than memory operations like the reassignment of indices as discussed above. Since the much larger bottleneck is in the storage, this effectively nullifies the time complexity comparison that a LinkedList is faster than an ArrayList in removing the first item.

In choosing the maximum size of the command history, we have to take note of some important caveats:

  1. This number must be small enough to not cause the app to lag when the whole history is being written to storage

  2. This number must be big enough to satisfy the user

Ultimately, we felt that 100 is a very generous estimate given that a user really only needs the past few commands at any point of time.

3.4. Command Autocomplete --- Aaron Choo

Similar to the previously mentioned Command History feature, this feature also serves to improve the user experience by allowing users to press the Tab key to autocomplete their partially entered commands.

3.4.1. Implementation

This feature is facilitated by the logic found in the Autocomplete class. Before we dive into the implementation, let us first define what unambiguous and ambiguous commands are:

Unambiguous Commands Ambiguous Commands

Can uniquely identify a single command using the sequence of letters that the user has entered

Cannot uniquely identify a single command using the sequence of letters that the user has entered

For example, assume we only have 3 commands in our app, add-c, add-e, and edit-c. If the user enters e and tries to autocomplete the command using Tab, we say that this is an unambiguous command since clearly, edit-c can be uniquely identified by e. If instead, the user enters a and presses Tab to autocomplete the command, we say that this is an ambiguous command, since both add-c and add-e are possible choices.
Behaviour of this feature

Again, this feature has also been implemented to mimic most modern CLIs, namely:

  1. Any unambiguous commands should be immediately completed upon pressing of the Tab key

  2. Any ambiguous commands should be completed up till the longest common prefix of all similar commands

    • Using the ambiguous command example in the introduction above, when the user enters a and presses Tab, the autocompletion should return add- (the longest common prefix of add-e and add-c) to the user

  3. A list of all similar commands should be presented to the user should he try to autocomplete an ambiguous command

  4. Pressing Tab when the command has already been completed will bring the user’s caret to the next prefix delimitter (/ in our case) with wraparound

How the Trie data structure works

Since Java does not provide a native Trie data structure, we had to implement our own version of it. Moreover, Java also does not allow methods with multiple return values, and thus, we had to create a wrapper class SimilarWordsResult to store the multiple results returned by Trie#listAllSimilarWords. In this section, we shall take a more in depth look at the overall implementation of this data structure.

We first look at the Node class provided in the same package which Trie relies on. Each Node object should contain the following attributes:

  • The parent node (null if the node is the root of the Trie)

  • The current letter it represents

  • The children nodes (if any)

  • A boolean to know whether that node represents a completed word

Since each node stores with it their parent node pointer, we can easily construct the word represented by a node by recursively building the word up letter by letter until the root is reached. This is implemented in Node#constructWord, as shown here:

public String constructWord() {
      if (isRoot()) {
            return EMPTY_STRING;
      }
      return parent.constructWord() + getLetter();
}

Now, let us discuss about how we implemented Trie to support the behaviours discussed above by first looking at Trie#getLongestPrefixNode. This method takes in an argument word and returns in 3 distinct cases:

  1. If the argument word matches no words currently in the Trie: null

  2. If the argument word is unambiguous: the Node whose constructed word (using Node#constructWord) is the longest word contained in Trie that can be formed from word

  3. If the argument word is ambiguous: the Node whose constructed word is the longest common prefix of all words similar to word contained in Trie

Refer to Figure 14, “Activity Diagram for the Autocomplete Logic” given in the next section for the complete sequence of the key decisions.

Let us move on to Trie#listAllSimilarWords which makes use of the Node found by Trie#getLongestPrefixNode. Cases 1 and 2 discussed above are relatively trivial and we shall not discuss about how they are handled in Trie#listAllSimilarWords. For case 3, in order for us to find all the similar words, we have chosen to use a Depth-First Search (DFS) approach, starting the search from the Node returned by Trie#getLongestPrefixNode, as shown here:

Node subtrie = getLongestPrefixNode(word);
ArrayList<String> similarWords = new ArrayList<>();

Stack<Node> stack = new Stack<>();

stack.push(subtrie);

while (!stack.isEmpty()) {
      Node current = stack.pop();
      if (current.isWordEnd()) {
            similarWords.add(current.constructWord());
      } else {
            stack.addAll(current.getChildren().values());
      }
}
The choice of a DFS approach as opposed to a Breadth-First Search (BFS) approach is arbitrary, both should work as expected.
How this feature works

Similar to Command History, this feature also relies heavily on the UI class CommandBox, and thus we have decided to let CommandBox interact with Autocomplete directly. A simplified class diagram of the classes involved is shown here:

CommandAutocompleteClassDiagram
Figure 11. Simplified Class Diagram for Autocomplete
Autocomplete returns an object of type AutocompleteResult to CommandBox when the Autocomplete#execute is called. As such, both Autocomplete and CommandBox depend on, but are not directly associated with, AutocompleteResult. The same reasoning applies for SimilarWordsResult which have been explained in the earlier section.

In the following sequence diagram, we follow the execution for when the user tries to autocomplete his partially entered command gra (which, in the current application, is an unambiguous command, and will result in the full completion of the graph command as well as its prefixes):

CommandAutocompleteSequenceDiagram
Figure 12. Simplified Sequence Diagram for Command Autocomplete

CommandBox retrieves the user input command and caret position from the TextField, and calls the execute method from Autocomplete with these information. This execute method (shown and explained in full in the next sequence diagram) creates an AutocompleteResult object and returns this to CommandBox, which retrieves all the information required and sets the TextField and ResultDisplay accordingly.

CommandAutocompleteSequenceDiagramRef
Figure 13. Sequence Diagram for the Autocomplete#execute Method

Within the execute method, Autocomplete calls the listAllSimilarWords method from Trie with the user input text. Trie, which would already have all the commands stored, finds the longest prefix node, calls the constructWord method from this node, and checks if this node represents the end of a completed word. Since it is indeed a completed word, Trie immediately creates a SimilarWordsResult object to store these information and returns it to Autocomplete. Then, Autocomplete retrieves these information, realises that it is dealing with an unambiguous command, and constructs the corresponding prefixes. It then creates a AutocompleteResult object to store all the information that CommandBox requires, and finally returns this object to CommandBox.

Lastly, in order to explain the key decisions that this feature does at each step starting from the point where the user presses Tab, we have provided the following activity diagram:

CommandAutocompleteActivityDiagram
Figure 14. Activity Diagram for the Autocomplete Logic

This feature currently only supports autocompletion of commands and prefixes, and not other fields/parameters like names and addresses that have been used by the user before. Implicitly, since all commands defined in FitBiz do not have empty spaces in them, this allows us to easily determine when to allow users to press Tab to get to the next prefix (behaviour 4): by simply checking for the presence of white spaces from the trimmed user input (like shown in the activity diagram).

3.4.2. Design Considerations

As discussed in the implementation section, we have decided to use a Trie data structure. Of course, we have also considered other much simpler alternatives like simply storing all available commands in a native Java List. A quick summary of the pros and cons is given here:

Considerations Trie (chosen) List

Time Complexity

Searching if a word exists is O(n), where n is the number of letters in the word to search for.

Finding the longest common prefix of an ambiguous command is O(n), where n is the number of letters in the original word.

Searching if a word exists is O(nm), where n is the number of letters in the word to search for, and m is the number of words in the list.

Finding the longest common prefix of an ambiguous command is not linear with n and m.

Ease of Implementation

Initial development might be more difficult; developers might not be familiar with this data structure as it is not as common.

Custom class means that additional, custom logic can be easily added.

Much faster initial development.

Custom logic cannot be easily added.

As such, the choice of implementing our own Trie data structure is obvious. As this app grows bigger in the forseeable future, the number of commands as well as the number of things we would want to autocomplete would increase. Overall, we felt that the Trie data structure will scale much better as compared to a List.

Exchanging some initial development time for future scalability of our app will ensure that we, or future developers, do not end up wasting time refactoring what could have been done in the first place. Moreover, the Trie data structure is much more effective and computationally inexpensive in finding the longest common prefix of all ambiguous commands. The same cannot be said when using a List.

Also, since we have implemented our own Trie data structure, it would also allow more custom logic to be added later, and allow more creative freedom with respect to the features that we, or future developers would want to add. For example, future version of this application might want to also include the autocompletion of frequently used parameters by the user.

3.5. Scheduling for a Client --- Ng Ming Liang

This feature allows for a user to assign a weekly schedule to a client. Timings are represented in the 24-hour format HHmm. Each client can have none or multiple schedules that do not have overlapping timings. Multiple clients are allowed to have overlapping timings with each other.

3.5.1. Implementation

This scheduling mechanism is facilitated by ScheduleCommand which extends Command. The format of the command is given by:

schedule INDEX sch/DAY-STARTTIME-ENDTIME [sch/DAY-STARTTIME-ENDTIME] …​

When using this command, at least one valid complete schedule parameter must be specified. The user can follow up with additional optional valid schedule parameters in order to assign more schedules to the same client. The following 3 examples are all valid usages of the schedule command:

Example Commands

  1. schedule 1 sch/MON-1100-1300

  2. schedule 2 sch/MON-1100-1300 sch/TUE-1100-1300 sch/SAT-1800-2000

  3. schedule 3 sch/

Elaboration on Example Commands

  1. This command instance schedules Client with index 1 to have a schedule slot on every Monday, 11:00am to 1:00pm.

  2. This command instance schedules Client with index 2 to have schedule slots on every Monday 11:00am to 1:00pm, Tuesday 11:00am to 1:00pm, and Saturday 6:00pm to 8:00pm.

  3. This command instance schedules Client with index 3 to have no schedule slots, that is essentially clearing the schedule of Client with index 3. The sch/ parameter is required when clearing the schedule.

Do note that the schedule parameters given in the schedule command will entirely overwrite the client’s current list of schedules.

The list of schedules of each client are structured as a ScheduleList, which is a wrapper class for an ArrayList of Schedule objects. Each Client contains one ScheduleList attribute to keep track of all Schedule assigned to it. If there are no assigned Schedule for the Client, then the ScheduleList simply contains an empty ArrayList of Schedule.

Schedule comprises three attributes:

  1. Day

  2. StartTime

  3. EndTime

Day wraps the enum DayEnum.Weekday and represents the day of the week the schedule takes place on.

StartTime and EndTime represent the start time and end time of the schedule in the "HHmm" format respectively.

The relations between these classes are shown in the class diagram below.

ScheduleClassDiagram

These attributes are bounded by these characteristics:

  1. Each Client can only contain unique Schedule, that is, there are no overlaps in timings between any two Schedule in the ScheduleList. This is ensured by ScheduleCommandParser#checkIfOverlaps()

  2. Overlapping timings between the Schedule of different Client is allowed

  3. The maximum timeframe between StartTime and EndTime is from 0000 to 2359

  4. StartTime cannot be later than EndTime

  5. Day can only take up the 7 values of the week (MON/TUE/WED/THU/FRI/SAT/SUN)

Here is an activity diagram displaying the steps taken when FitBiz receives a user input for the schedule command:

ScheduleActivityDiagram

In the following sequence diagram, we trace the execution for when the user decides to enter the command schedule 1 day/mon st/1100 et/1200 into FitBiz. For simplicity, we will refer to this command input as commandText:

ScheduleSequenceDiagram

This sequence diagram shows how the schedule command is processed in FitBiz. The LogicManager receives the input commandText and parses it with FitBizParser to obtain arguments that are then parsed into ScheduleCommandParser to construct a ScheduleCommand. This ScheduleCommand is returned back up to the LogicManager which then executes it with reference to the model argument. Subsequently, the Model is updated with a new Client with the schedule changes through a series of commands as shown in the right hand side of the sequence diagram, and control is return back to LogicManager.

3.5.2. Design Considerations

In designing this feature, we had to consider the alternative ways in which we can choose to store the information of a schedule. One option of storing the relevant information (day, start, end times) for a schedule was simply to concatenate these values into a single String, for example, monday-1100-1200. However, we found that this did not exploit the desirable principles of Object-Oriented Programming. As respective sanity checks had to be done for the day and timing, wrapping each of these properties into their wrapper classes allowed for better modularity and organisation of these attributes. For example, Day#isValidDay handles the validation of the input for day and Time#isValidTimingFormat handles the validation of time.

Considerations also then had to be made for how to contain multiple Schedule. The current implementation uses the ArrayList data structure to hold multiple Schedule. Other considered alternative for ScheduleList was HashSet.

ArrayList HashSet

Ensuring no overlaps

Does not ensure that its elements are unique

Ensures no duplicate values

Ensuring order of elements

Elements can be sorted and retrieved in ascending order

Does not return elements in order

Displaying the Schedule Panel

The schedules of all the clients are displayed in a time-sorted manner on the SchedulePanel of the main FitBiz GUI as shown in the picture below, demarcated by the red rectangle:

SchedulePanelGUIExample

The SchedulePanel extends UiPart<Region> and takes in a ScheduleDay class. ScheduleDay is similar to ScheduleList, the difference being:

  • ScheduleDay wraps an ArrayList of Schedule for a specific Day

  • ScheduleList wraps an ArrayList of Schedule for a specific Client

As the nature of the SchedulePanel was to display a sorted collection of Schedule, we chose ArrayList as the underlying data structure, due to the ability to sort the ArrayList via a comparator that compares Schedule according to their Day and StartTime. The code snippet below shows how the Schedule are being sorted using an anonymous comparator in the constructor for ScheduleDay:

this.scheduleList.sort(Comparator.comparingInt(o -> o.getStartTime().getDirectTimeInt()));

In addition, we also harnessed the capability of the HashSet to ensure no overlaps between Schedule within each Client, which is implemented by ScheduleCommandParser#checkIfOverlaps. As the ArrayList of Schedule is being populated in the constructor of ScheduleDay, we used a HashSet to check for any overlapping Schedule. The equals method of Schedule was overriden to consider overlapping timeframes between StartTime and EndTime to be equal.

3.6. Exercise Feature --- Yong Jie

This feature allows users to record the exercises done by a client. The exercises are displayed in a table form, after the view-c command is called.

3.6.1. Implementation

Implementation of Exercise class

The Exercise class is facilitated by the UniqueExerciseList, which is a wrapper class for an ObservableList of Exercise objects. Each Client contains one UniqueExerciseList attribute to keep track of all Exercises the client has.

Exercise comprises five attributes:

  1. ExerciseName

  2. ExerciseDate

  3. ExerciseReps

  4. ExerciseWeight

  5. ExerciseSets

All instances of Exercise of a client will be contained in the client’s UniqueExerciseList. There is an additional class PersonalBest which is also associated to Exercise. It is omitted and will be discussed in the Personal Best Section due to its high significance. Below shows a UML class diagram which shows Exercise class interactions.

ExerciseClassDiagram
Figure 15. Simplified Class Diagram for Exercise

An important point to note about our implementation of Exercise is the method, isSameExercise(). We will consider two Exercise as the same if isSameExercise() returns true.

  1. Executes when adding a new Exercise to client’s UniqueExerciseList. This includes add-e and edit-e.

  2. Checks if the new Exercise is the same with an existing instance of Exercise in the client’s UniqueExerciseList.

  3. Two Exercises are the same does not mean that they are equal. Two Exercise are equal only if all attributes are equal.

Below shows an object diagram of two Exercises that will return true for isSameExercise().

IsSameExerciseObjectDiagram
Figure 16. Object diagram of two instances of Exercise objects

The two Exercise only have different values in ExerciseSets but equal values in:

  1. ExerciseName

  2. ExerciseDate

  3. ExerciseReps

  4. ExerciseWeight

This implementation is chosen as we felt that the user should increment the value in ExerciseSets in the existing Exercise. We followed the same idea as in the real life context and want to combine the sets of exercises with the same name, date, reps and weight.

We do consider that the user might want to record the two instances separately as it might be done at different periods of the day. In the future, when adding a same Exercise, we can implement it such that the sets value of the exising Exercise gets incremented automatically instead of showing an error. For now, this implementation keeps the exercise table neat and compact for the user.

Execution flow of exercise commands

The exercise commands edits the client’s UniqueExerciseList. Currently, there are 3 exercise commands.

  1. add-e : Adds an exercise to a client

  2. edit-e : Edits a client’s exercise

  3. delete-e : Deletes a client’s exercise

The commands follow a similar execution flow as other commands.

ExerciseCommandActivityDiagram
Figure 17. Activity Diagram for exercise commands
  1. The FitBizParser will create the associated exercise command parser. e.g. AddExerciseCommandParser

  2. Using ParserUtil, the parser will extract attribute details from the input and create the Command. e.g. AddExerciseCommand

  3. The exercise Command will be executed and modify the client’s UniqueExerciseList.

    1. Exceptions like no client being viewed and invalid input are thrown here.

  4. The change will be reflected in the exercise table in GUI.

  5. Result box will display success message for the Command.

Adding an exercise to UniqueExerciseList

When adding a new Exercise to UniqueExerciseList, it is important which index it is added. This is such that the exercises for the client will be displayed in descending chronological order in the table after a view-c command. TableView provides sorting for dates. However, having TableView to do the sorting would result in mismatch of indexes of the exercises in the UniqueExerciseList and in the TableView. This will result in problems when using exercise commands that specifies an index like delete-e.

To address this problem, a custom insertion sort has been written in UniqueExerciseList, under the addToSorted(Exercise) method. This method will do a single pass of the internal list to insert the element at the correct position. This assumes that the internal list is initially sorted (which it should be, since reading from storage will do an initial sort on it).

We will use an example of a add-e command to illustrate the execution of addToSorted(Exercise). Consider an instance where user inputs add-e n/pushup d/12-12-2011 reps/20. The sequence diagram below shows the execution flow when the AddExerciseCommand is executed. Details of exception thrown are omitted as this is a postive instance and for simplicity.

AddExerciseCommandSequenceDiagram
Figure 18. Sequence Diagram for AddExerciseCommand

AddExerciseCommand checks if there is an client being viewed. For this instance, we will consider the positive case where indeed there is a client being viewed. AddExerciseCommand will retrieve the client being viewed from the Model so that details like the existing exercise list of the client can be obtained. The sequence diagram illustrates the execution flow of addToSorted(Exercise) to obtain details of the Exercise being added and the existing Exercise in UniqueExerciseList. AddCommand will then check if there is an exercise that we consider as the same in the UniqueExerciseList.

AddExerciseCommandSequenceDiagramPart2
Figure 19. Sequence Diagram for the addToSorted Method

UniqueExerciseList loops through the exercises in the list. and calls getExerciseDate() and getExerciseName(). The LocalDate and exercise name in String type are then used for comparision. The sequence diagram below illustrates the conditional checks during the comparision.

AddExerciseCommandSequenceDiagramPart3
Figure 20. Sequence Diagram for conditional checks in addToSorted Method

The conditional checks are such that UniqueExerciseList maintain sorted by descending chronological order, followed by alphabetical order for exercises with the same dates.

Below shows a code snippet of the conditional checks in addToSorted(Exercise).

int dateComparision = toAddDate.compareTo(currDate);
if (dateComparision > 0) {
    break;
} else if (dateComparision == 0) {
    if (toAddName.compareTo(currName) <= 0) {
        break;
    } else {
        idx++;
    }
} else {
    idx++;
}

Arrays.sort() can be used to sort the exercises in UniqueExerciseList after every addition. However, the worse case time complexity of Arrays.sort() is O(nlogn). This custom insertion sort will guarantee an O(n) time complexity to insert the new Exercise correctly. This is as efficient as it gets as any insertion will already incur an O(n) time complexity to first check if the internal list contains the same exercise. It is important to keep the time complexity low as clients can have many exercises.

3.6.2. Design Considerations

This section explains the our design considerations and analysis for the storage of exercises.

Considerations Store exercises with client and all clients in one JSON file (chosen) Store all exercises into a separate JSON file Store exercises with client but one JSON for each client

Ease of retrieving / storing

Easy to link the exercises to the client

Hard to link the exercises to the client

Hard to identify which JSON file is for which client

Separation of data

Does not keep client and exercise data separate

Keep client data separate from exercise data

Does not keep client and exercise data separate

Might have too many JSON files, one for each client

Size limit of JSON files

High chances of having one large JSON file and potentially exceed the size limit of a JSON file

Low chances of exceeding the size limit of a JSON file

Low chances of exceeding the size limit of a JSON file

Separation of concerns

Non-separate code for reading/storing exercises and clients data

Separate code for reading/storing exercises and clients data

Non-separate ode for reading/storing exercises and clients data

We decided to use the first approach of storing the exercises with the associated client and have all the clients data in one JSON file. Codewise, each JsonAdaptedClient will have a list of JsonAdaptedExercise.

ClientExerciseStorageClassDiagram

We want to keep the implementation of reading and storing of data simple. The first approach is the most simple. When reading the data, it removes the need to associate the exercises to the client. A client might potentially have a large amount of exercises, resulting in the reading process to be extremely slow. Therefore, a bad user experience.

Moreover, storing the exercise data from client data does not provide any performance benefits. Due to time constraints, we decided that the application should store all the data everytime it closes. This is regardless if the particular exercise or client data has been changed. Having to keep track of which data is edited and only overwrite those data would greatly increase the complexity of the application. Therefore, keeping exercises data separate from client data would be unnecessary and provide little additional functionality/benefits to the user.

Lastly, we foresee that it is improbable for the data size of both clients and exercises to exceed the maximum size limit of a JSON file. With the target user in mind, it is unlikely that he will have an enormous amount of clients. The application is meant to be used by a single user and not an organisation. Even though each client might have many exercises, the information of each exercise is relatively small. For now, collectively, the client and exercise data is unlikely to exceed the JSON size limit. We might consider to have multiple JSON files if the data size gets too big in future versions.

Indeed, JsonAdaptedClient having a list of JsonAdaptedExercise would violate separation of concerns. JsonAdaptedClient is now in charge of the client’s information and the exercises. However, we felt that the benefits outweighted the costs and proceeded with the first choice.

3.7. Personal Best --- Li Zi Ying

This feature allows the users (ie. gym managers) to view the personal bests of exercises done by a client. This information is displayed in a table form, after the command view-c INDEX is called.

3.7.1. Implementation

The personal best feature is facilitated by the model PersonalBest, and the logic behind it is in PersonalBestFinder. The behaviour of this feature determines the personal best of each exercise done by the client based on these considerations:

  1. If the ExerciseWeight attribute is recorded in the Exercise, then the ExerciseWeight is used as comparison

  2. If there is no ExerciseWeight recorded in the Exercise, then ExerciseReps will be used as comparison

  3. If neither of ExerciseWeight and ExerciseReps are recorded into the Exercise, then this particular exercise will not be put into the Exercise Personal Best table

    1. However, if the another Exercise of the same name is added in the future with ExerciseWeight and/or ExerciseReps specified, then the personal best of this exercise will still be calculated and shown in the Exercise Personal Best table

  4. Note that ExerciseSets, although an attribute of the Exercise model, is not considered when checking for PersonalBest as the number of sets of an exercise does not contribute to a personal best record

A simplified class diagram of the classes involved in this feature is given below:

PersonalBestClassDiagram
Figure 21. Simplified Class Diagram for Personal Best

In the following sequence diagram, we trace the execution for when the user decides to enter the command view-c into FitBiz:

PersonalBestSequenceDiagram
Figure 22. Sequence Diagram of the View#execute Method for Personal Best

The explanation for the sequence diagram is as follows: when the user inputs view-c, add-e, edit-c or delete-c, PersonalBestFinder#generateAndSetPersonalBest is called, taking the client currently in view as the parameter. PersonalBestFinder#generateAndSetPersonalBest then retrieves client’s list of exercises using Client#getExerciseList and creates a new HashMap, where the key is ExerciseName and the value is Exercise. Then the personal bests of each exercise of the client in view are generated using the above considerations. Finally the list of personal bests is set using PersonalBest#setPersonalBest.

3.7.2. Design Considerations

In designing this feature, we had to decide on the placement of the PersonalBest class in the model to comply with the OOP standards. Currently, the PersonalBest model has a whole-part relationship with Client, with Client being the whole and PersonalBest being a part of Client. The alternative is to consider PersonalBest as a part of Exercise instead.

Table 1. Table of Design Considerations
Put PersonalBest as a part of Client(Chosen) Put PersonalBest as a part of Exercise

Adhering to OOP standards (Coupling and Cohesion)

Increases cohesion as it logically makes more sense, currently each client has a list of exercises to themselves, and thus each client should also have a list of PersonalBest of each of these exercises to themselves

Increases coupling between the logic and model as every time the commands view-c, add-e, edit-e and delete-e are called, the personal best table has to be updated, a new PersonalBest object has to be created. Then the Client will have to be dependent on this PersonalBest object created in the logic component, which causes unnecessary dependencies and higher coupling

Ease of Implementation

Might have significant conflicts as the Client model is changed to include one more attribute

Easier to implemention as methods related to PersonalBest is kept under Exercise model and separate from Client data and methods, so no refactoring is needed

We decided to use the first approach of placing PersonalBest as a part of Client instead of Exercise. There are multiple reasons for our choice as mentioned below.

We want to maintain the OOP structure of the program. Logically, the personal best should belong to the client as the list of exercises belongs to the client. As the list of exercises is unique to every client, the personal best should also be so. We also do not want to increase coupling of the program as mentioned in the table above.

Moreover, even though personal best is generated using the list of exercises in the client, it can be instantiated even without an exercise list. Therefore it does not require the exercise class to exist and does not have a whole-part relationship with exercise. Coupling will also be increased as the client will be relying on the exercise class to generate the personal best. Therefore, the final choice was to place the personal best under client, with every client having their own personal best attribute.

This personal best feature also leads into the Graph feature, which will be discussed in the next section, where we plot a graph of the client’s progress of a specified exercise.

3.8. Graph --- Li Zi Ying

This feature allows users to see the progress graph of the current client in view. The user has to specify the exercise name, the y-axis (either weights or reps), the start date and the end date. There has to be existing exercises in the client’s exercise list for the specified axis and time period for the graph to be plotted, if no graph can be plotted, an error will be thrown.

3.8.1. Implementation

The graph mechanism is faciliated by the model class Graph, which contains the details of the graph. These include ExerciseName, Axis, StartDate and EndDate. The figure below is a UML class diagram to illustrate the Graph model.

GraphClassDiagram
Figure 23. Simplified Class Diagram for Graph

These attributes are bounded by these characteristics: . ExerciseName can only be alphanumeric characters . Axis can either be reps or weight only, case insensitive (sets are not considered due to the same reasoning in the above section) . Earliest StartDate possible can only be one year before the current date and cannot be after EndDate. StartDate also cannot be a future date . Earliest EndDate possible can only be one year before the current date and cannot be before StartDate. EndDate also cannot be a future date

Here is an activity diagram displaying the steps taken when FitBiz receives a user input for the graph command:

GraphCommandActivityDiagram
Figure 24. Activity Diagram for Graph Command

The behaviour of this feature determines the graph plotted of the exercise specified based on these considerations:

  1. If there is no such exercise with the matching ExerciseName in the client’s exercise list from the specified StartDate to EndDate, then the graph cannot be plotted

  2. If the Axis input is reps and the exercise specified does not have any reps input withint the StartDate to EndDate, then the graph cannot be plot

  3. If the Axis input is weight and the exercise specified does not have any weight input within the StartDate to EndDate, then the graph cannot be plot

  4. If all of the above are fulfilled (ie. there is at least one valid exercise with the matching ExerciseName and has weight/reps input depending on the Axis specified), then the graph will be plotted, with each exercise in chronological order

The flow of the program is illustrated using the sequence diagram below:

GraphSequenceDiagram
Figure 25. Sequence Diagram for Graph Command

The explanation is as follows: when the user inputs graph with all relevant arguments input correctly, a new GraphCommand() is created, taking the newly created Graph object as parameter.

GraphCommand#execute() then retrieves the exercise list from the client currently in view and checks if there is at least one exercise with a matching exercise name. If there is no exercise to plot, then an error GraphCommand.MESSAGE_EXERCISE_NOT_IN_LIST will be thrown. Next, the list of exercises to be plot will be generated using Graph#generateGraphList(). Once again, there will be a sanity check to see if the list size is zero, which means that no graph cannot be plotted.

3.8.2. Design Considerations

In designing this feature, we had to decide on the implementation of certain classes like Axis to comply with the OOP standards of Abstraction.

Table 2. Table of Design Considerations
Create enum class Axis Type(Chosen) Check for Axis value using raw types

Adhering to OOP standards (Abstraction)

Increases level of abstraction as there are only two different types of axis that can be chosen

Less abstraction and increases complexity as we will have to check for the equality of the axis type using the equality check for the String raw type

Ease of Implementation

Requires some refactoring to include AxisType class and the relevant getter methods

Easier to implemention as no extra classes or methods are needed, so no refactoring is needed

We decided to use the approach of abstracting the axis types away into AxisType enum class. As the graph implementation will require a substantial amount of equality checks, especially for the attributes of Graph to make sure that we are drawing the correct graph for the user. As such equality checks are made, it makes it difficult to keep checking String equality as regular data types like String would allow invalid values to be assigned to a variable.

As our axis values can only be REPS, WEIGHT or NA, we can check for each case using the switch case method instead of checking for equality using raw types. This is also much more efficient than using multiple if-else statements. For example, in the code snippet below, the method fillSeries() uses switch case statements to add data values depending on the AxisType.

Code snippet
    private void fillSeries() {
        switch (axisType) {
        case REPS:
            fillRepsSeries();
            yAxis.setLabel("Reps");
            break;
        case WEIGHT:
            fillWeightSeries();
            yAxis.setLabel("Weight");
            break;
        default:
        }
    }

Moreover, to keep in line with the OOP standards, we decided that it will be better to abstract away data types like AxisType into its separate class instead of storing it as a raw type in Axis. This ensures the code quality of our program and reduces complexity (especially in terms of equality checking as mentioned above) by abstracting away the more complex details into classes of a lower level. The consideration of abstracting details away is also used for creating StartDate and EndDate classes as attributes of Graph, instead of using the Java in-built LocalDate.

By considering the above two factors, despite having to put in the extra effort to create a new AxisType class and thus requiring extra methods like getters and setters, we decided to move with the approach of creating the AxisType enum class and refactor to accomodate for the additional data type.

3.9. Filtering the list of clients --- Toh Ker Wei

This feature allows users to filter the list of clients by specifying the Tag or Sport of the clients they want to view.

3.9.1. Implementation

This filtering mechanism is facilitated by TagAndSportContainsKeywordsPredicate, that implements Predicate<Client> which is a wrapper class for a boolean. FilterCommand is associated with Model is responsible for calling Model#updateFilteredClientList based on TagAndSportContainsKeywordsPredicate. TagAndSportContainsKeywordsPredicate will call test on Client to check if the clients Tag and Sport contains all the keyword. the relations between these classes are shown in the class diagram below.

FilterClassDiagram

To further elaborate, TagAndSportContainsKeywordsPredicate contains 2 booleans:

  1. hasTag: evaluates if the client has all the Tag specified

  2. hasSport: evaluates if the client has all the Sport specified

If there is no keyword specified for either Tag or Sport, the corresponding boolean will return true. There must be at least 1 keyword specified, regardless of whether it is a Tag or Sport. TagAndSportContainsKeywordsPredicate will then evaluate and return the logical addition of hasTag and hasSport.

In the following sequence diagram, we will be tracing the execution of the command filter t/obese s/swim entered by the user.

FilterSequenceDiagram

3.9.2. Design Considerations

Table 3. Table of Design Considerations
Using separate booleans to check for Tag and Sport keywords (Chosen) Using one boolean to check for all keywords

Ease of Implementation

Checks for client’s Tags and Sports containing keywords can be done separately ensuring that individual results are correct before combining them

Simpler logic but errors are more difficult to pinpoint to either TAG or SPORT

Ease of Expanding Feature

Easier to add new parameters to filter since a separate check will be done before combining with the result of previous checks

Boolean conditions can get very complex and logical error will be prone to occur

We decided to use the first approach of checking if the client contains Tag specified and Sport specified separately.

Firstly, by separating the checks for each attributes, a correct implementation of checking Tag against the keywords will allow us to easily duplicate the logic to be done for Sport. This makes the code easier to debug as we can simply check the hasAttribute boolean to see if it gives the correct value.

Secondly, separating the checks for each attributes will allow us to add attributes of different types stored in different data structure easier. We could simply add another check on the attribute against the keyword specified then do a logical addition of the result against the others.

Therefore, as we foresee us adding more attributes to be filtered increasing the need to ensure logical correctness, the first approach is the most ideal.

3.10. Viewing the information of a client --- Toh Ker Wei

This feature allows users to view the information of a specific client using his INDEX in the clients list. Information displayed includes additional information of the client, exercises done and his personal best of exercises done.

3.10.1. Implementation

The view client’s information feature is primarily facilitated by the model Client. The details for list of exercises done and personal best will be discussed in section 3.7 and not be covered here. The client’s INDEX in the clients list will be used to identify and retrieve his information. Additionally, only when a client’s information is being viewed, graph of his exercises can be plotted.

In the following sequence diagram, we will be tracing the execution when the user enters the command view-c 3

ViewSequenceDiagram

3.10.2. Design Considerations

Choose client to view based on INDEX (Chosen) Choose client to view based on NAME

Adhering to Single Responsibilities Principle

view-c only has to retrieve and display the client based on INDEX entered

view-c has to retrieve clients with the same name and use INDEX to specify the client to view

Ease of Implementation

Easier to implement as MODEL only needs to be accessed once

Harder to implement as view-c needs to return a list of clients with the same name before using INDEX to specify the client

We decided to use the first approach of using the client’s INDEX to view his information.

Firstly, as the client’s INDEX is unique, view-c will only be responsible for retrieving and displaying the client’s information and will not need to resolve clients with the same names.

Secondly, for clients with the same name, INDEX qill be used to specify the client to be view. This causes extra work for the implementation. Furthermore, in cases where users manage many clients and some with same names, there are functions like find and filter which allow users to scope the clients list and easily find the desired client’s INDEX.

Therefore, viewing a client by his INDEX minimises the responsibility of the command and will not need to resolve conflicting clients and is the most ideal.

4. Documentation

Refer to the guide here.

5. Testing

Refer to the guide here.

6. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Target user profile:

  • Has a need to manage a significant number of gym clients and their information (clients' details and exercises)

  • Prefer desktop apps over other types

  • Can type fast

  • Prefers typing over mouse input

  • Is reasonably comfortable using CLI apps

  • Wants to book facilities easily [v2.0]

Value proposition: Keep track of your gym training schedule and clients' exercises 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 …​ I want to …​ So that I can…​

* * *

coach for fitness competitors

record the cliental bests of my clients

monitor their progress

* * *

coach for fitness competitors

record the exercise type and intensity my clients have done for the day

know if they are on track for their competitions

* * *

coach for fitness competitors

record the date and time of my clients’ training sessions and keep track of which day they work out

* * *

coach with many fitness competitors

view my overall schedule for the day/week

* * *

coach that communicates with my clients

display visualisations (graphs/charts)

convey the client’s training progress better

* * *

coach

add new profiles to the app to keep track of new clients

* * *

coach

list all my clients

* * *

coach

edit a client’s details

change and update an existing client’s details

* * *

coach

delete my client

* * *

coach

search my client by typing their name

find my client’s information easily

* * *

coach

add, edit and delete new exercises that are not found in the application

* * *

coach

look for user help

get help on how to use the features

* *

coach with many clients

be reminded of my daily schedule at the start of the day

track my appointments

* *

forgetful coach with many clients

look at my records on clients

know what exercises they are weak in or require more assistance

* *

coach with a tight schedule

display my open slots

plan for training more effectively

* *

coach with many clients

set clientalised goals for my clients

plan a workout routine that is achievable

* *

coach with many different clients

easily export the data of a client (to a CSV file)

backup and store that data in another format

* *

coach

track my clients by using a tag

easily view the clients I want to

*

coach with clients all over SG

find the nearest gym based on where my client stays

*

coach with a tight schedule

view a summary page to present to me just the important data, configurable by me

*

coach

track my total earnings from all my clients

*

coach that likes to vary my clients’ training

choose from a list of different exercises with the same purposes

*

coach for fitness competitors

view incoming competitions of my clients

be reminded to focus on them more

*

coach who wants to visually track the progress of my clients

store photos to monitor the changes in my client’s physique

*

coach

check if the gym I am going to is closed

*

coach

use the timer in the application

seamlessly execute the time interval of the workout planned

*

coach

book the facilities required by the workout

*

coach

see upcoming competitions or meet

plan for my clients to attend them

*

coach for fitness competitors

record the food intake of my clients

know if they are following my diet plan for them

*

coach

monitor my clients caloric intake

know he is meeting his dietary requirements

*

coach

manage the payment fee/payment day of the clients

charge them accordingly

Appendix C: Use Cases

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

Use case 1: Add client

MSS

  1. User requests to add a client

  2. FitBiz requests for details (eg. name, phone number, address, email)

  3. User enters the requested details

  4. FitBiz adds client to database

    Use case ends.

Extensions

  • 3a. The input format is invalid

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new details

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes from step 4

Use case 2: View client

MSS

  1. User requests to view all the available information of client

  2. FitBiz shows a list of clients

  3. User requests to view a specific client in the list

  4. FitBiz shows all available information of the client

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The given index is invalid or out of range

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new index

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

Use case 3: Edit client

MSS

  1. User requests to edit a client’s details

  2. FitBiz shows a list of clients

  3. User requests to edit a specific client in the list and inputs the attributes and values

  4. FitBiz edits client’s details

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The given input is invalid

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new details

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

Use case 4: Delete client

MSS

  1. User requests to delete a client

  2. FitBiz shows a list of clients

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

  4. FitBiz deletes the client

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The given index is invalid or out of range

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new index

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

Use case 5: List clients

MSS

  1. User requests to list all existing clients

  2. FitBiz lists all existing clients

    Use case ends.

Extensions

  • 1a. The input format is invalid

    • 1a1. FitBiz shows an error message

    • 1a2. User provides new input

      Steps 1a1 to 1a2 are repeated until the input entered is correct.

      Use case resumes at step 2.

Use case 6: Add exercise

MSS

  1. User requests to add an exercise to a client

  2. FitBiz shows a list of clients

  3. User requests to add exercise to a specific client in the list

  4. FitBiz adds exercise to the client

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The input format is invalid

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new details

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

Use case 7: Filter clients

MSS

  1. User requests to filter clients based on a keyword in a client’s tag and/or sports

  2. FitBiz filters and displays clients based on specified keywords

    Use case ends.

Extensions

  • 1a. The input format is invalid

    • 1a1. FitBiz shows an error message

    • 1a2. User provides new input

      Steps 1a1 to 1a2 are repeated until the input entered is correct.

      Use case resumes at step 2.

Use case 8: Add schedule

MSS

  1. User requests to add schedule for a client

  2. FitBiz displays a list of clients

  3. Client inputs the schedule for the day or the time specified for a particular client

  4. FitBiz adds and displays the schedule

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The given input format is incorrect

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new input

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

Use case 9: View graph visualisations

MSS

  1. User requests to view graph visualisations of a client’s progress

  2. FitBiz shows a list of clients

  3. User requests to view the specified client in the list by index

  4. Fitbiz shows all available information of the client

  5. User requests to view the graph of the specified exercise in the client’s exercise list

  6. FitBiz displays the graph of the specified exercise

    Use case ends.

Extensions

  • 2a. The list is empty

    • 2a1. FitBiz displays an empty client list

      Use case ends.

  • 3a. The given index is invalid or out of range

    • 3a1. FitBiz shows an error message

    • 3a2. User enters the new index

      Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.

  • 5a. The given input format is incorrect

    • 5a1. FitBiz shows an error message

    • 5a2. User enters the new input

      Steps 5a1 to 5a2 are repeated until the data entered is correct. Use case resumes at step 6.

Appendix D: Non Functional Requirements

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

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

  3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

  4. Should work mostly without the need for the Internet.

  5. Should work reliably.

  6. Should be able to store data in a human-readable format.

  7. Should be for a single user.

  8. Should not use DBMS to store data.

  9. Should not exceed 100Mb in file size.

Appendix E: Glossary

Client

The client of the fitness coach.

Client In View

The client currently in view (after the view-c command is used).

Client List

The GUI section which shows all client currently listed in FitBiz.

Client View

The GUI section which shows all the information of the client in view

Exercise

A workout activity done by a client that can be recorded.

Exercise Table

The GUI section which shows the list of the client in view’s exercises

Fitness Coach

The targer user base of FitBiz.

Graph

The graph of an exercise done by a client (reps/weights vs. date).

Mainstream OS

Any modern OS like Windows, Linux, Unix, or OS-X.

Personal Best

The exercise done by the client with the highest weight (or highest rep if weight does not exist).

Personal Best Table

The GUI section which shows the list of the client in view’s personal best exercises

Schedule

The client’s training schedule per week. Note that one client cannot have overlapping schedules but other client’s schedule can overlap with another client’s.

Schedule Panel

The GUI section which shows the list of all clients' schedules from the current client list

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 without the client view populated with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

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

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

F.2. Command autocomplete

  1. Unambiguous, single command found

    1. Test case: type gr into the command box and press Tab
      Expected: command box shows completed graph n/ a/ sd/ ed/ command, with the caret position right after n/. Result box shows the graph command usage.

    2. Test case: type add-e into the command box and press Tab
      Expected: command box shows completed add-e n/ d/ reps/ ew/ sets/ command, with the caret position right after n/. Result box shows the add-e command usage.

  2. Ambiguous, multiple commands found

    1. Test case: type a into the command box and press Tab
      Expected: command box shows longest common prefix add-, with the caret position at the end of the line. Result box indicates and lists that add-e and add-c commands are found.

  3. No valid commands found

    1. Test case: type Fitbiz! into the command box and press Tab
      Expected: command box and caret positions does not change. Result box indicates that no commands were found.

  4. Prefix traversal when commands are completed with white spaces

    1. Test case: type graph n/ a/ sd/ ed/ into the command box and press Tab
      Expected: caret position travels around all the / with wraparound.

    2. Test case: type /// into the command box and press Tab
      Expected: command box and caret positions does not change. Result box indicates that no commands were found. This is similar to 3, no valid commands found.

  5. Commands with white spaces but without /

    1. Test case: type i <3 2103 into the command box and press Tab
      Expected: command box and caret positions does not change. Result box does not change. Basically nothing happens.

F.3. Deleting a client

  1. Deleting a client while all clients are listed

    1. Prerequisites: At least one client in the list. List all clients using the list-c command.

    2. Test case: delete-c 1
      Expected: First client is deleted from the list. Details of the deleted client shown in the status message.

    3. Test case: delete-c 0
      Expected: No client is deleted. Error details shown in the status message.

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

F.4. Adding an exercise

  1. Adding an exercise to the client in view’s exercise table

    1. Prerequisites: View a client using the view-c command. A list of exercises done by the client can be seen in the exercise table. It is possible to have no exercises recorded at the moment.

    2. Test case: Substitute DATE with the current date
      add-e n/bench press date/DATE reps/30 sets/4 ew/10
      Expected: The added exercise can be seen in the exercise table. A success message stating that there is a new exercise recorded will also be shown in the result box.

    3. Test case: Substitute DATE with the current date
      add-e n/pushup date/DATE
      Expected: The added exercise can be seen in the exercise table. A success message stating that there is a new exercise recorded will also be shown in the result box.

    4. Test case: Substitute DATE with the day after the current date
      add-e n/bench press date/DATE reps/30 sets/4 ew/10
      Expected: No exercise added. An error message stating that the valid range for date will be shown.

    5. Test case: Substitute DATE with the current date
      add-e n/pushup d/DATE reps/ten
      Expected: No exercise added. An error message stating that input for reps have to be a whole number from 1 to 9999 will be shown.

    6. add-e n/pushup
      Expected: No exercise added. An error message stating "invalid command format!" and a description on how to use add-e command will be shown.

    7. Test case: Substitute DATE with the current date
      add-e n/pushup d/DATE sets/4
      add-e n/pushup d/DATE sets/2
      Expected: No exercise added. An error message stating that the exercise already exists and ask user to increment the existing exercise will be shown.

F.5. Saving data

  1. Dealing with missing/corrupted data files

    1. {explain how to simulate a missing/corrupted file and the expected behavior}

F.6. Filtering the client list

  1. Filtering the client list with different Tag and Sport specified

    1. Test case: filter-c t/normal s/hockey
      Expected: Client with Tag normal and Sport listed.

    2. Test case: filter-c s/sumo wrestling
      Expected: Client with Sport sumo wrestling listed.

    3. Test case: filter-c t/normal vegetarian
      Expected: Error stating "Tags names should be alphanumeric" shown.

    4. Test case: filter-c t/normal t/vegetarian
      Expected: Client with Tag normal and vegetarian listed.

F.7. Adding new client into FitBiz

  1. Adding a new client into FitBiz with different parameters stated

    1. Test case: add-c n/John Cena p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 g/Male b/26-01-1980 cw/96 tw/69 h/156 s/Sumo Wrestling t/Vegan t/healthy r/History of back injuries
      Expected: New client added.

    2. Test case: add-c n/Micheal Jordan p/98254 e/mikeyboii@example.com a/basketball court blk 666 #02-25
      Expected: New client added.

    3. Test case: add-c n/Simba p/98543456 e/thelionking@gmail.com
      Expected: Error "Invalid command format!" shown.

    4. Test case: add-c n/Secret Man p/97656561 e/cantseemee@void.com a/here @ dodo coast g/ b/ cw/ tw/ h/ r/ s/ t/
      Expected: New client added.

    5. Test case: add-c n/Rachel tan p/93214321 e/RachelTan@mail.com a/choa chu kang ave 22 blk 909 followed by add-c n/Rachellie Tan p/98786110 e/RachelTan@mail.com a/bishan grove 45
      Expected: Error "This phone number and/or email already exists in FitBiz." shown.

F.8. Scheduling clients

  1. Scheduling a valid client from the current Client List to have 1 schedule

    1. Prerequisites: At least one client in the Client List

    2. Test Case: schedule 1 sch/MON-1100-1200
      Expected: FIRST_CLIENT_NAME 's overall schedule has been changed to: MON Time: 11:00 - 12:00

  2. Scheduling an invalid client index

    1. Test Case: schedule -1 sch/MON-1100-1200
      Expected: Invalid command format!
      schedule: Adds the training schedule of the client, identified by the index number used in the displayed client list. The schedule should include the first 3 letters of the day, start and end time in 24 hour format, in 1-minute denominations. Multiple training schedule can be added to a client.

  3. Multiple scheduling

    1. Prerequisite: At least one client in the Client List

    2. Test Case: schedule 1 sch/MON-1300-1400 sch/TUE-1700-1900
      Expected: Alex Yeoh’s overall schedule has been changed to:
      MON Time: 13:00 - 14:00
      TUE Time: 17:00 - 19:00

F.9. Listing all clients

  1. Using list-c

    1. Test Case: list-c
      Expected: Listed all clients

F.10. Editing a client

  1. Editing a client’s address

    1. Prerequisites: At least one client in the Client List

    2. Test Case: edit-c 1 a/New Address
      Expected: Edited Client: CLIENT INFORMATION

  2. Adding multiple sports to a client

    1. Prerequisites: At least one client in the Client List

    2. Test Case: edit-c 1 s/Tennis s/Soccer
      Expected: Edited Client: CLIENT INFORMATION

  3. Removing client’s sports

    1. Prerequisites: At least one client in the Client List, with existing sports

    2. Test Case: edit-c 1 s/
      Expected: Edited Client: CLIENT INFORMATION

F.11. Viewing a client

  1. Viewing a client while all clients are listed

    1. Prerequisites: At least one client in the list. List all clients using the list-c command.

    2. Test case: view-c 1
      Expected: First client is viewed from the list. Success message shows that you are currently viewing the first client.

    3. Test case: view-c 0
      Expected: No client is viewed. Error details shown in the status message.

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

F.12. Viewing a graph of an exercise

  1. Viewing a graph of the specified exercise while a client is in view

    1. Prerequisities: Have a client currently in view using the view-c command. Have at least one exercise in the exercise table.

    2. Test case: graph n/Push Up a/reps sd/01-01-2020 ed/01-04-2020 command format is correct and no other input issues
      Expected: Graph is displayed and success message is shown.

    3. Test case: graph or any other parameters missing
      Expected: No graph displayed, error stating "Invalid command format!" with the correct usage is shown.

    4. Test case: Correct graph command format but no exercise record within the stipulated timeframe
      Expected: No graph displayed, error stating "Graph cannot be plotted" as there are no records of this exercise within the given timeframe.

    5. Test case: Correct graph command format and existing exercise records but no valid inputs for the given axis
      Expected: No graph displayed, error stating "There is no graph to be plotted for this axis specified" is shown and the user is asked to pick a different axis or exericse.

    6. Test case: Correct graph command format but the start date is chronologically later than the end date
      Expected: No graph displayed, error stating "Invalid command format!" with the correct usage is shown