diff --git a/.gitignore b/.gitignore index 2873e189e1..40c0614971 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +FitTrackLogger.log +FitTrackLogger.log.lck +data/saveFile.txt diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..108187aab6 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: fittrack.FitTrack + diff --git a/README.md b/README.md index e243ece764..394c6755a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# fittrack This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -8,7 +8,7 @@ Prerequisites: JDK 17 (use the exact version), update Intellij to the most recen 1. **Ensure Intellij JDK 17 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 17 in a previous Intellij project. 1. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -1. **Verify the setup**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: +1. **Verify the setup**: After the importing is complete, locate the `src/main/java/fittrack/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: ``` > Task :compileJava > Task :processResources NO-SOURCE @@ -39,7 +39,7 @@ Prerequisites: JDK 17 (use the exact version), update Intellij to the most recen ### JUnit tests -* A skeleton JUnit test (`src/test/java/seedu/duke/DukeTest.java`) is provided with this project template. +* A skeleton JUnit test (`src/test/java/fittrack/duke/DukeTest.java`) is provided with this project template. * If you are new to JUnit, refer to the [JUnit Tutorial at se-education.org/guides](https://se-education.org/guides/tutorials/junit.html). ## Checkstyle diff --git a/build.gradle b/build.gradle index ea82051fab..bb263ffeee 100644 --- a/build.gradle +++ b/build.gradle @@ -30,11 +30,14 @@ test { application { mainClass.set("seedu.duke.Duke") + // mainClass.set("fittrack.FitTrack") } shadowJar { archiveBaseName.set("duke") archiveClassifier.set("") + // archiveFileName = 'fittrack.jar' + } checkstyle { @@ -42,5 +45,6 @@ checkstyle { } run{ + enableAssertions = true standardInput = System.in -} +} \ No newline at end of file diff --git a/data/saveFile.txt b/data/saveFile.txt new file mode 100644 index 0000000000..efa5f0fef1 --- /dev/null +++ b/data/saveFile.txt @@ -0,0 +1,3 @@ +User|MALE|12 +TrainingSession|session1|12/11/2024 10:12|MALE|12|No mood recorded|0|0|-1|0|0|-1 +TrainingSession|session2|12/11/2024 10:13|MALE|12|No mood recorded|0|0|-1|0|0|-1 diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..c184e41c66 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,8 @@ # About us - -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:----------:|:----------------------------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Zackermax See Zheng Feng | [Github](https://github.com/Zackermax) | [Portfolio](team/zackermax.md) +![](https://via.placeholder.com/100.png?text=Photo) | Ng Chee Kiang | [Github](https://github.com/CheeKiangg) | [Portfolio](team/CheeKiang.md) +![](https://via.placeholder.com/100.png?text=Photo) | Marcus Wong | [Github](https://github.com/TheDinos) | [Portfolio](team/marcuswong.md) +![](https://via.placeholder.com/100.png?text=Photo) | Avjay Bhar | [Github](https://github.com/Yvorm) | [Portfolio](team/Yvorm.md) +![](https://via.placeholder.com/100.png?text=Photo) | Ayushi Yadav | [Github](https://github.com/ayushi0803) | [Portfolio](team/ayushi.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..223de340ac 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,709 @@ # Developer Guide ## Acknowledgements +* The structure of this Developer Guide is inspired by [AB-3](https://se-education.org/addressbook-level3/DeveloperGuide.html). -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +## Setting up, getting started +First , **fork** this repo, and clone the fork into your computer. +1. **Configure the JDK**: Follow the guide [se-edu/guides] IDEA: Configuring the JDK to ensure Intellij is configured + to use **JDK 17**. +2. **Import the project as a Gradle project**: Follow the guide [se-edu/guides] IDEA: Importing a Gradle project + to import the project into IDEA. +3. Verify the setup: + (i) Run the FitTrack.Main and try a few commands like `help`. + (ii) Run the tests to ensure that all of it pass. ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Software Architecture +![Archi_Architecture.png](Images/Archi_Architecture.png) +The Architecture Diagram shown above depicts the high-level design of the FitTrack CLI application. + +**FitTrack** is the main class and entry point of the application. It manages high level functionalities by coordinating the four main classes: + +| Class | Functionality | +|-----------------|--------------------------------------------------------------------------------------------------------------| +| Storage | Manages saving and loading data from a persistent storage file | +| User | Records the user’s information, such as age and gender, and provides methods to modify or retrieve this data | +| Parser | Handles parsing of user input, converting it into commands and actions | +| Ui | Handles user interaction and CLI output, printing messages and data to the console | + +**Commons** and **Exceptions** represent a collection of lower level Classes and Exceptions used by the main classes above. +**Commons** classes are as follows: + +| Class | Functionality | +|-------------------|-----------------------------------------------------------------------------------------------------------------| +| FitTrackLogger | Manages logging for the application, ensuring errors and important events are properly recorded | +| TrainingSession | Represents a single training session, including exercises and metadata (e.g. date and description) | +| Exercise | Represents different types of exercises available in the application, like pull-ups or shuttle runs | +| Calculator | Look up points achieved by user based on age, gender and performance result of each exercise station | +| GraphPoints | Illustrates the cumulative points earned across sessions, showcasing overall fitness progress and achievements. | +| GraphPerformance | Visualises performance metrics for a specific exercise, adapting for time-based or rep-based tracking. | +| Reminder | Allows users to set reminders to be track deadlines and upcoming events. | +| Goal | Allows users to set, list, and delete specific goals related to fitness and overall well-being | +| FoodWaterIntake | Allows users to add, view, and delete water and food intake logs to monitor daily hydration levels | + +The following Class Diagram elaborates on the interactions between major classes and their multiplicities. + +### Overall Class Diagram +![Class_Overall.png](Images/Class_Overall.svg) + +## Features + +### Storage +### Storage Overview + +The `Storage` class handles the reading and writing of data for the application, interacting with various objects such +as `TrainingSession`, `Goal`, `Reminder`, `FoodEntry`, and `WaterEntry`. The data is saved in a file located at +`data/saveFile.txt`, with functionality to initialize the save file, load data from it, and update the file with the +latest information. Below is a breakdown of the methods used for these tasks: + +![Class_Storage.png](Images/Class_Storage.png) + +#### 1. Initializing the Save File +The `initialiseSaveFile()` method ensures that the necessary directory and save file exist. If the "data" directory or +"saveFile.txt" file is not found, the method attempts to create them. If successful, a message is logged confirming the +creation of the new save file or the access to an existing one. This method also asserts that the file exists after the +initialization. + +- **Helper Functions:** + - `Files.createDirectories(dirPath)`: Creates the necessary directory for the save file. + - `file.createNewFile()`: Creates the save file if it doesn't already exist. + +#### 2. Loading the Save File +The `loadSaveFile(ArrayList loadList)` method reads the save file and populates the provided list (`loadList`) +with objects that are deserialized from the file. It uses a `Scanner` to read the file line by line. Each line +corresponds to a serialized object (such as `TrainingSession`, `Goal`, or `Reminder`), and the method delegates the +parsing to the `createSaveableFromString(String saveString)` method, which identifies the type of object based on the +prefix of the line. + +- **Helper Functions:** + - `createSaveableFromString(String saveString)`: This method inspects the prefix of each line in the save file and + - creates the appropriate object by invoking the `fromSaveString` method of the corresponding class + - (e.g., `TrainingSession.fromSaveString(saveString)`). + - `Scanner`: Reads the file content line by line. + +#### 3. Updating the Save File +The `updateSaveFile()` method writes the latest data to the save file. It serializes objects from the provided lists +(`sessionList`, `goalList`, `reminderList`, and `foodWaterList`) and writes each object’s string representation to the +save file. It ensures that the lists are non-null before attempting to write, and for each object, it calls the +`toSaveString()` method to retrieve the string representation. After each entry, a newline is added to separate entries. +The method uses `FileWriter` to perform the writing operation. + +- **Helper Functions:** + - `toSaveString()`: Called for each object to retrieve its serialized string representation. + - `FileWriter`: Writes the serialized data to the save file. + +#### 4. Error Handling and Logging +Throughout these operations, appropriate error handling is implemented. If the save file cannot be found or created, or +if an invalid data format is encountered during loading, an exception is thrown or logged. For instance, +`InvalidSaveDataException` is thrown if an unrecognized descriptor is found in the save file. Additionally, the +`LOGGER` object is used extensively to log the success or failure of various operations, providing insight into the +application's file handling processes. + +### Sequence of Events for Each Operation + +1. **Initialization of Save File:** + - The method checks for the existence of the directory and file. + - If they do not exist, it creates them and logs the event. + - If successful, the method ensures the file exists with an assertion. + +2. **Loading Data from Save File:** + - The method initializes a `Scanner` to read the save file. + - For each line, it calls `createSaveableFromString()` to convert the line into an appropriate object. + - Each object is added to the `loadList`. + - On success, a message is logged indicating successful loading. + +3. **Saving Data to File:** + - The method iterates through each list (`sessionList`, `goalList`, `reminderList`, `foodWaterList`). + - For each object in the list, it calls `toSaveString()` to convert the object to a string representation. + - The string is written to the save file using `FileWriter`. + - After each entry, a newline character is added. + - The process concludes with a log indicating that the file has been successfully updated. + +This system provides an efficient and reliable way to persist and retrieve application data, ensuring data integrity and +ease of maintenance. + + + +### Set User +When the application starts up, it will prompt the user for their gender and age via the Set User feature. +Their input is processed by Parser and stored in a newly created instance of the User class, which is assigned to the +object "user". +Upon successful setting of the gender and age fields, a confirmation of the user's gender and age will be printed in the +CLI via the Ui class. + +If the user wants to update their age or gender after the initialization process, they can set it again at any time by +calling the "set" command. +This performs the same operations, re-instantiating the "user" object with a new User instance with the updated details. + +The sequence diagram for this process is shown below. + +[//]: # (![Class_SetUser.png](Images/Class_SetUser.png)) + +![Sequence_SetUser.png](Images/Sequence_SetUser.png) + +### Add Training Session + +[//]: # (![Class_AddTrainingSession.png](Images/Class_AddTrainingSession.png)) + +#### 1. Class Interaction Overview +When the user adds a new training session, an instance of the `TrainingSession` class is created. +This instance initializes an EnumMap, which instantiates the 6 `ExerciseStation` child classes with +their initial values. +Below is a class diagram showing the EnumMap after an instance of `TrainingSession` is created. + +![Class_TrainingSessionInitialState.png](Images/Class_TrainingSessionInitialState.png) + +#### 2. Sequence of Events + +![Sequence_addTrainingSession.png](Images/Sequence_addTrainingSession.png) + +1) **User inputs the add command**: The User initiates the "add " command by + calling Parser with the appropriate input. +2) **Instantiation of TrainingSession**: The Parser creates a new TrainingSession object with the + current time, description, and user. +3) **Instantiation of Exercise Stations**: Within TrainingSession class, all 6 subclasses of exercise + stations are instantiated. +4) **UI Interaction**: The Parser calls Ui.printAddedSession(sessionList), which: + (i) Begins a UI segment + (ii) Prints a session message + (iii) Prints the description of the last added session + (iv) Calls printSessionCount to show the total count + (v) Ends the segment. +5) Refer to Section on Edit Exercise and Point Calculation for specific implementation of + performance metric and point conversion. + +### Modify the DateTime of a Training Session +When Parser detects the "modify" command, the TrainingSession instance at the user's specified index in sessionList will +be overwritten with the new DateTime as specified by the user via the setDateTime function call. + +![Sequence_ModifyTrainingSession.png](Images/Sequence_ModifyTrainingSession.png) + +### Delete Training Session +When Parser detects the "delete" command, the TrainingSession instance at the user's specified index in sessionList will + be copied into a new private TrainingSession instance called sessionToDelete. The original instance in sessionList will +then be deleted. The new private instance is used to print the details of the deleted session, giving the user +confirmation that the TrainingSession they wished to delete has been successfully deleted. The new TrainingSession +instance is then disposed of. + +[//]: # (![Class_DeleteTrainingSession.png](Images/Class_DeleteTrainingSession.png)) + +![Sequence_DeleteTrainingSession.png](Images/Sequence_DeleteTrainingSession.png) + +### List Training Sessions +When Parser detects the "list" command, it calls printSessionList() followed by printSessionCount(). +printSessionList() first checks if sessionList is empty. If sessionList is empty, it prints a message saying so. +If sessionList is not empty, it will be iterated through. +For each TrainingSession in sessionList, getSessionDescription will be called, returning its details as a String. +The TrainingSession's index will be printed, followed by the session description before iterating to the next index. +When all the TrainingSessions have been printed, Ui calls printSessionCount() to display the total number of +TrainingSessions in sessionList. + +[//]: # (![Class_ListTrainingSessions.png](Images/Class_ListTrainingSessions.png)) + +![Sequence_ListTrainingSessions.png](Images/Sequence_ListTrainingSessions.png) + +### View Training Session +When Parser detects the "view" command, it calls printSessionView() on the user's specified session index. +This in turn calls viewSession(), which outputs the details of the TrainingSession instance in the CLI. +This process fetches the details of each of the 6 ExerciseStation classes, which fetch details from the Calculator +classes. +These details are then printed to the CLI. + +[//]: # (![Class_ViewTrainingSession.png](Images/Class_ViewTrainingSession.png)) + +![Sequence_ViewTrainingSession.png](Images/Sequence_ViewTrainingSession.png) + +### Edit Exercise + +The **Edit Exercise** feature is managed by the `TrainingSession` class, and is primarily carried out by its +`editExercise()` function. This feature utilizes the `setPerformance()`and `getPoints()` methods from the +`ExerciseStation` classes to edit the performance for the user’s selected +exercises. Additionally, it calculates the points the user will earn for each exercise based on the updated performance +values. + +When the user wishes to edit a training session, they specify an `Exercise` Enum, and the reps/timing to be inputted. +These variables are then passed to the`editExercise` function. This function calls the relevant methods to update the +repetitions or timings and calculates the corresponding points for the specified exercise. + +The following sequence diagram illustrates the function calls involved in this process: + +![Sequence_editExercise.png](Images/Sequence_editExercise.png) + +Additionally, the state diagram below shows the end state of the `editExercise` function after execution of the command, +`editExercise(Exercise.PULL_UP, 1)` and `editExercise(Exercise.SHUTTLE_RUN, "16.0")`: + +![Class_TrainingSessionEditState.png](Images/Class_TrainingSessionEditState.png) + +### Add Reminder +When a user inputs the command to add a new reminder (`remind `), the process begins by parsing +the input to separate the `description` and `deadline`. The description and deadline are split using the `//` delimiter. +If the input does not adhere to the expected format an error message is displayed, and the process is halted. Both the +description and deadline are then validated; if either is empty or invalid, an exception is thrown, and an appropriate +error message is shown to the user. + +Once the input is validated, the deadline is parsed into a `LocalDateTime` object using the parseDeadline method. If +the deadline format is incorrect, a `DateTimeParseException` is caught, and an error message is displayed. + +Following successful validation, a new `Reminder` object is created using the validated information and constructor of the Reminder class. + +The newly created Reminder object is then added to the `reminderList`, which is maintained by the `:FitTrack` class. +After the reminder is added, the `printAddedReminder()` method is invoked to display the reminder’s details to the +user. Finally, the `updateSaveFile` method is called within the Parser class, which utilizes the `toSaveFile` helper +method to format and write the updated reminder list to the `SaveFile` Document, ensuring the persistence of reminder +data between user sessions. + +![Sequence_addReminder.png](Images/Sequence_addReminder.png) + + +### List Reminder +The "view reminders" command triggers a sequence that iterates through the main reminder list. For each reminder, +`printReminderDescription()` is called, displaying the description and deadline of each reminder. If the list is empty, +the system outputs a message indicating no reminders are available. This functionality lets users view all saved +reminders at a glance. + +When Parser detects the `list-remind` command, it calls `printReminderList()` followed by `printReminderCount()`. +`printSessionList()` first checks if reminderList is empty. If reminderList is empty, it prints a message saying so. +If reminderList is not empty, it will be iterated through. +For each Reminder in reminderList, `printReminderDescription()` is called, displaying the description and deadline of +each reminder as String. The `Reminder` objects' index will be printed, followed by the description and deadline before +iterating to the next index. When all the Reminders have been printed, `Ui` calls `printSessionCount()` to display the total number of +Reminders in `reminderList`. + +![Sequence_ListReminders.png](Images/Sequence_ListReminders.png) + +### List Upcoming Reminders +The "list upcoming reminders" command (`list-remind`) invokes the +`findUpcomingReminders(ArrayList mainReminderList)` helper method. This method iterates through +`remainderList` similarly to `list-remind`, checking if each reminder’s deadline is within one week (168 hours) from the +current date/time. For reminders that match this condition, they are added to an `upcomingReminderList`. Finally, for +each reminder in `upcomingReminderList`, `printReminderDescription()` is called, displaying only those reminders with due +dates in the upcoming week. This function is called automatically at program initialisation to provide a summary +reminder for users starting the program. + +![Sequence_ListUpcomingReminders.png](Images/Sequence_ListUpcomingReminders.png) + + +### Delete Reminder +The delete command (`delete-remind `) removes a reminder at the specified index from the main list. +The validity of the passed `index` is checked, and an exception is thrown back to the parser to be handled if found to +be out of bounds, improperly formatted, or non-numeric. + +Before removal, the specified reminder is copied to a temporary instance, `sessionToDelete`, which holds the reminder's +data for confirmation. Following deletion, the details of the deleted reminder are printed by invoking +`printReminderDescription()` on `reminderToDelete`, confirming successful removal. The `sessionToDelete `instance is +then discarded, ensuring efficient memory use. The `updateSaveFile` function is then called within the `Parser` class, +using the local `toSaveFile` helper method to write a formatted string to the SaveFile Document - for information +permanence between user sessions. + +![Sequence_DeleteReminder.png](Images/Sequence_DeleteReminder.png) + + +### Add Goals + +Goals allow users to set specific objectives within the application. Users can add goals using the +`add-goal