PROJECT: ZeroToOne

Overview

ZeroToOne is a desktop application that serves as an all-in-one exercise tracker and personal aide. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.

Summary of contributions

  • Major enhancement: added the Exercise Feature

    • What it does: This feature allows users to create, read, update and delete exercises and exercise sets.

    • Justification: The feature formed the base for all the other features to build upon. For example, the Workout feature required exercises to have been created first.

    • Highlights: Since this feature forms the base for all other features, the design of this feature will inevitably affect them, as well as features that will be implemented in the future. As such, I ensured that the feature is compliant with the SOLID priciples. All relevant classes are abstracted and well documented, such that each class only has one single responsibility. As such, this allows the Exercise feature to be extensible by any other developers.

  • Major enhancement: added the Splash Screen

    • What it does: The splash screen will load up first while the application runs through its initialisation process.

    • Justification: This enables users to feel that the application is more responsive, enhancing the user experience of the application.

    • Highlights: While the application runs locally and the loading process is relatively fast right now, the aim of this enhancement is for future-proofing purposes. It allows developers to further extend the application using other external sources such as an external DBMS, while retaining the "responsiveness" of the application.

    • Credits: Some ideas to implement this feature were taken from this tutorial created by Oracle.

  • Code contributed:

  • Other contributions:

    • Project management:

      • Lead release v1.2 on GitHub

      • Explained the original codebase to developers

    • Enhancements to existing features:

      • Headed the initial code refactorisation

        • Replaced all references to AddressBook to ZeroToOne

        • Modularised the inital AddressBook-Level3 codebase that allowed developers to easily extend the project

        • Pull Request #66

    • Documentation:

      • Helped Chi Shan with the editing of the User Guide and Developer Guide to ensure that the English used is friendly and simpler to understand.

      • Edited high-level UML Diagrams of the application to fit the latest update. Pull Request #228

    • Community:

      • PRs reviewed (with non-trivial review comments): #192, #231

      • Number of PRs reviewed: 25

      • Reported bugs and suggestions for other teams in the class (examples: #165, #166, #169)

    • Tools:

      • Added Appveyor and Coveralls Support. Pull Request #12

      • Added githooks support to prevent developers from pushing errorneous code. #193

Contributions to the User Guide

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

Managing Your Exercises [Aloysius Chan]

The commands in this section allows you to manage your customised exercises in ZeroToOne. These exercises will eventually be the building blocks of a workout.

Creating a new exercise

To create a new exercise in ZeroToOne, simply enter this command into the command box:

exercise create e/<exercise_name>
Example use:
exercise create e/squat
UgCreatedExercise
Figure 1. Created Exercise

The newly created exercise will be automatically added to the bottom of the exercise list. This exercise will not contain any sets at this point.

NOTE: <exercise_name> has to be a string, consisting of only alphanumeric characters

Adding a set to an exercise

After you have created a new exercise in ZeroToOne, the next step is to add a set to the exercise! To add a set, simply enter this command:

exercise set add EXERCISE_ID r/<num_of_reps> m/<weight>
Example use:
exercise set add 2 r/2 m/30
UgAddedExerciseSet
Figure 2. Added Exercise Set

The exercise set will be automatically appended to the current list of sets in the exercise. The user interface will be updated to show the edited exercise.

NOTE:

  • This command assumes that you have already created an exercise under EXERCISE_ID. If you have not created the exercise, refer to the section on “Creating a new exercise” first.

  • EXERCISE_ID refers to the index of the exercise in exercise list

  • <num_of_reps> should be a positive integer

  • <weight> should be a positive integer between 1 and 1000

Editing a set in an exercise

Changed your mind on the details of an exercise set? No worries, you can edit the information in an exercise set by simply entering this command:

exercise set edit EXERCISE_ID SET_ID r/<num_of_reps> m/<weight>
Example use:
exercise set edit 1 1 r/20 m/30

The exercise set will be automatically updated in the exercise list. If so, the following message will be displayed in the feedback display:

Edited exercise set: Deadlift

NOTE:

  • EXERCISE_ID refers to the index of the exercise in exercise list

  • SET_ID refers to the index of the set in the exercise

  • <num_of_reps> has to be a positive integer

  • <weight> has to be a positive integer between 1 and 1000

Deleting a set in an exercise

Want to delete an exercise set from the exercise? You can do so by simply entering this command:

exercise set delete EXERCISE_ID SET_ID
Example use:
exercise set delete 1 2

The exercise set will be removed from the exercise, and the view will automatically update to show that the exercise no longer contains that set. If this is successful, the following message will be displayed in the feedback display:

Deleted Exercise Set: Deadlift

NOTE:

  • EXERCISE_ID refers to the index of the exercise in exercise list

  • SET_ID refers to the index of the set in the exercise

Listing all exercises

To show a list of exercises that you have created in ZeroToOne, simply enter this command into the command box:

exercise list
UgExerciseList
Figure 3. Exercise List

The User Interface will automatically switch to the “Exercise” tab, and the result display will automatically update with the list of exercises.

Finding an exercise by name

To find and view the information of a particular exercise that you have previously created, you can simply enter this command:

exercise find e/<exercise_name>
Example use:
exercise find e/Bench Press

The Result Display will automatically update to only show exercises that match the search keyword.

UgFindExercise
Figure 4. Find Exercise

NOTE:

  • <exercise_name> has to be a String, consisting of only Alphanumeric characters

  • <exercise_name> can be a partial substring of the full exercise name

  • <exercise_name> is not case-sensitive

Changing an exercise’s name

Made a mistake while creating the exercise’s name? You can change the exercise name by simply running this command in the command box:

exercise edit EXERCISE_ID e/<exercise_name>
Example use:
exercise edit 1 e/Squat

The exercise in ZeroToOne will be automatically updated to show its new name. If this is successful, the following message will be displayed in the feedback display:

Edited exercise: Squat

NOTE:

  • EXERCISE_ID refers to the index of the exercise in exercise list

  • <exercise_name> has to be a String, consisting of only Alphanumeric characters

Deleting an exercise

Want to remove an exercise from ZeroToOne? You can do so by entering this command into the command box:

exercise delete EXERCISE_ID
Example use:
exercise delete 1

The exercise will be removed from ZeroToOne. At the same time, all current workouts that contain this exercise will also have this exercise removed. If this is successful, the following message will be displayed in the feedback display:

Deleted Exercise: Deadlift

NOTE:

  • EXERCISE_ID refers to the index of the exercise in exercise list

Contributions to the Developer Guide

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

Exercise [Aloysius Chan]

Overview

The Exercise feature forms the basic building block for the application. It allows users to CRUD exercises, which will then be used for the creation of workouts. The command is prefixed by the keyword exercise. Available commands are create, edit, list, delete and many more.

Implementation

The following portion will explain in-detail each component of the Exercise feature.

Model
ExerciseModelClassDiagram
Figure 5. Exercise Model Structure

The figure above depicts the Model of the Exercise feature. Starting from the most primitive type:

  1. NumReps stores the number of repetitions a person has to complete for a particular exercise set

  2. Weight stores the weight in kg that a person has to complete for a particular exercise set

  3. ExerciseSet represents an exercise set that a person has to complete. Each ExerciseSet comprises of exactly 1 NumReps object and 1 Weight object

  4. ExerciseName stores the name of a particular exercise

  5. Exercise represents a collection of exercise sets that a person has to complete. Each Exercise will consists of exactly 1 ExerciseName object and any number of ExerciseSet (including zero)

  6. UniqueExerciseList represents a collection of Exercise objects. There can be any number of Exercise objects in UniqueExerciseList, but they must be unique.

  7. ExerciseList implements the interface ReadOnlyExerciseList that ensures that the outward-facing exercise list is unmodifiable by other modules. The ExerciseList object has exactly 1 UniqueExerciseList for storage purposes.

  8. ExerciseList is controlled by the ModelManager.

Do note that some of the Exercise objects in the ExerciseList are also referenced in the WorkoutList. For more information, refer to the Workout implementation.

Storage
ExerciseStorageClassDiagram
Figure 6. Exercise Storage Structure

The Storage component provides the functionalities that enable the persistent storage of the model. Starting from the most primitive type:

  1. JacksonExerciseSet contains exactly two String objects, one for the number of repetitions and one for the weight. This class has a dependency to the NumReps and Weight model due to the toModelType() function. This function converts JacksonExerciseSet into the model’s ExerciseSet so that it can be used in other parts of the application.

  2. JacksonExercise contains exactly one String object that represents the exercise name, and any number of JacksonExerciseSet objects. Similarly, it has a dependency to ExerciseName, ExerciseSet and Exercise object due to the toModelType() function.

  3. JacksonExerciseList is the persistent storage for the ExerciseList model, and it contains any number of JacksonExercise objects.

  4. ExerciseListStorageManager implements the ExerciseListStorage which provides certain functionalities required for the storage to work properly. The ExerciseListStorageManager is controlled by the StorageManager.

Logic - Commands
ExerciseCommandClassDiagram
Figure 7. Exercise Commands Structure

The Exercise Commands package stores the business logic of the exercise feature. The commands are organised in a hierarchical fashion, in the order of precedence in a valid input. For example, SetCommand inherits from ExerciseCommand as the set comes after exercise in the input exercise set.

Each command contains a COMMAND_WORD which is a single word that is unique to the command. Each command also implements an execute method that represents the logic of the command. Instructions to control the model, storage and view are stored inside this method.

Logic - Parsers
ExerciseParserClassDiagram
Figure 8. Exercise Parser Structure

The parsers are responsible for parsing a user input into a Command object. For the Exercise component, there are parsers for every command that accepts user arguments. For example, since exercise list does not take in any argument, there is no parser for the ListCommand. After parsing the user input, the parser will return Command object to the caller, which will execute the command via the execute method.

Sample Command Execution

This section will illustrate an example of an exercise command execution using the input exercise create e/Bench Press.

CreateCommandSequenceDiagram
Figure 9. Exercise’s CreateCommand Sequence Diagram

In this portion, we will trace the sequence diagram of the exercise create command to better understand the internals of the Exercise feature.

  1. The user enters the command exercise create e/Bench Press

  2. LogicManager will pass the command to the ParserManager for parsing

  3. ParserManager upon seeing that the command is prefixed by exercise creates a ExerciseCommandParser

  4. ParserManager then pass create e/Bench Press to ExerciseCommandParser

  5. ExerciseCommandParser upon seeing that the command is prefixed by create creates a CreateCommandParser

  6. ExerciseCommandParser then pass the argument e/Bench Press to CreateCommandParser

  7. CreateCommandParser then attempts to create an ExerciseName object using the String in the argument

  8. Using the ExerciseName, CreateCommandParser then create a CreateCommand object with the exercise name

  9. The CreateCommand is then passed back to the LogicManager

  10. LogicManager calls c.execute()

  11. CreateCommand will attempt to create an Exercise using the exercise name

  12. After creating the Exercise object, the CreateCommand will attempt to store the new exercise by calling the addExercise method of Model

  13. After the exercise is successfully added, a CommandResult object is created

  14. This result is then passed back to the LogicManager which will display the output on the GUI

Summary
EditSetActivityDiagram
Figure 10. Editing Exercise Set Activity Diagram

At this point, you should have gather enough information to start developing the Exercise feature. As a summary, this is a sample Activity Diagram that depicts a user flow when they want to edit an exercise set.

Design Considerations

Parser Component

One of the consideration while designing was that the commands in exercise are extremely nested. We have commands such as exercise set create r/1 m/10. While we could have chucked all the parsing in ExerciseSetCreateParser class, we realised that it will be better if we were to abstract the parser into separate classes. This allows us to group the functionalities of the parser in a single file. For example, ExerciseCommandParser will parse any string that has the word exercise as the prefix. SetCommandParser will do so for a prefix of set. This means that for the above command, while we have to go through multiple parsers which can make the performance of the application suffer, each of the parsers have a single responsibility which makes it a better design choice.

Model Component

For the Model component, note that Exercise objects are supposed to be unique whereas ExerciseSet objects are not. This is created due to our observations of the workout regimes in the real world.

For ExerciseSet, while set weights and number of repetitions tend to vary during an exercise, users may want to have the freedom to do multiple sets with the same configuration during the course of the exercise. Hence, it is unwise to make it unique.

However, for exercises, we noted that users tend to reuse the same exercise throughout different workout plans. At the same time, there is a high chance of users creating duplicate exercises when the number of exercises in the application increases significantly. Therefore, we chose to make Exercise a unique object instead.