diff --git a/README.md b/README.md
index 13f5c77403f..d887eeab911 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/AY2223S1-CS2103T-T15-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T15-4/tp/actions)
+[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T15-4/tp/branch/master/graph/badge.svg?token=3QLCJGGTH7)](https://codecov.io/gh/AY2223S1-CS2103T-T15-4/tp)
+[![Pages](https://github.com/AY2223S1-CS2103T-T15-4/tp/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/AY2223S1-CS2103T-T15-4/tp/actions/workflows/pages/pages-build-deployment)
![Ui](docs/images/Ui.png)
-* This is **a sample project for Software Engineering (SE) students**.
+* Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Gim allows you to keep track your exercises and Personal Records in a efficient way.
Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
+ * as a tracking tool to keep track of completed exercises
+ * as a tracking tool to keep track of your personal records
+ * as a workout generator to generate exercises based on your personal records
+* It is named `Gim` as a name-play tribute to `Vim` and `Gym`.
+* For the detailed documentation of this project, see the **[Gim Product Website](https://ay2223s1-cs2103t-t15-4.github.io/tp/)**.
* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
diff --git a/build.gradle b/build.gradle
index 108397716bd..08c1600fb1a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ plugins {
id 'jacoco'
}
-mainClassName = 'seedu.address.Main'
+mainClassName = 'gim.Main'
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -66,7 +66,11 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'gim.jar'
+}
+
+run {
+ enableAssertions = true
}
defaultTasks 'clean', 'test'
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index d618671b832..2a2c82ed494 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -385,10 +385,10 @@
JAVADOC CHECKS
-->
-
+
-
+
@@ -400,7 +400,7 @@
-
+
diff --git a/copyright.txt b/copyright.txt
index 93aa2a39ce2..5d9601c9b13 100644
--- a/copyright.txt
+++ b/copyright.txt
@@ -1,7 +1,7 @@
Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob
Copyright by Susumu Yoshida - http://www.mcdodesign.com/
-- address_book_32.png
+- exercise_tracker_32.png
- AddressApp.ico
Copyright by Jan Jan Kovařík - http://glyphicons.com/
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..559b42ffef1 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -7,53 +7,51 @@ We are a team based in the [School of Computing, National University of Singapor
You can reach us at the email `seer[at]comp.nus.edu.sg`
+[[homepage](https://ay2223s1-cs2103t-t15-4.github.io/tp/)]
+
## Project team
-### John Doe
+### Donovan Lee Jun Hao
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/24Donovan24)]
+[[portfolio](team/24donovan24.md)]
-* Role: Project Advisor
+* Role: Developer
-### Jane Doe
+### Ervin Kin Zhe Zheng
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/ErvinK123)]
+[[portfolio](team/ervink123.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
-### Johnny Doe
+### Kavan Tan
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/kavantan)]
+[[portfolio](team/kavantan.md)]
* Role: Developer
-* Responsibilities: Data
-### Jean Doe
+### Kok Ee Suan
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/ee-suan)]
+[[portfolio](team/ee-suan.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
-### James Doe
+### Steven Lim
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/stevenlimhw)]
+[[portfolio](team/stevenlimhw.md)]
* Role: Developer
-* Responsibilities: UI
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..9f41d936241 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,14 +2,24 @@
layout: page
title: Developer Guide
---
+
+
+
+
+
+
+
+**Table of Contents**
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
+
## **Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* Gim is adapted from the [AddressBook-Level3](https://se-education.org/addressbook-level3/) project created by the SE-EDU initiative.
+* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
--------------------------------------------------------------------------------------------------------------------
@@ -19,6 +29,8 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
--------------------------------------------------------------------------------------------------------------------
+
+
## **Design**
@@ -36,7 +48,7 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for,
+**`Main`** has two classes called [`Main`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/Main.java) and [`MainApp`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/MainApp.java). 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 methods where necessary.
@@ -52,7 +64,7 @@ The rest of the App consists of four components.
**How the architecture components interact with each other**
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
+The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `:del 1`.
@@ -61,186 +73,461 @@ Each of the four main components (also shown in the diagram above),
* defines its *API* in an `interface` with the same name as the Component.
* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.
+
+
For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
+
+
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/ui/Ui.java)
![Structure of the UI Component](images/UiClassDiagram.png)
-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `ExerciseListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` component uses the 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`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the 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`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/resources/view/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.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
-* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+* depends on some classes in the `Model` component, as it displays `Exercise` object residing in the `Model`.
+
+
### Logic component
-**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** : [`Logic.java`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
How the `Logic` component works:
-1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command.
-1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`.
-1. The command can communicate with the `Model` when it is executed (e.g. to add a person).
-1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
+1. When `Logic` is called upon to execute a command, it uses the `ExerciseTrackerParser` class to parse the user command.
+2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`.
+3. The command can communicate with the `Model` when it is executed (e.g. to add an exercise).
+4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
-The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call.
+
-![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
+The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute(":del 1")` API call.
+
+![Interactions Inside the Logic Component for the `:del 1` Command](images/DeleteSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
-* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
+* When called upon to parse a user command, the `ExerciseTrackerParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `ExerciseTrackerParser` returns back as a `Command` object.
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
+
+
### Model component
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+**API** : [`Model.java`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/model/Model.java)
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` 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.
+* stores the exercise tracker data i.e., all `Exercise` objects (which are contained in a `ExerciseList` and `ExerciseHashMap` object).
+* stores the currently 'selected' `Exercise` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` 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.
* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
-
-
-
-
-
+
### Storage component
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/AY2223S1-CS2103T-T15-4/tp/blob/master/src/main/java/gim/storage/Storage.java)
The `Storage` component,
-* can save both address book data and user preference data in json format, and read them back into corresponding objects.
-* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
+* can save both exercise tracker data and user preference data in json format, and read them back into corresponding objects.
+* inherits from both `ExerciseTrackerStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
### Common classes
-Classes used by multiple components are in the `seedu.addressbook.commons` package.
+Classes used by multiple components are in the `gim.commons` package.
--------------------------------------------------------------------------------------------------------------------
+
+
## **Implementation**
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### **Exercise**
-#### Proposed Implementation
+#### Implementation
+
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+An `Exercise` is stored in `ExerciseList` and `ExerciseHashmap` of Model
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+An `Exercise` contains the following attributes:
+1. a `Name`, which represents the name of the Exercise
+2. a `Weight`, which represents the total weight used for a certain Exercise
+3. a `Reps`, which represents the number of times a specific exercise was performed
+4. a `Sets`, which represents the number of cycles of reps that was completed
+5. a `Date`, which represents the date an exercise was performed
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+**Date Implementation**
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+
-![UndoRedoState0](images/UndoRedoState0.png)
+The default format for date follows `dd/MM/uuuu`. `uuuu` is chosen over `yyyy` because this avoids unexpected exceptions
+under strict parsing by the Java API `DateTimeFormatter`, such as those exceptions related to year-of-era.
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+The validity of a given date string depends on two factors: (1) Regex formatting and (2) Non-existent date checking.
-![UndoRedoState1](images/UndoRedoState1.png)
+1. For the regex formatting, the Singleton class `RegexList` contains a `List` called `regexList` that contains the
+accepted regex strings. The method `isValidDateByRegex` iterates through `regexList` to check whether a given date string
+follows any of the accepted regex strings.
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+2. For the non-existent date checking, the Singleton class `FormatterList` contains a `List` called `formatterList`
+that contains the accepted date patterns. The private constructor of this class uses stream mapping to obtain a `List`
+called `formatterList` containing `DateTimeFormatter` objects that follows strict date validity check using
+`ResolverStyle.STRICT`.
-![UndoRedoState2](images/UndoRedoState2.png)
+The Singleton pattern is used here to prevent multiple instantiation of this class.
+1. The drawbacks of using this pattern is not really pronounced as there are not many classes having them as dependencies (only the `Date` class).
+Therefore, coupling within the code base will not increase much.
+2. Testing will not be affected by the fact that singleton objects carry data from one test to another because there is no mutation
+of data inside the singleton objects `RegexList` and `FormatterList`. All tests will have the same singleton objects used.
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+
-
+#### Design Considerations
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+**Aspect: Fields of Exercise are Final**
+* **Current choice**: The aforementioned fields in `Exercise` are final, effectively making our Exercise class immutable.
+ * Rationale: Code written with immutable objects is easier to reason with and easier to understand, facilitating a smoother process when it comes to debugging and testing any code related to `Exercise`.
-![UndoRedoState3](images/UndoRedoState3.png)
+
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+### **Exercise Hashmap**
-
+#### Implementation
+The Exercise Hashmap stores data in the form of a hashmap, where the key of the hashmap is the `Name` of an `Exercise` and its associated value is an `Exercise` ArrayList, containing a list of exercises (with the same name).
+
+#### Design Considerations
+
+**Aspect: Choosing the Data Structure**
+* **Current choice**: We decided to use a hashmap data structure.
+ * Rationale: We wanted to create associations between exercises with the same name. Utilising a hashmap structure, we can easily identify and retrieve exercises with the same exercise name (by their unique key identifier). Hence, this facilitates commands that rely on this retrieval to be implemented, such as [Listing of Personal Records](#listing-of-personal-records) and [Generating a suggested workout routine](#generating-a-suggested-workout-routine).
+
+### **Sorting Exercise List**
+
+#### Implementation
+
+The sorting of exercise list is facilitated by `ModelManager` which implements `Model`. `ModelManager` contains a `filteredExercises`
+list which is the list of exercises in a `FilteredList` 'wrapper' from `javafc.collections.transformation`. `filteredExercises`
+gets the list of exercises to be displayed from method `getExerciseList()` in `ExerciseTracker`.
+
+`ExerciseTracker` has method `sortDisplayedList()` which calls `sortDisplayedList()` in `ExerciseList`.
+
+`ExerciseList` contains a `displayedList` of type `ObservableList` and is the list that will be displayed by the `Ui`.
+It is a duplicated copy of the `internalUnmodifiableList` of type `unmodifiableObservableList`. `ExerciseList` has method
+`sortDisplayedList()` which sorts the `displayedList` by order of date using the `sort()` method in `java.util.Collections` with a `Comparator`.
+
+
+
+**Execution**
+
+When the command `:sort` is entered, the `Ui` sends the command to `Logic`. `Logic` parses and identifies the `:sort` command that was entered, and creates
+an instance of it. `Logic` then executes the command. `Model` will have the displayed list sorted and the sorted list will be displayed by `Ui`.
+
+**Example Usage**
+
+Given below is an example usage scenario and how the sorting mechanism behaves at each step.
+
+Step 1: The user launches the application which loads the set of exercises previously keyed. `displayedList` will be initialised
+to be the same as the `internalUnmodifiableList` in `ExerciseList` where the exercises are sorted by the date of input.
+
+Step 2: The user executes `:sort` command to sort the exercises based on date of exercises done. The `ExerciseTrackerParser`
+identifies that the command is a `SortCommand`. The command calls `Model` to `sortDisplayedList` and the `Ui` displays the
+`displayedList` which has the exercises sorted by their respective dates.
+
+The following sequence diagram shows how the sort command is executed.
+
+
+
+
+
+#### Design considerations
+
+**Aspect: Displayed List structure**
+* **Current choice**: `displayedList` is a duplicated copy of the list of exercises in `internalUnmodifiableList` of type
+ `UnmodifiableObservableList` in `ExerciseList` class
+ * Rationale: The sort command will sort the `diplayedList`, not affecting the `internalUnmodifiableList`. This allows
+ users to view the sorted list of exercises while maintaining a defensive copy of exercises keyed by user.
+
+**Aspect: Open-Closed Principle**
+* **Current choice**: `sortDisplayedList()` in `ExerciseList` sorts the `displayedList` using the `sort()` method in `java.util.Collections` and a `Comparator`.
+ * Rationale: Using a `Comparator` in `sort()` allows one to extend the `sortDisplayedList` method to accommodate other sorting orders
+ by simply changing the `Comparator` used should the sorting criteria change in the future.
+
+### **Viewing exercises within a date range**
+
+#### Implementation
+
+* `ExerciseTrackerParser` calls `RangeCommandParser#parse`.
+* `RangeCommandParser#parseArguments` will return an enum type `Variation` according to the arguments in the
+`ArgumentMultimap` created.
+* When we obtain `Variation.ONE`, `RangeCommandParser#getVariationOne` will be called.
+ * In this case, the arguments expected to be inside the `ArgumentMultimap` will be the start date and
+ end date.
+* When we obtain `Variation.TWO`, `RangeCommandParser#getVariationTwo` will be called.
+ * In this case, we expect the number of days to be the only argument inside `ArgumentMultimap`.
+* `ParserUtil#parseDate` will be called to obtain the `Date` object(s).
+* `RangeCommand` is returned.
+* Then, the `execute()` method of the resulting `RangeCommand` object will be called, returning a
+`CommandResult` object with the appropriate message.
+
+
+
+**Execution**
+
+When the command `:range start/START_DATE end/END_DATE` or `:range last/NUMBER_OF_DAYS` is entered, the `Ui` sends
+the command to `Logic`. `LogicManager` parses and identifies the `:range` command that was entered, and creates an instance of it.
+
+`LogicManager` then executes the command by calling `execute()` in `RangeCommand`.
+
+`RangeCommand` will call the method `sortFilteredExerciseList(predicate)` defined in `Model`. This will cause
+`filteredExercises` in `ModelManager` to be filtered according to the predicate `DateWithinRangePredicate`.
+The resulting list will only include exercise entries that have dates between the start date (inclusive) and
+end date (inclusive), arranged from the most recent first. If two exercise entries have the same date,
+they will be sorted in alphabetical order.
+
+`Model` will have the list sorted and the sorted list will be displayed by `Ui`.
+
+**Example Usage**
+
+Given below is an example usage scenario and how the date range view mechanism behaves at each step.
+
+Step 1: The user launches the application which loads the set of exercises previously keyed.
+
+Step 2: The user executes `:range start/10/10/2022 end/15/10/2022` command to view all the exercises done between
+10 October 2022 and 15 October 2022. The `ExerciseTrackerParser` identifies that the command is a `RangeCommand`.
+The command calls `Model` to `sortFilteredExerciseList(predicate)` and the `Ui` displays the `filteredExercises` list which has all the exercises between the specified dates.
+
+
+
+The following sequence diagram shows how the date range process is executed.
+
+
-The following sequence diagram shows how the undo operation works:
+
-![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
+#### Design considerations
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+**Aspect: Simplicity of command design**
+* **Current choice**: The two variations of the `:range` command uses the same keyword but with different expected arguments.
+This reduces the number of command keywords the user needs to remember when using the app.
+* **Alternative choice**: Instead of using `:range` for both variations, another keyword such as
+`:rangeLast` could be used in addition to `:range`. The drawback for this design choice is that
+it goes against the simplicity of command design where the set of command keywords should be minimised where possible.
+
+**Aspect: Enum types are used to indicate the variations**
+* **Current choice**: Within `RangeCommandParser`, the enum `Variation` has only two values: `ONE` and `TWO` to represent
+`:range` command variation one and two respectively. This ensures that the value representing the command variation cannot be anything not defined in the enum type.
+Adding new variations is also easily done by adding another value inside the enum type.
+
+### **Listing of Personal Records**
+
+#### Implementation
+
+The mechanism for listing Exercise personal record(s) is facilitated by `PrCommand`, which extends from `Command`.
+
+It implements the following operations:
+
+* `PrCommand#execute()`Executes and coordinates the necessary objects and methods to list personal record(s).
+* `PrCommandParser#parse()`Parses user input from UI and initialises a PrCommand object.
+
+The user can choose to view personal record(s) for specific exercises with the 'n/' prefix:
+* `:pr n/NAME1 [n/NAME2 n/NAME3 ...]`
+
+The user can also choose to view personal records for ALL exercises with the 'all/ prefix':
+* `:pr all/`
+
+
+
+**Example Usage**
+
+Given below is an example usage scenario for how the mechanism for listing Exercise personal record(s) behaves at each step.
+
+Step 1. The user launches the application and already has 4 Exercise instances, with two unique Exercises (Squat and Deadlift), in the exercise tracker.
+
+Step 2: The user enters the command `:pr n/Squat` to view their personal record for the exercise 'Squat'.
+
+The following sequence diagram shows how the `PrCommand` works.
+
+![ListPersonalRecordSequenceDiagram](images/ListPersonalRecordSequenceDiagram.png)
+
+
+
+#### Design considerations
+
+**Aspect: Type of arguments to accept**
+* **Alternative 1 (current choice)**: Accept exercise names.
+ * Pros: Being able to view and list personal records by Exercise name is more intuitive and convenient, especially since all unique Exercises are listed in the UI (bottom right).
+ * Cons: Would require users to type more characters; also require users to enter exercise names accurately.
+
+* **Alternative 2**: Accept index as arguments.
+ * Pros: Suggestions are generated based on PR recorded by the app. As such, the input exercise(s) must already exist in the app. Accepting indexes would guarantee this condition.
+ * Cons: May require users to scroll to locate index of desired exercise, when the number of exercises grow.
+
+
+
+### **Generating a suggested workout routine**
+
+#### Implementation
+
+Workout suggestions are suggested by `Generator` objects. The suggestion mechanism follows the command pattern. The `GeneratorFactory` creates a concrete `Generator` object, and passes it to the `GenerateCommand` object, which treats all generators as a general `Generator` type. `GenerateCommand` is able to get a workout suggestion without knowledge of the type of generator. The following class diagram illustrates this.
+
+![GeneratorCommandPattern](images/GeneratorCommandPattern.png)
+
+The mechanism for generating a suggested workout routine is facilitated by `GenerateCommand`, which extends from `Command`.
+
+It implements the following operations:
+
+* `GenerateCommand#execute()` — Executes and coordinates the necessary objects and methods to generate a suggested workout routine.
+* `GenerateCommandParser#parse()` — Parses user input from UI and initializes a GenerateCommand object.
+
+Cases such as where the index from the user input is out of bounds, are handled by the methods.
+
+
+
+**Example Usage**
+
+Given below is an example usage scenario for how the mechanism for generating a workout routine behaves at each step.
+
+Step 1. The user launches the application, and already has 2 exercises, squat and deadlift, at index 1 and 2, in the exercise tracker.
+
+Step 2: The user enters the command `:gen 1,2 level/easy` to generate an easy workout routine consisting of the exercises squat and deadlift.
+
+The following sequence diagram shows how the `GenerateCommand` works.
+A `Name` object `exerciseName` is returned to `g:GenerateCommand` by calling a method in `:Model`.
+For the sake of brevity, this interaction is omitted from the diagram.
+
+![GenerateWorkoutSequenceDiagram](images/GenerateWorkoutSequenceDiagram.png)
+
+
+
+The diagram below illustrates the interaction between `g:GenerateCommand` and `GeneratorFactory` class.
+The static method `GeneratorFactory#getGenerator()` creates a `Generator` of the correct difficulty level, such as `EasyGenerator`.
+The number of `Generator` objects created is equal to the number of unique exercise names. They are `s:EasyGenerator` and `d:EasyGenerator` for squat and deadlift respectively.
+
+![GetSuggestionSequenceDiagram](images/GetSuggestionSequenceDiagram.png)
+
+
:information_source: **Note:** The sd frame should capture the entire diagram here, but due to a limitation of PlantUML, it appears as such.
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+#### Design considerations
+
+**Aspect: Number of `Generator` objects:**
+* **Current choice**: Pairing each unique exercise to one `Generator`.
+ * Rationale: The current `:gen` command specifies a single difficulty level for all exercises listed in the command. A possible extension in the future would be to allow each exercise to be linked to its own difficulty level, for example, `:gen deadlift/easy squat/hard`. This design would make such an implementation possible.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+
+### **Listing of unique stored Exercises in a graphical UI**
+
+#### Implementation
+
+The display window is located in the bottom right of the application. The display mechanism has been implemented with the Observer design pattern in mind.
+
+It is primarily driven by `SavedExerciseListWindow` (which holds the UI for the display). The logic is
+handled by `ExerciseKeys` and `ExerciseHashMap`.
+
+**General class diagram**
+
+The `SavedExerciseListWindow` class implements the `Observer` interface as it is the observer. The `ExerciseHashMap` class maintains an internal ArrayList of type `Observer`, which can be modified through the addUI function. As the UI elements are usually initialized later than the data on loading of the application, the `SavedExerciseListWindow`UI object is only added as an observer after its constructor is called. This guards against any null-pointer exceptions which may occur when preloading data from a hashmap in storage.
+
+![ObserverPatternClass](images/ObserverPattern.png)
+
+
+
+**Subscribing to updates**
+
+Once the `SavedExerciseListWindow` object has been added to the arraylist of `Observer` in the `ExerciseHashMap`, it 'subscribes' to notifications whenever the ExerciseHashMap changes. Based on the functionality of the Hashmap as well as the application, this can be generalised into two distinct scenarios.
+
+* **Adding an exercise** - Whenever a new exercise has been added, there is a possibility of a new key being added.
+* **Removing an exercise** - Whenever a new exercise has been removed, there is a possibility of a key being removed permanently.
+
+
:information_source: **Note:** The current implementation subscribes to notification for any form of addition or deletion, regardless if the exercise is unique or already exists in the list.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+**Updating**
+
+Whenever there is a state changing operation, the `ExerciseHashMap` object will notify all observers through the notifyObservers method. All Observers in the list will run the update method that is individually specified in their class. As such , all Observers of ExerciseHashMap are required to override the update method as shown below.
-![UndoRedoState4](images/UndoRedoState4.png)
+```
+ @Override
+ public void update() {
+ .
+ unique implementation detail here...
+ .
+ }
+```
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+
-![UndoRedoState5](images/UndoRedoState5.png)
+Below is a sample sequence diagram for the current implementation of how notifyObservers work.
-The following activity diagram summarizes what happens when a user executes a new command:
+![NotifyObservers](images/NotifyObservers.png)
-
+
:information_source: **Note:** Currently, there is only SavedExerciseListWindow observing the ExerciseHashMap
+
-#### Design considerations:
+The logic behind the calculations and formatting of the display message is handled by the `ExerciseKeys` class.
-**Aspect: How undo & redo executes:**
+Through this pattern, each observer gets to define exactly what the required display/result should be.
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+#### Design considerations
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+**Aspect: Polymorphism**
+* The immediately apparent benefit of this design would be the Polymorphism that it capitalises on. In particular, the notifyObservers function in `ExerciseHashMap`.
-_{more aspects and alternatives to be added}_
+```
+ public void notifyObservers() {
+ for (Observer o: observerArrayList) {
+ o.update();
+ }
+ }
+```
-### \[Proposed\] Data archiving
+
-_{Explain here how the data archiving feature will be implemented}_
+* Notice that `ExerciseHashMap` does not know the nature of the observers and how they interact with it. `ExerciseHashMap` only stores a list of the objects observing it. It does not have to define what they should do to update, instead, the responsibility of deciding what to do is passed on to the Observers themselves.
+* This allows for flexibility in having different types of objects having different forms of updating. This keeps the code in `ExerciseHashMap` short and hides the implementation of the Observers behind the `Observer` interface which acts as an intermediary to help the UI communicate with `ExerciseHashMap`.
--------------------------------------------------------------------------------------------------------------------
+
+
## **Documentation, logging, testing, configuration, dev-ops**
* [Documentation guide](Documentation.md)
@@ -251,80 +538,298 @@ _{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
+
+
## **Appendix: Requirements**
-### Product scope
+### **Product scope**
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+* Programmers who love vim and want to hit the gym for some exercise. However, they are too occupied with work to recall their progressions and don’t know what to do next
+* They may also find it hard to remember their statistics on each exercise
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**:
+* Leverage on their blazing speed on vim to save, write and view gym data in a familiar fashion
+* Provides a fast platform for users to track their gym progress or workout routine
+* Has vim-like commands to make things more efficient for vim lovers
-### User stories
+### **User stories**
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+
+
+| Priority | As a … | I want to … | so that I can … |
+|----------|---------------|----------------------------------------------------------------------|----------------------------------------------------------------------------|
+| `* * *` | user | add exercises | keep track of my exercises |
+| `* * *` | user | delete exercises | see what exercises I have done |
+| `* * *` | user | look for all entries of a specific exercise | track my progression for that particular exercise |
+| `* * *` | user | view exercises in chronological order | keep track of my gym progress |
+| `* * *` | user | want to track my personal records | keep track of my progress for each exercise (and show off to my friends) |
+| `* * *` | user | generate workouts of different difficulty | customise my workout based on how I’m feeling that day |
+| `* * *` | user | view my recent exercises | plan for my next gym session |
+| `* * *` | user | view my exercises done within a date range | track my overall progress over a period of time (eg. weekly, monthly, etc) |
+| `* * *` | user | see what names the system has registered | add exercises correctly and quickly |
+| `* * ` | new user | remove all sample data | input my own data |
+| `* * ` | advanced user | have a quick summary of all the commands I can do in the application | save time |
+| `* * ` | clumsy user | have a safeguard against accidentally clearing all data | preserve my exercise |
+| `*` | user | track my calories intake | attain my fitness goals |
+| `*` | user | track my calories burnt during the gym session | attain my fitness goals |
+| `*` | user | have a tailored workout program | target my specific strengths and weaknesses for outstanding results |
+| `*` | user | have motivation to go to the gym | stay motivated to attain my fitness goals |
+| `*` | user | track my Rate of Perceived Exertion (RPE) of previous workout | better plan for my next workout |
+| `*` | user | view my run timings | track my running progression |
+| `*` | user | share my workout plan with my friends | progress together with them |
+| `*` | user | access a workout plan done by my friends | learn from them |
+
+
+
+### **Use cases**
+
+(For all use cases below, the **System** is `Gim` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case 1: Help**
+
+System: Gim
+Use case: UC01 - Request for help
+Actor: User
+
+**MSS**
+
+1. User requests for help.
+2. Gim displays help message.
+ Use case ends.
+
+**Use case 2: Add an exercise**
+
+System: Gim
+Use case: UC02 - Add an exercise
+Actor: User
+
+**MSS**
+
+1. User requests to add an exercise.
+2. Gim adds the exercise into storage.
+ Use case ends.
+
+**Extensions**
+
+* 1a. User enters the command wrongly.
+ * 1a1. Gim shows an error message.
+ Use case resumes at step 1.
+
+
+
+**Use case 3: Delete an exercise**
+
+System: Gim
+Use case: UC03 - Delete an exercise
+Actor: User
+
+**MSS**
+
+1. User requests to delete an exercise.
+2. Gim deletes the exercise.
+ Use case ends.
+
+**Extensions**
+
+* 1a. User enters the command wrongly.
+ * 1a1. Gim displays the error message.
+ Use case resumes at step 1.
+
+* 1b. User enters an exercise that does not exist in the app.
+ * 1b1. Gim displays that the exercise does not exist.
+ Use case ends.
+
+**Use case 4: Clear all exercise entries in the system**
+
+System: Gim
+Use case: UC04 - Clear all exercise entries in the system
+Actor: User
+
+**MSS**
+
+1. User requests to clear all exercise entries in the system.
+2. Gim clears all exercise entries in the system.
+ Use case ends.
+
+**Extensions**
+
+* 1a. User enters the command wrongly.
+ * 1a1. Gim shows an error message.
+ Use case resumes at step 1.
+
+
+
+**Use case 5: List exercises**
+
+System: Gim
+Use case: UC05 - List exercises
+Actor: User
-*{More to be added}*
+**MSS**
-### Use cases
+1. User requests to list all stored exercises.
+2. Gim lists the stored exercises.
+ Use case ends.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+**Use case 6: Sort exercises**
-**Use case: Delete a person**
+System: Gim
+Use case: UC06 - Sort exercises
+Actor: User
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to sort the displayed list of exercises.
+2. Gim sorts the displayed list of exercises by date.
+ Use case ends.
+
+**Use case 7: View exercises within a time period**
- Use case ends.
+System: Gim
+Use case: UC07- View exercises within a time period
+Actor: User
+
+**MSS**
+
+1. User requests to view displayed exercises within a time period.
+2. Gim displays exercises completed within the specified time period in an order sorted by date.
+ Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 1a. User enters the command wrongly.
+ * 1a1. Gim displays the error message.
+ Use case resumes at step 1.
+* 1b. User enters an invalid date.
+ * 1b1. Gim displays the invalid date error message.
+ Use case resumes at step 1.
- Use case ends.
+
-* 3a. The given index is invalid.
+**Use case 8: Filter exercises by keyword(s)**
- * 3a1. AddressBook shows an error message.
+System: Gim
+Use case: UC08 - Filter exercises by keyword(s)
+Actor: User
- Use case resumes at step 2.
+**MSS**
-*{More to be added}*
+1. User requests to filter the displayed list of exercises by keyword(s).
+2. Gim displays the filtered list of exercises.
+ Use case ends.
-### Non-Functional Requirements
+**Extensions**
-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 persons 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.
+* 1a. User enters the command wrongly.
+ * 1a1. Gim displays the error message.
+ Use case resumes at step 1.
+* 2a. Filtered list of exercises is empty.
+ * 2a1. Gim displays a reminder message.
+ Use case ends.
-*{More to be added}*
+**Use case 9: View Personal Record (PR) for exercise(s)**
-### Glossary
+System: Gim
+Use case: UC09 - View Personal Record (PR) for exercise(s)
+Actor: User
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+**MSS**
+
+1. User requests to view Personal Record (PR) for exercise(s).
+2. Gim calculates and displays the Personal Record (PR) for exercise(s).
+ Use case ends.
+
+**Extensions**
+
+* 1a. User enters the command wrongly.
+ * 1a1. Gim displays the error message.
+ Use case resumes at step 1.
+* 1b. User enters the name of exercise(s) wrongly.
+ * 1b1. Gim displays exercise(s) not registered in system message.
+ Use case resumes at step 1.
+
+
+
+**Use case 10: Generate workout suggestion for exercise(s)**
+
+System: Gim
+Use case: UC10 - Generate workout suggestion for exercise(s)
+Actor: User
+
+**MSS**
+
+1. User requests to generate a workout suggestion.
+2. Gim computes a sample workout for the user.
+ Use case ends.
+
+**Extensions**
+
+* 1a. User enters the command wrongly.
+ * 1a1. Gim displays the error message.
+ Use case resumes at step 1.
+* 1b. User enters an invalid index.
+ * 1b1. Gim displays the invalid index error message.
+ Use case resumes at step 1.
+* 1c. User enters an incorrect format for index(es).
+ * 1c1. Gim displays the incorrect index format message.
+ Use case resumes at step 1.
+* 1d. User enters the name of exercise(s) wrongly.
+ * 1d1. Gim displays exercise(s) not registered in system message.
+ Use case resumes at step 1.
+* 1e. User enters an invalid difficulty level.
+ * 1e1. Gim displays the invalid difficulty level message.
+ Use case resumes at step 1.
+
+
+**Use case 11: Exit Gim**
+
+System: Gim
+Use case: UC11 - Exit Gim
+Actor: User
+
+**MSS**
+
+1. User requests to exit Gim.
+2. Gim exits.
+ Use case ends.
+
+
+
+### **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 exercises without a noticeable sluggishness in performance for typical usage.
+3. Should work without an internet connection.
+4. Should be able to support frequent updating of data.
+5. 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.
+6. Should not be able to verify if the user actually perform the exercises they input.
+7. Users with no coding background should be able to use Gim.
+8. Should be optimised for a single user.
+9. The product file size should not exceed 100MB.
+10. Data should be persisted locally and be in a human-readable format, e.g. JSON.
+11. Should be delivered to a user as a single JAR file.
+12. Should not lose any data if application is closed through external means (i.e. not using exit command).
+
+
+### **Glossary**
+* **Exercise**: Physical activity done in a regular gym that is structured and repetitive, usually involving some weights.
+* **Mainstream OS**: Windows, Linux, Unix, OS-X.
+* **Personal Record (PR)**: Heaviest weight recorded in the exercise tracker for a specific exercise.
+* **Rate of Perceived Exertion (RPE)**: A measure of a physical activity intensity level.
+* **Reps**: Number of times you perform a specific exercise.
+* **Sets**: Number of cycles of reps that you complete.
+* **Vim**: A Unix text editor, known for being lightweight, fast and efficient. It can be controlled entirely with the keyboard with no need for menus or a mouse.
+* **Weight**: Total weight of equipment (in kg).
--------------------------------------------------------------------------------------------------------------------
+
+
## **Appendix: Instructions for manual testing**
Given below are instructions to test the app manually.
@@ -334,44 +839,179 @@ testers are expected to do more *exploratory* testing.
-### Launch and shutdown
+**Launch and shutdown**
1. Initial launch
1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Double-click the jar file Expected: Shows the GUI with a set of sample exercises. The window size may not be optimum.
-1. Saving window preferences
+2. Saving window preferences
1. Resize the window to an optimum size. Move the window to a different location. Close the window.
1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+
+
+**Adding an exercise**
+
+1. Adding an exercise to the system.
-### Deleting a person
+ 1. Test case: `:add n/Squat w/60 s/1 r/5 d/25/01/2022`.
+ Expected: A new exercise with the name `Squat`, with weight `60kg`, with set `1`, with reps `5` and with date `25/01/2022` is added at the bottom of the Exercise List in Gim.
+ The Result Display Window indicates that an exercise named `Squat` has been successfully added.
+
+ 2. Test case: `:add n/Squat w/60 s/1 r/5`.
+ Expected: A new exercise with the name `Squat`, with weight `60kg`, with set `1`, with reps `5` and with today's date is added at the bottom of the Exercise List in Gim.
+ The Result Display Window indicates that an exercise named `Squat` has been successfully added.
-1. Deleting a person while all persons are being shown
+ 3. Test case: `:add n/Squat`.
+ Expected: No exercise is added. The Result Display Window indicates that the command is of an invalid format.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 4. Other incorrect `:add` commands to try: `:add`, `:add x/invalid`, `...` (where x is any invalid prefix).
+ Expected: Similar to previous.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+**Deleting an exercise**
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+1. Deleting an exercise while all exercises are being shown
+
+ 1. Prerequisite: List all exercises using the `:list` command. Multiple exercises in the list.
+
+ 2. Test case: `:del 1`
+ Expected: First exercise is deleted from the list. Details of the deleted exercise shown in the Result Display Window. Timestamp in the status bar is updated.
+
+ 3. Test case: `:del 0`
+ Expected: No exercise is deleted. Error details shown in the Result Display Window. Status bar remains the same.
+
+ 4. Other incorrect delete commands to try: `:del`, `:del x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+**Clearing all exercises in the system**
+
+1. Clearing all exercises in the system
+
+ 1. Prerequisite: There is at least one exercise in the Exercise List.
+
+ 2. Test case: `:clear`
+ Expected: The Result Display Window will indicate that the command is invalid.
+
+ 3. Test case: `:clear confirnm/`
+ Expected: All exercises in the system is cleared. The Result Display Window will indicate that the exercise tracker has been cleared.
+
+
+
+**Filtering exercises by keyword(s)**
+
+1. Filtering displayed list of exercises using keyword(s).
+
+ 1. Prerequisite: There are multiple exercises in the Exercise List.
+
+ 2. Prerequisite: There exists an exercise with exercise name `Deadlift`.
+ Test case: `:filter Deadlift`.
+ Expected: The exercise with exercise name `Deadlift` appears in the Exercise List after executing the command.
+
+ 3. Prerequisite: There exists an exercise with exercise name `Deadlift` and an exercise with exercise name `Squat`.
+ Test case: `:filter Deadlift Squat`.
+ Expected: The exercise with exercise name `Deadlift` and exercise with exercise named `Squat` appear in the Exercise List after executing the command.
+
+ 4. Other incorrect `:filter` commands to try: `:filter` (no keywords provided).
+ Expected: The Result Display Window will indicate that the command is invalid.
+
+**Sorting exercises**
+
+1. Sorting displayed list of exercises by order of date.
+
+ 1. Prerequisite: There are multiple exercises in the Exercise List.
+
+ 2. Test case: `:sort`.
+ Expected: The exercises displayed in the Exercise List will be sorted by date after executing the command.
+
+
+
+**Viewing exercises within a time period**
+
+1. Viewing exercises within a time period.
+
+ 1. Prerequisite: There is at least one exercise in the Exercise List.
+
+ 2. Test case: `:range last/5`.
+ Expected: List exercises completed in the last 5 days.
+
+ 3. Test case: `:range start/01/01/2022 end/31/01/2022`.
+ Expected: List exercises completed between 01/01/2022 and 31/01/2022.
+
+ 4. Test case: `:range start/01/01/202222 end/31/01/202222`.
+ Expected: The Result Display Window will indicate that the date input format is invalid.
+
+ 5. Other incorrect `:range` commands to try: `:range`, `range last/abc`, `range start/01/01/2022`, ...
+ Expected: The Result Display Window will indicate that the command is invalid.
+
+
+
+**Generating workout suggestion for exercise(s)**
+
+1. Generating workout suggestion using index(es).
+
+ 1. Prerequisite: There is at least one exercise in the Exercise List.
+
+ 2. Test case: `:gen 1 level/easy`.
+ Expected: Generate a workout with difficulty level easy for the exercise at index 1 of the Exercise List.
+
+ 3. Prerequisite: There is only one exercise in the Exercise List.
+ Test case: `:gen 2 level/easy`.
+ Expected: The Result Display Window will indicate that the index is invalid.
+
+2. Generating workout suggestion using exercise name(s).
+
+ 1. Prerequisite: There is at least one exercise in the Exercise List.
+
+ 2. Prerequisite: There exists an exercise with exercise name `Deadlift`.
+ Test case: `:gen n/Deadlift level/easy`.
+ Expected: Generate a workout with difficulty level easy for exercise with exercise name `Deadlift`.
+
+ 3. Prerequisite: There does not exist an exercise with exercise name `Squat`.
+ Test case: `:gen n/Squat level/easy`.
+ Expected: The Result Display Window will indicate that the exercise is not registered in the system.
+
+ 4. Test case: `:gen n/Deadlift level/easyyyyyy`.
+ Expected: The Result Display Window will indicate that the level is not supported.
+
+ 5. Other incorrect `:gen` commands to try: `:gen` (no keywords provided).
+ Expected: The Result Display Window will indicate that the command is invalid.
+
+
+
+**Listing Personal Records (PR)**
+
+1. Listing Personal Record(s) of exercise(s).
+
+ 1. Prerequisite: There is at least one exercise in the Exercise List.
+
+ 2. Prerequisite: There exists at least one exercise with exercise name `Deadlift`.
+ Test case: `:pr n/Deadlift`.
+ Expected: List the Personal Record for exercise with exercise name `Deadlift`.
+
+ 3. Prerequisite: There exists only exercises with exercise name `Deadlift` and exercise name `Squat`.
+ Test case: `:pr all/`.
+ Expected: List the Personal Records for exercises with exercise name `Deadlift` and exercise name `Squat`.
+
+ 4. Prerequisite: There does not exist an exercise with exercise name `Bench press`.
+ Test case: `:pr n/Bench press`.
+ Expected: The Result Display Window will indicate that the exercise is not registered in the system.
+
+ 5. Other incorrect `:pr` commands to try: `:pr` (no keywords provided).
+ Expected: The Result Display Window will indicate that the command is invalid.
-### Saving data
+**Saving data**
1. Dealing with missing/corrupted data files
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Prerequisites: Able to access the `/data` folder (created in the same folder containing the `Gim.jar` file).
-1. _{ more test cases … }_
+ 2. Test Case: Navigate to the `/data` folder and delete the `exercisetracker.json` file. Then reopen the application.
+ Expected: The application will be repopulated with the initial starting data.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..95b501aa529 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -23,7 +23,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
+ 1. Run the `gim.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
--------------------------------------------------------------------------------------------------------------------
@@ -45,7 +45,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Learn the design**
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
+ When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [Gim’s architecture](DeveloperGuide.md#architecture).
1. **Do the tutorials**
These tutorials will help you get acquainted with the codebase.
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..4e9823a83c9 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -29,8 +29,8 @@ There are two ways to run tests.
This project has three types of tests:
1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
+ e.g. `gim.commons.StringUtilTest`
1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
+ e.g. `gim.storage.StorageManagerTest`
1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
+ e.g. `gim.logic.LogicManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..cf3eba758da 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,190 +3,555 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+
+
+
+
+
+
+
+**Table of Contents**
* Table of Contents
{:toc}
+--------------------------------------------------------------------------------------------------------------------
+
+
+
+## 1. Introduction
+
+### 1.1. What is Gim?
+Gim is a desktop app for **managing gym exercises**. Gim allows you to **keep track of your progress** and **craft personalised workout plans**. Gim commands are inspired by those of [Vim](#8-glossary-of-terminologies). Gim is optimised for use via a [Command Line Interface (CLI)](#8-glossary-of-terminologies) while still having the benefits of a [Graphical User Interface (GUI)](#8-glossary-of-terminologies).
+
+### 1.2. Who is this guide for?
+Are you a gym-goer looking to use Gim to track your exercises? This user guide will get you started in no time and help you navigate through Gim's features. For a quick start guide, head over to [Getting Started](#3-getting-started).
+
+--------------------------------------------------------------------------------------------------------------------
+
+## 2. How to use this guide?
+Gim uses a Command Line Interface (CLI), which may be new to some users. If you are a new user, we strongly recommend you to look through the user guide from start to end to fully understand how to use Gim. However, you may also choose to skip to the relevant sections described below:
+* Refer to our Table of Contents to easily navigate between sections of the User Guide.
+* Refer to our [Getting Started](#3-getting-started) guide to learn how to setup Gim.
+* Refer to our [GUI Orientation](#4-gui-orientation) to better orientate yourself around the GUI.
+* Refer to our [Commands](#5-commands) section to learn in detail the different features and commands available in Gim.
+* Refer to our [FAQ](#6-faq) to read common queries that new users may have.
+* Refer to our [Command Summary](#7-command-summary) to have a quick overview of the different commands and their respective formats.
+* Refer to our [Glossary of Terminologies](#8-glossary-of-terminologies) to learn key terms that are used in this User Guide.
+
+### 2.1 Useful Notations
+While exploring Gim's features with this user guide, do take note of these symbols used in the user guide and what they represent.
+
+| Symbol | Meaning |
+|:--------------------:|-------------------------|
+| :information_source: | Important information |
+| :exclamation: | Warning or Caution |
+| :thinking: | When should I use this? |
+| :bulb: | Tips |
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+
+
+## 3. Getting Started
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. Download the latest `gim.jar` [here](https://github.com/AY2223S1-CS2103T-T15-4/tp/releases).
+
+3. Copy the file to the folder you want to use as the _home folder_ for your Gim.
+
+4. Double-click the file to start the app. The GUI similar to the one shown below should appear in a few seconds. Note how the app contains some sample data.
+ ![Ui](images/Ui.png)
+
+
+5. Type the command in the command box and press Enter to execute it. e.g. typing **`:help`** and pressing Enter will open the help window.
+
+6. Refer to the [Commands Section](#5-commands) below for details of each command.
+
+Back To Table of Contents
+
+--------------------------------------------------------------------------------------------------------------------
+
+
+## 4. GUI Orientation
+
+![GUI](images/GUIOrientation.png)
+
+### 4.1. Command Box
+
+The `Command Box` is where you can input your commands.
+
+### 4.2. Exercise List
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+The `Exercise List` displays exercise entries. When the application is first launched, the `Exercise List` displays all exercise entries in the system, arranged by the order in which they were added. Whenever you issue commands that may truncate/reorder the `Exercise List`, they will **only act upon the entries that are currently displayed in the** `Exercise List`.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
+### 4.3. Result Display
- * **`list`** : Lists all contacts.
+The `Result Display Window` displays feedback after executing a command. This includes feedback for both correctly and incorrectly entered commands.
- * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+### 4.4. Recognised Exercise Name List
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+The `Recognised Exercise Name List Window` provides you a list of all unique exercise names that are currently registered in the system.
+
- * **`clear`** : Deletes all contacts.
+Notice that there are two counts.
- * **`exit`** : Exits the app.
+* **Count #1**: Displays the number of exercise names registered in the system (i.e. does not include duplicates)
+* **Count #2**: Displays the total number of exercise entries in the system (i.e. includes duplicates)
-1. Refer to the [Features](#features) below for details of each command.
+For illustrative purposes, let us refer to the image below. There are multiple Squat entries in the system registered under the same name Squat, which is reflected by **Count #1**. On the other hand, because **Count #2** includes duplicates, it will count both "Squat" entries individually even if they are registered under the same name.
+
+![RecognisedList](images/RecognisedExercisesOrientation.png)
+
+
+
+
:bulb:
+**Tip:** Use the list circled in green to identify any mispellings in your exercise entries!
+
+E.g. If you see an entry for Squatz in the green list when you normally name your exercise Squat, it means you may have an incorrectly spelled entry.
+
+
+
:exclamation: **Caution:**
+Exercise names are recognised as equal if, upon removing white spaces and setting the names to lowercase, the names are the same.
+i.e. Bench Press, BENCH PRESS, BenchPress will be logged as the same exercise for your convenience in adding.
+However, the first time you add an exercise with an unrecognised name, the Recognised Exercise Name List will save the form you have input. Choose wisely!
+
+
+Back To Table of Contents
--------------------------------------------------------------------------------------------------------------------
-## Features
+
+
+## 5. Commands
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+* Words in `UPPER_CASE` are the [parameters](#8-glossary-of-terminologies) to be supplied by you.
+ E.g. In `n/NAME w/WEIGHT`, `NAME` and `WEIGHT` are parameters which can be used as `n/Squat w/100`.
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ E.g. `n/NAME [d/DATE]` can be used as `n/Deadlift d/27-10-2022` or as `n/Deadlift`.
-* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+* Items with `…` after them can be used multiple times including zero times.
+ E.g. `[n/NAME]…` can be used as ` ` (i.e. 0 times), `n/Squat` (i.e. 1 time), `n/Squat n/Deadlift` (i.e. 2 times) etc.
* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+ E.g. If the command specifies `n/NAME w/WEIGHT`, `w/WEIGHT n/NAME` is also acceptable.
-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
+* If a parameter is expected only once in the command but you specify it multiple times, only the last occurrence of the parameter will be taken.
+ E.g. If you specify `n/Squat n/Deadlift`, only `n/Deadlift` will be taken.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* Redundant inputs for commands that do not take in additional parameters (such as `:list`, `:sort`, `:help` `:wq`) will be ignored.
+ E.g. If you specify `:help 123`, it will be interpreted as `:help`.
-### Viewing help : `help`
+
-Shows a message explaning how to access the help page.
+### 5.1. Adding an exercise: `:add`
-![help message](images/helpMessage.png)
+Adds an exercise that we have done for the day.
+
+
-Format: `help`
+**:information_source: Note about add:**
+If an exercise (identified by their names) is added for the first time, it is automatically registered as a new unique exercise.
+
:bulb:
+**Tip:** If `d/DATE` field is left empty, the system will store the exercise with the current date.
+
+
+Parameter constraints:
+* The name **must only contain alphanumeric** (alphabets & numbers) **characters and spaces**.
+ * Examples: Squat, Bench press, Deadlift...
+* The [weight](#8-glossary-of-terminologies) **must be a non-negative decimal number, up to 3 digits for the whole number and up to 2 digits for the decimal place**.
+ * Examples: 0, 0.55, 35, 100.1, 200.00...
+* The [sets](#8-glossary-of-terminologies) **must be a positive integer, up to 3 digits, with no leading zeroes**.
+ * Examples: 1, 2, 3, 10, 100...
+* The [reps](#8-glossary-of-terminologies) **must be a positive integer, up to 3 digits, with no leading zeroes**.
+ * Examples: 1, 2, 3, 10, 100...
+* The date **must be a valid date**.
+ * Accepted formats:
+ * day/month/year
+ * year/month/day
+ * day-month-year
+ * year-month-day
+ * day month year
+ * year month day
+ * The day **must be a positive integer, up to 2 digits**.
+ * The month **must be a positive integer, up to 2 digits**.
+ * The year **must be a positive integer, with exactly 4 digits**.
+ * Examples: 27/10/2022, 01-10-2022...
+
+
+
+Examples:
+* `:add n/Squat w/30 s/3 r/5` Adds a Squat exercise of weight 30kg for 3 sets of 5 reps for today's date.
+* `:add n/Deadlift w/60 s/1 r/1 d/27/01/2022` Adds a Deadlift exercise of weight 60kg for 1 set of 1 rep for 27th January 2022.
-### Adding a person: `add`
+![AddCommand](images/AddCommand.png)
-Adds a person to the address book.
+
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+### 5.2. Deleting an exercise : `:del`
+
+Deletes a particular exercise from our list.
+
+
+
+**:information_source: Note about delete:**
+If the deleted exercise was the last exercise with the same name, then the exercise is automatically de-registered from the list of unique exercises.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+Format: `:del INDEX`
-### Listing all persons : `list`
+Parameter constraints:
+* The [index](#8-glossary-of-terminologies) **must be a positive integer**.
+ * Example: 1, 2, 3, ...
-Shows a list of all persons in the address book.
+Example:
+* `:del 9` Deletes an exercise at index 9 of the list.
-Format: `list`
+### 5.3. Clearing all exercises : `:clear`
-### Editing a person : `edit`
+Clears the saved exercises and resets the data in the system.
-Edits an existing person in the address book.
+Format: `:clear confirm/`
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+
:exclamation: **Caution:**
+Redundant inputs (before and after the `confirm/` flag) will be ignored. E.g. If the command specifies `:clear abc confirm/ 123`, it will be interpreted as `:clear confirm/`.
+
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+Example:
+* `:clear confirm/` Clears all saved exercises, resetting the data in the system.
-Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+
-### Locating persons by name: `find`
+### 5.4. Filtering exercises by keyword(s) : `:filter`
-Finds persons whose names contain any of the given keywords.
+Filters exercises, in the current [Exercise List](#42-exercise-list), with names containing any of the given keywords.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Format: `:filter KEYWORD [KEYWORD]...`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+**:information_source: Notes about filter:**
+
+* Only the exercise name is searched.
+* The keyword is case-insensitive. e.g bench will match Bench.
+* The order of the keywords does not matter. e.g. Deadlift Squat will match Squat Deadlift.
+* Only full words will be matched e.g. Squat will not match Squats.
+* Exercises matching at least one keyword will be returned e.g. `:filter Bench press` will return Bench press and Leg press.
+
+
+
+
+
+**:thinking: When should I use this?**
+I should use this when I want to find the entries of a specific exercise.
+
+
+
+Example:
+* `:filter Deadlift Squat` Shows the list of Deadlift and Squat exercises.
+
+![FilterCommand](images/FilterCommand.png)
+
+
+
+### 5.5. Sorting exercises : `:sort`
+
+Sorts the exercises, in the current [Exercise List](#42-exercise-list), according to their date of completion, with the latest exercise completed displayed at the top of the list.
+
+Format: `:sort`
+
+Example:
+* `:sort` Shows the sorted list of exercises.
+
+![ListAfterSortCommand](images/ListAfterSortCommand.png)
+
+
+### 5.6. Viewing all exercises within a time period : `:range`
+
+Shows all exercises, among exercises in the current [Exercise List](#42-exercise-list), within the specified date range; the latest exercise completed is displayed at the top of the list.
+
+
+
+**:information_source: There are 2 formats supported for this command.**
+
+
+
+
+
+Format (1) : `:range start/START_DATE end/END_DATE`
+
+Parameter constraints:
+* The START_DATE and END_DATE **must be a valid date**.
+ * Accepted formats:
+ * day/month/year
+ * year/month/day
+ * day-month-year
+ * year-month-day
+ * day month year
+ * year month day
+ * The day **must be a positive integer, up to 2 digits**.
+ * The month **must be a positive integer, up to 2 digits**.
+ * The year **must be a positive integer, with exactly 4 digits**.
+* Start date should be before end date. Otherwise, no exercises will be displayed.
+
+Example:
+* `:range start/25/10/2022 end/26/10/2022` Shows the exercises done between October 25, 2022 and October 26, 2022 (both inclusive).
+
+![RangeCommandOne](images/RangeCommandOneSample.png)
-### Deleting a person : `delete`
+
-Deletes the specified person from the address book.
+Format (2) : `:range last/NUMBER_OF_DAYS`
-Format: `delete INDEX`
+Parameter constraints:
+* Number of days **can only take non-negative integer values** up to 5 digits, excluding leading zeroes.
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+Example:
+* `:range last/3` Shows the exercises done today and the last 3 days.
+
+![RangeCommandTwo](images/RangeCommandTwoSample.png)
+
+
+
+### 5.7. Listing all exercises : `:list`
+
+Shows a list of all exercises.
+
+
+
+**:thinking: When should I use this?**
+I should use this when I want to reset my Exercise List back to the default state (before any sorting/filtering commands were used).
+
+
+
+Format: `:list`
+
+Example:
+* `:list` Shows the list of exercises you have completed.
+
+
:bulb:
+**Tip: Tracking exercise progressions over time**
+If you want to view your squat progression over the past week, here's a nifty sequence of commands you can try!
+1. `:list` Current exercise list now shows all exercises.
+2. `:filter Squat` Current exercise list now shows 'Squat' exercises.
+3. `:range last/7` Current exercise list now shows 'Squat' exercises in the past 7 days.
+
+
+
+
+
+### 5.8. Listing Personal Records (PR): `:pr`
+
+Finds the [Personal Record](#8-glossary-of-terminologies) of certain exercises in the exercise tracker.
+
+
+
+**:information_source: There are 2 formats supported for this command.**
+
+
+
+Format (1): `:pr n/NAME [n/NAME]...`
+
+Parameter constraints:
+* Name **must only contain alphanumeric** (alphabets & numbers) **characters and spaces**.
+ * Examples: Squat, Bench press, Deadlift...
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `:pr n/Squat` Lists the personal record for the Squat exercise (if found).
+* `:pr n/Deadlift n/Bench press n/Squat` Lists the personal records for the Deadlift, Bench press and Squat exercises (if found).
-### Clearing all entries : `clear`
+![PrCommandExample1](images/PrCommandExample1.png)
-Clears all entries from the address book.
+
-Format: `clear`
+Format (2): `:pr all/`
-### Exiting the program : `exit`
+
:exclamation: **Caution:**
+Redundant inputs (before and after the `all/` flag) will be ignored. E.g. If the command specifies `:pr abc all/ 123`, it will be interpreted as `:pr all/`.
+
-Exits the program.
+Example:
+* `:pr all/` Lists the personal records for all uniquely registered exercises in the exercise tracker.
+
+![PrCommandExample2](images/PrCommandExample2.png)
+
+
+
+### 5.9. Generating a sample workout based on Personal Records: `:gen`
+
+Generates a sample workout suggestion based on existing personal records of the exercises, according to the difficulty level specified. Exercises are indicated either by their [index](#8-glossary-of-terminologies) or their exercise names.
+
+
+
+**:thinking: When should I use this?**
+I should use this when I want to get a quick workout plan based on how I am feeling.
+
+
+
+
+
+**:information_source: There are 2 formats supported for this command.**
+
+
+
+
+
+Format (1): `:gen INDEX [, INDEX]... level/DIFFICULTY_LEVEL`
-Format: `exit`
+Parameter constraints:
+* The index **must be a positive integer**.
+ * Example: 1, 2, 3, ...
+* The difficulty level must be supported; currently supported are: easy, medium, hard.
-### Saving the data
+Example:
+* `:gen 4, 5 level/easy` Generates a sample workout for Squat; this command is equivalent to `:gen 4 level/easy` since both index 4 and 5 in the displayed list are Squat exercises.
+* `:gen 1, 2 level/easy` Generates a sample workout for exercises at index 1 and 2 of the list, Deadlift and Incline Bench.
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+![GenerateCommandExample1](images/GenerateCommandExample1.png)
-### Editing the data file
+
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+Format (2): `:gen n/NAME [n/NAME]... level/DIFFICULTY_LEVEL`
-
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run.
+Parameter constraints:
+* Name **must only contain alphanumeric** (alphabets & numbers) **characters and spaces**.
+* The difficulty level must be one that is supported; currently supported are: easy, medium, hard.
+
+
:exclamation: **Caution:**
+Redundant inputs (before the first `n/` flag) will be ignored.
+E.g. if the command specifies `:gen 1,2,3 n/Squat level/easy`, it will be interpreted as `:gen n/Squat level/easy`.
-### Archiving data files `[coming in v2.0]`
+Example:
+* `:gen n/Squat n/Squat level/easy` Generates a sample workout for Squat; this command is equivalent to `:gen n/Squat level/easy` since both exercise names are the same.
+* `:gen n/Squat n/Deadlift level/easy` Generates a sample workout for exercises Squat and Deadlift (if found).
+
+![GenerateCommandNameExample1](images/GenerateCommandNameExample1.png)
+
+
+
+### 5.10. Viewing help : `:help`
+
+Accesses the help menu. The help menu contains a brief summary of the commands supported and provides a link to the user guide.
+
+Format: `:help`
+
+![HelpCommand](images/HelpCommand.png)
-_Details coming soon ..._
+
+
+### 5.11. Exiting the program : `:wq`
+
+Exits the program.
+
+Format: `:wq`
+
+
+
+**:information_source: Gim data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually.**
+
+
+
+Back To Table of Contents
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+
+
+## 6. FAQ
+
+**Q**: Do I have to update the [Recognised Exercise Name List Window](#44-recognised-exercise-name-list) manually?
+
+**A**: You do not have to update it manually as the Recognised Exercise Name List is already updated automatically whenever you add or delete an exercise entry from the system.
+
+**Q**: Can I change the name of my uniquely registered exercise?
+
+**A**: To change the way it is represented, you can find the exercise with the name, delete the entries and re-enter the exercises with your new name of choice.
+
+
+
+**:information_source: The way you format the exercise name when you first add it will be the way it is displayed in the system. After that, all exercises added that have the same name will be categorised under the same exercise.**
+
+
+
+**Q**: Can I edit an exercise?
+
+**A**: You can do so by deleting the entry and adding a new entry.
+
+**Q**: Why is `:filter`, `:range` or `:sort` not showing the "correct" list even though I have input valid parameters?
+
+**A**: The three commands works on the exercises in the current [Exercise List](#42-exercise-list). If your current Exercise List has been altered by list-changing commands such as `:range` or `:filter`, the commands will act on the current Exercise List rather than the full list comprising all exercises in the system.
+
+
:bulb:
+**Tip:** If you would like to operate on the full list instead, try executing the command `:list` to display the full list before running the commands again.
+
+
+**Q**: How is data stored in Gim?
+
+**A**: Gim data is stored in `[JAR file location]/data/exercisetracker.json`. If you are comfortable working with [JSON](#8-glossary-of-terminologies) files, you are welcome to update Gim's data by editing the data file directly.
+
+
:exclamation: **Caution:**
+If your changes to the data file makes its format invalid, Gim will discard all data and start with an empty data file at the next run.
+
+
+Back To Table of Contents
+
+--------------------------------------------------------------------------------------------------------------------
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+
+
+## 7. Command Summary
+
+| Action | Format | Examples |
+|------------------------------------|------------------------------------------------|-------------------------------------------|
+| **Add exercise** | :add n/NAME w/WEIGHT s/SETS r/REPS [d/DATE] | :add n/Deadlift w/60 s/1 r/1 d/27/10/2022 |
+| **Delete exercise** | :del INDEX | :del 3 |
+| **Clear all exercises** | :clear confirm/ | :clear confirm/ |
+| **Filter exercise(s)** | :filter KEYWORD [KEYWORD]... | :filter Deadlift Squat |
+| **Sort exercises** | :sort | :sort |
+| **View range (1)** | :range start/START_DATE end/END_DATE | :range start/25/10/2022 end/26/10/2022 |
+| **View range (2)** | :range last/NUMBER_OF_DAYS | :range last/3 |
+| **List all exercises** | :list | :list |
+| **List Personal Record(s) (1)** | :pr n/NAME [n/NAME]... | :pr n/Deadlift n/Squat |
+| **List Personal Record(s) (2)** | :pr all/ | :pr all/ |
+| **Generate workout (1)** | :gen INDEX [, INDEX]... level/DIFFICULTY_LEVEL | :gen 1, 2 level/easy |
+| **Generate workout (2)** | :gen n/NAME [n/NAME]... level/DIFFICULTY_LEVEL | :gen n/Deadlift level/easy |
+| **Help menu** | :help | :help |
+| **Exit program** | :wq | :wq |
+
+Back To Table of Contents
--------------------------------------------------------------------------------------------------------------------
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+
+
+## 8. Glossary of Terminologies
+* **Command**: An instruction to perform an action in Gim, preceded by a colon, i.e. `:instruction`
+* **Command Line Interface (CLI)**: Interface that relies on keyboard inputs to interact with the system.
+* **Exercise**: Physical activity done in a regular gym that is structured and repetitive, usually involving some weights.
+* **Graphical User Interface (GUI)**: Interface that relies on mouse inputs on visible components to interact with the system.
+* **Index**: Number associated to an Exercise in the [Exercise List](#42-exercise-list).
+* **JavaScript Object Notation (JSON)**: Filetype used for storing the user's data that can be edited using a text editor.
+* **Parameters**: Inputs for commands that you come up with.
+* **Personal Record (PR)**: Heaviest weight recorded in the exercise tracker for a specific exercise.
+* **Reps**: Number of times you perform a specific exercise.
+* **Sets**: Number of cycles of reps that you complete.
+* **Vim**: A text editor, known for being lightweight, fast and efficient. It can be controlled entirely with the keyboard with no need for menus or a mouse.
+* **Weight**: Total weight of equipment (in kg).
+
+Back To Table of Contents
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..b176aeb2764 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "Gim"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2223S1-CS2103T-T15-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..4e602615818 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,8 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "Gim";
font-size: 32px;
}
}
-
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..96431b17948 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,19 +7,19 @@ Participant ":Logic" as logic LOGIC_COLOR
Participant ":Model" as model MODEL_COLOR
Participant ":Storage" as storage STORAGE_COLOR
-user -[USER_COLOR]> ui : "delete 1"
+user -[USER_COLOR]> ui : ":del 1"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute(":del 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteExercise(p)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> storage : saveExerciseTracker(exerciseTracker)
activate storage STORAGE_COLOR
storage -[STORAGE_COLOR]> storage : Save to file
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 5731f9cbaa1..afa48044358 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -4,18 +4,18 @@ skinparam arrowThickness 1.1
skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
-UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList -[hidden]down- UniquePersonList
+ExerciseTracker *-right-> "1" UniqueExerciseList
+ExerciseTracker *-right-> "1" UniqueTagList
+UniqueTagList -[hidden]down- UniqueExerciseList
+UniqueTagList -[hidden]down- UniqueExerciseList
UniqueTagList *-right-> "*" Tag
-UniquePersonList -right-> Person
+UniqueExerciseList -right-> Exercise
-Person -up-> "*" Tag
+Exercise -up-> "*" Tag
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
+Exercise *--> Name
+Exercise *--> Weight
+Exercise *--> Sets
+Exercise *--> Address
@enduml
diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml
index 6a6b23a006f..85eef30fdc7 100644
--- a/docs/diagrams/CommitActivityDiagram.puml
+++ b/docs/diagrams/CommitActivityDiagram.puml
@@ -5,10 +5,10 @@ start
'Since the beta syntax does not support placing the condition outside the
'diamond we place it as the true branch instead.
-if () then ([command commits AddressBook])
+if () then ([command commits ExerciseTracker])
:Purge redundant states;
- :Save AddressBook to
- addressBookStateList;
+ :Save ExerciseTracker to
+ exerciseTrackerStateList;
else ([else])
endif
stop
diff --git a/docs/diagrams/DateClassDiagram.puml b/docs/diagrams/DateClassDiagram.puml
new file mode 100644
index 00000000000..d85cbd2abb2
--- /dev/null
+++ b/docs/diagrams/DateClassDiagram.puml
@@ -0,0 +1,19 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model <>{
+
+Class Date
+Class FormatterList
+Class RegexList
+
+}
+
+Date --> "1" FormatterList: uses >
+Date --> "1" RegexList: uses >
+
+
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 1dc2311b245..7d8a4695498 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -3,7 +3,7 @@
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ExerciseTrackerParser" as ExerciseTrackerParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
@@ -13,20 +13,20 @@ box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute(":del 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
+LogicManager -> ExerciseTrackerParser : parseCommand(":del 1")
+activate ExerciseTrackerParser
create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
+ExerciseTrackerParser -> DeleteCommandParser
activate DeleteCommandParser
-DeleteCommandParser --> AddressBookParser
+DeleteCommandParser --> ExerciseTrackerParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+ExerciseTrackerParser -> DeleteCommandParser : parse("1")
activate DeleteCommandParser
create DeleteCommand
@@ -36,19 +36,19 @@ activate DeleteCommand
DeleteCommand --> DeleteCommandParser : d
deactivate DeleteCommand
-DeleteCommandParser --> AddressBookParser : d
+DeleteCommandParser --> ExerciseTrackerParser : d
deactivate DeleteCommandParser
'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
+DeleteCommandParser -[hidden]-> ExerciseTrackerParser
destroy DeleteCommandParser
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
+ExerciseTrackerParser --> LogicManager : d
+deactivate ExerciseTrackerParser
LogicManager -> DeleteCommand : execute()
activate DeleteCommand
-DeleteCommand -> Model : deletePerson(1)
+DeleteCommand -> Model : deleteExercise(1)
activate Model
Model --> DeleteCommand
diff --git a/docs/diagrams/GenerateWorkoutSequenceDiagram.puml b/docs/diagrams/GenerateWorkoutSequenceDiagram.puml
new file mode 100644
index 00000000000..77687bfc217
--- /dev/null
+++ b/docs/diagrams/GenerateWorkoutSequenceDiagram.puml
@@ -0,0 +1,76 @@
+@startuml
+!include style.puml
+skinparam sequenceReferenceBackgroundColor AntiqueWhite
+
+box Logic LOGIC_COLOR_T1
+participant ":Logic\nManager" as LogicManager LOGIC_COLOR
+participant ":ExerciseTracker\nParser" as ExerciseTrackerParser LOGIC_COLOR
+participant ":Generate\nCommandParser" as GenerateCommandParser LOGIC_COLOR
+participant "g:Generate\nCommand" as GenerateCommand LOGIC_COLOR
+participant GeneratorFactory as "<>\nGenerator\nFactory" LOGIC_COLOR
+participant ":Command\nResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute\n(":gen 1,2 level/easy")
+activate LogicManager
+
+LogicManager -> ExerciseTrackerParser : parseCommand\n(":gen 1,2 level/easy")
+activate ExerciseTrackerParser
+
+create GenerateCommandParser
+ExerciseTrackerParser -> GenerateCommandParser
+activate GenerateCommandParser
+
+GenerateCommandParser --> ExerciseTrackerParser
+deactivate GenerateCommandParser
+
+ExerciseTrackerParser -> GenerateCommandParser : parse\n("1,2 level/easy")
+activate GenerateCommandParser
+
+create GenerateCommand
+GenerateCommandParser -> GenerateCommand
+activate GenerateCommand
+
+GenerateCommand --> GenerateCommandParser : g
+deactivate GenerateCommand
+
+GenerateCommandParser --> ExerciseTrackerParser : g
+deactivate GenerateCommandParser
+
+GenerateCommandParser -[hidden]-> ExerciseTrackerParser
+destroy GenerateCommandParser
+
+ExerciseTrackerParser --> LogicManager : g
+deactivate ExerciseTrackerParser
+
+LogicManager -> GenerateCommand : execute()
+activate GenerateCommand
+
+
+GenerateCommand -> Model : getExercisePR(exerciseName)
+activate Model
+Model --> GenerateCommand : exercisePR
+deactivate Model
+
+ref over GenerateCommand, GeneratorFactory
+get suggested workouts
+end ref
+
+create CommandResult
+GenerateCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> GenerateCommand
+deactivate CommandResult
+
+GenerateCommand --> LogicManager : result
+deactivate GenerateCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/GeneratorCommandPattern.puml b/docs/diagrams/GeneratorCommandPattern.puml
new file mode 100644
index 00000000000..d4c08eaf493
--- /dev/null
+++ b/docs/diagrams/GeneratorCommandPattern.puml
@@ -0,0 +1,19 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+skinparam classAttributeIconSize 0
+
+
+Class "<>\nGenerator" as Generator
+Generator : + suggest()
+GenerateCommand .right.> Generator : gets suggestion from >
+
+
+GeneratorFactory .right.> XYZGenerator : creates >
+XYZGenerator -up-|> Generator
+
+note right of XYZGenerator: XYZGenerator = EasyGenerator, \nMediumGenerator, etc
+
+@enduml
diff --git a/docs/diagrams/GetSuggestionSequenceDiagram.puml b/docs/diagrams/GetSuggestionSequenceDiagram.puml
new file mode 100644
index 00000000000..0365609fcab
--- /dev/null
+++ b/docs/diagrams/GetSuggestionSequenceDiagram.puml
@@ -0,0 +1,61 @@
+@startuml
+!include style.puml
+
+group sd get suggested workouts
+box LOGIC_COLOR_T1
+
+participant "g:GenerateCommand" as GenerateCommand LOGIC_COLOR
+participant GeneratorFactory as "<>\nGeneratorFactory" LOGIC_COLOR
+participant "s:EasyGenerator" as EasyGeneratorSquat LOGIC_COLOR
+participant "d:EasyGenerator" as EasyGeneratorDeadlift LOGIC_COLOR
+
+
+activate GenerateCommand
+GenerateCommand -> GeneratorFactory : getGenerator(squatPR, EASY)
+
+activate GeneratorFactory
+create EasyGeneratorSquat
+
+GeneratorFactory -> EasyGeneratorSquat : EasyGenerator(squatPR)
+activate EasyGeneratorSquat
+
+EasyGeneratorSquat --> GeneratorFactory : s
+deactivate EasyGeneratorSquat
+
+GeneratorFactory --> GenerateCommand : s
+deactivate GeneratorFactory
+
+GenerateCommand -> EasyGeneratorSquat : suggest()
+activate EasyGeneratorSquat
+
+EasyGeneratorSquat --> GenerateCommand : suggested workout for squat
+deactivate EasyGeneratorSquat
+
+EasyGeneratorSquat -[hidden]> GenerateCommand
+destroy EasyGeneratorSquat
+
+GenerateCommand -> GeneratorFactory : getGenerator(deadliftPR, EASY)
+
+activate GeneratorFactory
+create EasyGeneratorDeadlift
+
+GeneratorFactory -> EasyGeneratorDeadlift : EasyGenerator(deadliftPR)
+activate EasyGeneratorDeadlift
+
+EasyGeneratorDeadlift --> GeneratorFactory : d
+deactivate EasyGeneratorDeadlift
+
+GeneratorFactory --> GenerateCommand : d
+deactivate GeneratorFactory
+
+GenerateCommand -> EasyGeneratorDeadlift : suggest()
+activate EasyGeneratorDeadlift
+
+EasyGeneratorDeadlift --> GenerateCommand : suggested workout for deadlift
+deactivate EasyGeneratorDeadlift
+
+EasyGeneratorDeadlift -[hidden]> GenerateCommand
+destroy EasyGeneratorDeadlift
+
+end
+@enduml
diff --git a/docs/diagrams/ListPersonalRecordSequenceDiagram.puml b/docs/diagrams/ListPersonalRecordSequenceDiagram.puml
new file mode 100644
index 00000000000..019f01d74fd
--- /dev/null
+++ b/docs/diagrams/ListPersonalRecordSequenceDiagram.puml
@@ -0,0 +1,72 @@
+@startuml
+!include style.puml
+skinparam sequenceReferenceBackgroundColor AntiqueWhite
+
+box Logic LOGIC_COLOR_T1
+participant ":Logic\nManager" as LogicManager LOGIC_COLOR
+participant ":ExerciseTracker\nParser" as ExerciseTrackerParser LOGIC_COLOR
+participant ":Pr\nCommandParser" as PrCommandParser LOGIC_COLOR
+participant "command:Pr\nCommand" as PrCommand LOGIC_COLOR
+participant GeneratorFactory as "<>\nGenerator\nFactory" LOGIC_COLOR
+participant ":Command\nResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute\n(":pr n/Squat")
+activate LogicManager
+
+LogicManager -> ExerciseTrackerParser : parseCommand\n(":pr n/Squat")
+activate ExerciseTrackerParser
+
+create PrCommandParser
+ExerciseTrackerParser -> PrCommandParser
+activate PrCommandParser
+
+PrCommandParser --> ExerciseTrackerParser
+deactivate PrCommandParser
+
+ExerciseTrackerParser -> PrCommandParser : parse("n/Squat")
+activate PrCommandParser
+
+create PrCommand
+PrCommandParser -> PrCommand
+activate PrCommand
+
+PrCommand --> PrCommandParser : command
+deactivate PrCommand
+
+PrCommandParser --> ExerciseTrackerParser : command
+deactivate PrCommandParser
+
+PrCommandParser -[hidden]-> ExerciseTrackerParser
+destroy PrCommandParser
+
+ExerciseTrackerParser --> LogicManager : command
+deactivate ExerciseTrackerParser
+
+LogicManager -> PrCommand : execute()
+activate PrCommand
+
+
+PrCommand -> Model : getExercisePR(exerciseName)
+activate Model
+Model --> PrCommand : exerciseWithPR
+deactivate Model
+
+create CommandResult
+PrCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> PrCommand
+deactivate CommandResult
+
+PrCommand --> LogicManager : commandResult
+deactivate PrCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index d4193173e18..f2ced9dd900 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR
package Logic {
-Class AddressBookParser
+Class ExerciseTrackerParser
Class XYZCommand
Class CommandResult
Class "{abstract}\nCommand" as Command
@@ -27,8 +27,8 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic
LogicManager .right.|> Logic
-LogicManager -right->"1" AddressBookParser
-AddressBookParser ..> XYZCommand : creates >
+LogicManager -right->"1" ExerciseTrackerParser
+ExerciseTrackerParser ..> XYZCommand : creates >
XYZCommand -up-|> Command
LogicManager .left.> Command : executes >
@@ -38,7 +38,7 @@ LogicManager --> Storage
Storage --[hidden] Model
Command .[hidden]up.> Storage
Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+note right of XYZCommand: XYZCommand = AddCommand, \nFilterCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..e37d4327d07 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,46 +5,45 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
-Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
+Class "<>\nReadOnlyExerciseTracker" as ReadOnlyExerciseTracker
Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
Class "<>\nModel" as Model
-Class AddressBook
+Class ExerciseTracker
Class ModelManager
Class UserPrefs
-Class UniquePersonList
-Class Person
-Class Address
-Class Email
+Class ExerciseList
+Class ExerciseHashMap
+Class Exercise
Class Name
-Class Phone
-Class Tag
+Class Weight
+Class Reps
+Class Sets
+Class Date
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+ExerciseTracker .up.|> ReadOnlyExerciseTracker
ModelManager .up.|> Model
Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
-ModelManager -left-> "1" AddressBook
+Model .left.> ReadOnlyExerciseTracker
+ModelManager -left-> "1" ExerciseTracker
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
-
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
-
-ModelManager -->"~* filtered" Person
+ExerciseTracker *--> "1" ExerciseList
+ExerciseTracker *--> "1" ExerciseHashMap
+ExerciseHashMap ---> "~*" Exercise
+ExerciseList ---> "~*" Exercise
+Exercise *--> Name
+Exercise *--> Weight
+Exercise *--> Reps
+Exercise *--> Sets
+Exercise *--> Date
+
+ModelManager -->"~* filtered" Exercise
@enduml
diff --git a/docs/diagrams/NotifyObservers.puml b/docs/diagrams/NotifyObservers.puml
new file mode 100644
index 00000000000..fa9887f2b40
--- /dev/null
+++ b/docs/diagrams/NotifyObservers.puml
@@ -0,0 +1,39 @@
+@startuml
+!include style.puml
+
+box MODEL_COLOR_T1
+participant ":ExerciseHashMap" as ExerciseHashMap MODEL_COLOR
+end box
+box
+
+box UI_COLOR_T1
+participant ":SavedExerciseListWindow" as SavedExerciseListWindow UI_COLOR
+end box
+
+box MODEL_COLOR_T1
+participant ":ExerciseKeys" as ExerciseKeys MODEL_COLOR
+end box
+
+[-> ExerciseHashMap : notifyObservers()
+activate ExerciseHashMap
+
+ExerciseHashMap -> SavedExerciseListWindow : update()
+activate SavedExerciseListWindow
+
+create ExerciseKeys
+SavedExerciseListWindow -> ExerciseKeys
+activate ExerciseKeys
+
+ExerciseKeys -> ExerciseKeys :getDisplay()
+ExerciseKeys --> SavedExerciseListWindow
+deactivate ExerciseKeys
+
+SavedExerciseListWindow -> SavedExerciseListWindow :setText(display)
+SavedExerciseListWindow --> ExerciseHashMap
+deactivate SavedExerciseListWindow
+
+[<-- ExerciseHashMap
+deactivate ExerciseHashMap
+
+
+@enduml
diff --git a/docs/diagrams/ObserverPattern.puml b/docs/diagrams/ObserverPattern.puml
new file mode 100644
index 00000000000..850427fff60
--- /dev/null
+++ b/docs/diagrams/ObserverPattern.puml
@@ -0,0 +1,27 @@
+@startuml
+!include style.puml
+skinparam classAttributeIconSize 0
+
+Package " " <> {
+ Class "<>\nObserver" as Observer LOGIC_COLOR
+ Class ExerciseHashMap MODEL_COLOR
+ Class SavedExerciseListWindow UI_COLOR
+ Class ExerciseKeys MODEL_COLOR
+}
+
+ExerciseHashMap : - internalArrayList : ArrayList
+ExerciseHashMap : - notifyObservers()
+ExerciseHashMap : + addUI(Observer)
+
+SavedExerciseListWindow : + update()
+SavedExerciseListWindow : -exerciseHashMap : ExerciseHashMap
+
+Observer : + update()
+
+ExerciseKeys : + getDisplay()
+
+ExerciseHashMap - "*" Observer
+SavedExerciseListWindow ..|> Observer
+SavedExerciseListWindow --> ExerciseKeys
+
+@enduml
diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml
index 0c7424de6e0..243335feb71 100644
--- a/docs/diagrams/ParserClasses.puml
+++ b/docs/diagrams/ParserClasses.puml
@@ -9,7 +9,7 @@ Class XYZCommand
package "Parser classes"{
Class "<>\nParser" as Parser
-Class AddressBookParser
+Class ExerciseTrackerParser
Class XYZCommandParser
Class CliSyntax
Class ParserUtil
@@ -19,12 +19,12 @@ Class Prefix
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> AddressBookParser
+HiddenOutside ..> ExerciseTrackerParser
-AddressBookParser .down.> XYZCommandParser: creates >
+ExerciseTrackerParser .down.> XYZCommandParser: creates >
XYZCommandParser ..> XYZCommand : creates >
-AddressBookParser ..> Command : returns >
+ExerciseTrackerParser ..> Command : returns >
XYZCommandParser .up.|> Parser
XYZCommandParser ..> ArgumentMultimap
XYZCommandParser ..> ArgumentTokenizer
diff --git a/docs/diagrams/RangeSequenceDiagram.puml b/docs/diagrams/RangeSequenceDiagram.puml
new file mode 100644
index 00000000000..d8693bb2c11
--- /dev/null
+++ b/docs/diagrams/RangeSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":ExerciseTrackerParser" as ExerciseTrackerParser LOGIC_COLOR
+participant ":RangeCommandParser" as RangeCommandParser LOGIC_COLOR
+participant "r:RangeCommand" as RangeCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute(command)
+activate LogicManager
+
+LogicManager -> ExerciseTrackerParser : parseCommand(command)
+activate ExerciseTrackerParser
+
+create RangeCommandParser
+ExerciseTrackerParser -> RangeCommandParser
+activate RangeCommandParser
+
+RangeCommandParser --> ExerciseTrackerParser
+deactivate RangeCommandParser
+
+ExerciseTrackerParser -> RangeCommandParser : parse(arguments)
+activate RangeCommandParser
+
+create RangeCommand
+RangeCommandParser -> RangeCommand
+activate RangeCommand
+
+RangeCommand --> RangeCommandParser : r
+deactivate RangeCommand
+
+RangeCommandParser --> ExerciseTrackerParser : r
+deactivate RangeCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+RangeCommandParser -[hidden]-> ExerciseTrackerParser
+destroy RangeCommandParser
+
+ExerciseTrackerParser --> LogicManager : r
+deactivate ExerciseTrackerParser
+
+LogicManager -> RangeCommand : execute()
+activate RangeCommand
+
+RangeCommand -> Model : sortFilteredExerciseList(DateWithinRangePredicate)
+activate Model
+
+Model --> RangeCommand
+deactivate Model
+
+create CommandResult
+RangeCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> RangeCommand
+deactivate CommandResult
+
+RangeCommand --> LogicManager : result
+deactivate RangeCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/SortSequenceDiagram.puml b/docs/diagrams/SortSequenceDiagram.puml
new file mode 100644
index 00000000000..ab6ee0b471a
--- /dev/null
+++ b/docs/diagrams/SortSequenceDiagram.puml
@@ -0,0 +1,52 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":ExerciseTrackerParser" as ExerciseTrackerParser LOGIC_COLOR
+participant "s:SortCommand" as SortCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute(":sort")
+activate LogicManager
+
+LogicManager -> ExerciseTrackerParser : parseCommand(":sort")
+activate ExerciseTrackerParser
+
+create SortCommand
+ExerciseTrackerParser -> SortCommand
+activate SortCommand
+
+SortCommand --> ExerciseTrackerParser
+deactivate SortCommand
+
+ExerciseTrackerParser --> LogicManager : s
+deactivate ExerciseTrackerParser
+
+LogicManager -> SortCommand : execute()
+activate SortCommand
+
+SortCommand -> Model : sortDisplayedList()
+activate Model
+
+Model --> SortCommand
+deactivate Model
+
+create CommandResult
+SortCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> SortCommand
+deactivate CommandResult
+
+SortCommand --> LogicManager : result
+deactivate SortCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 760305e0e58..047cd6924e8 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -14,12 +14,12 @@ Class JsonUserPrefsStorage
Class "<>\nStorage" as Storage
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Class "<>\nAddressBookStorage" as AddressBookStorage
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
-Class JsonAdaptedTag
+package "ExerciseTracker Storage" #F4F6F6{
+Class "<>\nExerciseTrackerStorage" as ExerciseTrackerStorage
+Class JsonExerciseTrackerStorage
+Class JsonSerializableExerciseTracker
+Class JsonAdaptedExercise
+Class JsonAdaptedDate
}
}
@@ -29,15 +29,15 @@ HiddenOutside ..> Storage
StorageManager .up.|> Storage
StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager -up-> "1" ExerciseTrackerStorage
Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -right-|> ExerciseTrackerStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
-JsonAddressBookStorage .up.|> AddressBookStorage
-JsonAddressBookStorage ..> JsonSerializableAddressBook
-JsonSerializableAddressBook --> "*" JsonAdaptedPerson
-JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonExerciseTrackerStorage .up.|> ExerciseTrackerStorage
+JsonExerciseTrackerStorage ..> JsonSerializableExerciseTracker
+JsonSerializableExerciseTracker --> "*" JsonAdaptedExercise
+JsonAdaptedExercise --> "1" JsonAdaptedDate
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..d7f273d591b 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,10 +11,11 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
+Class ExerciseListPanel
+Class ExerciseCard
Class StatusBarFooter
Class CommandBox
+Class SavedExerciseListWindow
}
package Model <> {
@@ -31,27 +32,30 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
MainWindow *-down-> "1" CommandBox
+MainWindow *-down-> "1" SavedExerciseListWindow
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" ExerciseListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
-PersonListPanel -down-> "*" PersonCard
+ExerciseListPanel -down-> "*" ExerciseCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+ExerciseListPanel --|> UiPart
+ExerciseCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
+SavedExerciseListWindow --up|> UiPart
-PersonCard ..> Model
+
+ExerciseCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
+ExerciseListPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
index 96e30744d24..df9a43272cb 100644
--- a/docs/diagrams/UndoRedoState0.puml
+++ b/docs/diagrams/UndoRedoState0.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title Initial state
package States {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab2:ExerciseTracker__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 01fcb9b2b96..be16fefe60a 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "delete 5"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab2:ExerciseTracker__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index bccc230a5d1..c556df9c9f8 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "add n/David"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab2:ExerciseTracker__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
index ea29c9483e4..b5ad64b9e1e 100644
--- a/docs/diagrams/UndoRedoState3.puml
+++ b/docs/diagrams/UndoRedoState3.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "undo"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab2:ExerciseTracker__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
index 1b784cece80..f468a7ca528 100644
--- a/docs/diagrams/UndoRedoState4.puml
+++ b/docs/diagrams/UndoRedoState4.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "list"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab2:ExerciseTracker__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
index 88927be32bc..4f5be72f718 100644
--- a/docs/diagrams/UndoRedoState5.puml
+++ b/docs/diagrams/UndoRedoState5.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "clear"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab3:AddressBook__"
+ class State1 as "__ab0:ExerciseTracker__"
+ class State2 as "__ab1:ExerciseTracker__"
+ class State3 as "__ab3:ExerciseTracker__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
index 410aab4e412..e43dff2eb58 100644
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ b/docs/diagrams/UndoSequenceDiagram.puml
@@ -3,42 +3,42 @@
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ExerciseTrackerParser" as ExerciseTrackerParser LOGIC_COLOR
participant "u:UndoCommand" as UndoCommand LOGIC_COLOR
end box
box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
+participant ":VersionedExerciseTracker" as VersionedExerciseTracker MODEL_COLOR
end box
[-> LogicManager : execute(undo)
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand(undo)
-activate AddressBookParser
+LogicManager -> ExerciseTrackerParser : parseCommand(undo)
+activate ExerciseTrackerParser
create UndoCommand
-AddressBookParser -> UndoCommand
+ExerciseTrackerParser -> UndoCommand
activate UndoCommand
-UndoCommand --> AddressBookParser
+UndoCommand --> ExerciseTrackerParser
deactivate UndoCommand
-AddressBookParser --> LogicManager : u
-deactivate AddressBookParser
+ExerciseTrackerParser --> LogicManager : u
+deactivate ExerciseTrackerParser
LogicManager -> UndoCommand : execute()
activate UndoCommand
-UndoCommand -> Model : undoAddressBook()
+UndoCommand -> Model : undoExerciseTracker()
activate Model
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
+Model -> VersionedExerciseTracker : undo()
+activate VersionedExerciseTracker
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
+VersionedExerciseTracker -> VersionedExerciseTracker :resetData(ReadOnlyExerciseTracker)
+VersionedExerciseTracker --> Model :
+deactivate VersionedExerciseTracker
Model --> UndoCommand
deactivate Model
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..15af441b367 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -71,5 +71,5 @@ skinparam DefaultTextAlignment center
skinparam packageStyle Rectangle
hide footbox
-hide members
+hide empty members
hide circle
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml
index fdcbe1c0ccc..73e6480e8ba 100644
--- a/docs/diagrams/tracing/LogicSequenceDiagram.puml
+++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml
@@ -2,7 +2,7 @@
!include ../style.puml
Participant ":LogicManager" as logic LOGIC_COLOR
-Participant ":AddressBookParser" as abp LOGIC_COLOR
+Participant ":ExerciseTrackerParser" as abp LOGIC_COLOR
Participant ":EditCommandParser" as ecp LOGIC_COLOR
Participant "command:EditCommand" as ec LOGIC_COLOR
@@ -13,7 +13,7 @@ create ecp
abp -> ecp
abp -> ecp ++: parse(arguments)
create ec
-ecp -> ec ++: index, editPersonDescriptor
+ecp -> ec ++: index, editExerciseDescriptor
ec --> ecp --
ecp --> abp --: command
abp --> logic --: command
diff --git a/docs/images/24donovan24.png b/docs/images/24donovan24.png
new file mode 100644
index 00000000000..9ec4a3c6956
Binary files /dev/null and b/docs/images/24donovan24.png differ
diff --git a/docs/images/AddCommand.png b/docs/images/AddCommand.png
new file mode 100644
index 00000000000..43fd73bedac
Binary files /dev/null and b/docs/images/AddCommand.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..273a8097e29 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/DateClassDiagram.png b/docs/images/DateClassDiagram.png
new file mode 100644
index 00000000000..fb3d8027f0a
Binary files /dev/null and b/docs/images/DateClassDiagram.png differ
diff --git a/docs/images/DateRangeSequenceDiagram.png b/docs/images/DateRangeSequenceDiagram.png
new file mode 100644
index 00000000000..efd0eb9d3cd
Binary files /dev/null and b/docs/images/DateRangeSequenceDiagram.png differ
diff --git a/docs/images/DeleteCommand.png b/docs/images/DeleteCommand.png
new file mode 100644
index 00000000000..3b5d51cb7ee
Binary files /dev/null and b/docs/images/DeleteCommand.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index fa327b39618..f9e1e899890 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/FilterCommand.png b/docs/images/FilterCommand.png
new file mode 100644
index 00000000000..08ca51b7821
Binary files /dev/null and b/docs/images/FilterCommand.png differ
diff --git a/docs/images/GUIOrientation.png b/docs/images/GUIOrientation.png
new file mode 100644
index 00000000000..5ddebf0a977
Binary files /dev/null and b/docs/images/GUIOrientation.png differ
diff --git a/docs/images/GenerateCommandExample1.png b/docs/images/GenerateCommandExample1.png
new file mode 100644
index 00000000000..2d9efd96c3e
Binary files /dev/null and b/docs/images/GenerateCommandExample1.png differ
diff --git a/docs/images/GenerateCommandExample2.png b/docs/images/GenerateCommandExample2.png
new file mode 100644
index 00000000000..6d5d0c4c628
Binary files /dev/null and b/docs/images/GenerateCommandExample2.png differ
diff --git a/docs/images/GenerateCommandNameExample1.png b/docs/images/GenerateCommandNameExample1.png
new file mode 100644
index 00000000000..3dbf30fc7b1
Binary files /dev/null and b/docs/images/GenerateCommandNameExample1.png differ
diff --git a/docs/images/GenerateWorkoutSequenceDiagram.png b/docs/images/GenerateWorkoutSequenceDiagram.png
new file mode 100644
index 00000000000..4b1dceb066e
Binary files /dev/null and b/docs/images/GenerateWorkoutSequenceDiagram.png differ
diff --git a/docs/images/GeneratorCommandPattern.png b/docs/images/GeneratorCommandPattern.png
new file mode 100644
index 00000000000..909bec8ea74
Binary files /dev/null and b/docs/images/GeneratorCommandPattern.png differ
diff --git a/docs/images/GetSuggestionSequenceDiagram.png b/docs/images/GetSuggestionSequenceDiagram.png
new file mode 100644
index 00000000000..d61306dae68
Binary files /dev/null and b/docs/images/GetSuggestionSequenceDiagram.png differ
diff --git a/docs/images/GimLogo.png b/docs/images/GimLogo.png
new file mode 100644
index 00000000000..46a74c1f9df
Binary files /dev/null and b/docs/images/GimLogo.png differ
diff --git a/docs/images/HelpCommand.png b/docs/images/HelpCommand.png
new file mode 100644
index 00000000000..3a47ec97557
Binary files /dev/null and b/docs/images/HelpCommand.png differ
diff --git a/docs/images/ListAfterSortCommand.png b/docs/images/ListAfterSortCommand.png
new file mode 100644
index 00000000000..20942668485
Binary files /dev/null and b/docs/images/ListAfterSortCommand.png differ
diff --git a/docs/images/ListPersonalRecord.png b/docs/images/ListPersonalRecord.png
new file mode 100644
index 00000000000..7f33034e072
Binary files /dev/null and b/docs/images/ListPersonalRecord.png differ
diff --git a/docs/images/ListPersonalRecordSequenceDiagram.png b/docs/images/ListPersonalRecordSequenceDiagram.png
new file mode 100644
index 00000000000..0dd57d47ab9
Binary files /dev/null and b/docs/images/ListPersonalRecordSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index 9e9ba9f79e5..9c56788c1cc 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..d80e9e766dd 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/NotifyObservers.png b/docs/images/NotifyObservers.png
new file mode 100644
index 00000000000..6b585b6ea41
Binary files /dev/null and b/docs/images/NotifyObservers.png differ
diff --git a/docs/images/ObserverPattern.png b/docs/images/ObserverPattern.png
new file mode 100644
index 00000000000..1cd8eac69de
Binary files /dev/null and b/docs/images/ObserverPattern.png differ
diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png
index e7b4c8880cd..de096a28266 100644
Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ
diff --git a/docs/images/PrCommandExample1.png b/docs/images/PrCommandExample1.png
new file mode 100644
index 00000000000..f3d11620599
Binary files /dev/null and b/docs/images/PrCommandExample1.png differ
diff --git a/docs/images/PrCommandExample2.png b/docs/images/PrCommandExample2.png
new file mode 100644
index 00000000000..d8218ef6ae1
Binary files /dev/null and b/docs/images/PrCommandExample2.png differ
diff --git a/docs/images/RangeCommandOneSample.png b/docs/images/RangeCommandOneSample.png
new file mode 100644
index 00000000000..6b081711345
Binary files /dev/null and b/docs/images/RangeCommandOneSample.png differ
diff --git a/docs/images/RangeCommandTwoSample.png b/docs/images/RangeCommandTwoSample.png
new file mode 100644
index 00000000000..69a42e53f5a
Binary files /dev/null and b/docs/images/RangeCommandTwoSample.png differ
diff --git a/docs/images/RangeSequenceDiagram.png b/docs/images/RangeSequenceDiagram.png
new file mode 100644
index 00000000000..687779693c6
Binary files /dev/null and b/docs/images/RangeSequenceDiagram.png differ
diff --git a/docs/images/RecognisedExercisesOrientation.png b/docs/images/RecognisedExercisesOrientation.png
new file mode 100644
index 00000000000..29d91f8a607
Binary files /dev/null and b/docs/images/RecognisedExercisesOrientation.png differ
diff --git a/docs/images/SortSequenceDiagram.png b/docs/images/SortSequenceDiagram.png
new file mode 100644
index 00000000000..18bf6842cf7
Binary files /dev/null and b/docs/images/SortSequenceDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 2533a5c1af0..1d07a5492f9 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..8c9a15c7928 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 785e04dbab4..e8089f27af0 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/default_Ui.png b/docs/images/default_Ui.png
new file mode 100644
index 00000000000..5bd77847aa2
Binary files /dev/null and b/docs/images/default_Ui.png differ
diff --git a/docs/images/ee-suan.png b/docs/images/ee-suan.png
new file mode 100644
index 00000000000..e1f4f320e5e
Binary files /dev/null and b/docs/images/ee-suan.png differ
diff --git a/docs/images/ervink123.png b/docs/images/ervink123.png
new file mode 100644
index 00000000000..36f8dd04352
Binary files /dev/null and b/docs/images/ervink123.png differ
diff --git a/docs/images/kavantan.png b/docs/images/kavantan.png
new file mode 100644
index 00000000000..4fe8cb30126
Binary files /dev/null and b/docs/images/kavantan.png differ
diff --git a/docs/images/stevenlimhw.png b/docs/images/stevenlimhw.png
new file mode 100644
index 00000000000..c6bac8c88ac
Binary files /dev/null and b/docs/images/stevenlimhw.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..ad7b276ba28 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,17 @@
---
layout: page
-title: AddressBook Level-3
+title: Gim
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2223S1-CS2103T-T15-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T15-4/tp/actions)
+[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T15-4/tp/branch/master/graph/badge.svg?token=3QLCJGGTH7)](https://codecov.io/gh/AY2223S1-CS2103T-T15-4/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). Gim builds on the commands of Vim so if you can type fast and are an avid Vim user, Gim can optimize your exercise routines to a much greater capacity than traditional GUI apps.
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+* If you are interested in using Gim, head over to the [_Getting Started_ section of the **User Guide**](UserGuide.html#3-getting-started).
+* If you are interested about developing Gim, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/24donovan24.md b/docs/team/24donovan24.md
new file mode 100644
index 00000000000..f93d5f75d80
--- /dev/null
+++ b/docs/team/24donovan24.md
@@ -0,0 +1,50 @@
+---
+layout: page
+title: Donovan Lee's Project Portfolio Page
+---
+
+### Project: Gim
+
+Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Gim allows you to keep track your exercises and Personal Records in an efficient way.
+
+Example usages:
+* as a tracking tool to keep track of completed exercises
+* as a tracking tool to keep track of your personal records
+* as a workout generator to generate exercises based on your personal records
+
+Given below are my contributions to the project.
+
+* **New Feature**: Sort command ([PR #100](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/100))
+ * What it does: Allows users to sort any displayed list of exercises by date of completion.
+ * Justification: This feature is essential as sorting the exercises by date of completion allows users to better track their workout progression.
+ * Highlights:
+ * Tracing of the code base was necessary to ensure that the sort command is executed on the displayed list of exercises without altering the default ordering of exercises stored in the system.
+ * A defensive copy of the default ordering of exercises based off entry date has to be maintained to allow users to toggle between a sorted ordering and the default ordering.
+ * Utilise Open-Closed Principle to accommodate other sorting orders should sorting criteria change in the future.
+
+* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=24donovan24&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Enhancements to existing features**:
+ * Updated the GUI colour scheme ([PR #111](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/111))
+ * Added a welcome message when the application is launched ([PR #113](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/113))
+ * Added Sets as a field of an Exercise ([PR #53](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/53), [PR #62](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/62), [PR #72](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/72))
+ * Added unit tests for Sets ([PR #62](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/62))
+
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for `:sort` command ([PR #125](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/125))
+ * Added documentation for `:filter` command with some edits to existing documentation as per discussed by team ([PR #133](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/133))
+ * FAQ ([PR #133](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/133))
+
+ * Developer Guide:
+ * Added implementation details for `:sort` command ([PR #101](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/101))
+ * Added user stories, use cases and instructions for manual testing as per discussed by team ([PR #211](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/211))
+
+* **Contribution to team-based tasks**:
+ * Reviewed Team Members' PRs.
+ * Participated and contributed to weekly team meetings.
+
+* **Tools**:
+ * PlantUML: Creating UML diagrams.
diff --git a/docs/team/ee-suan.md b/docs/team/ee-suan.md
new file mode 100644
index 00000000000..4d2f768e232
--- /dev/null
+++ b/docs/team/ee-suan.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: Ee Suan's Project Portfolio Page
+---
+
+### Project: Gim
+
+* Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Gim allows you to keep track your exercises and Personal Records in a efficient way.
+ Example usages:
+ * as a tracking tool to keep track of completed exercises
+ * as a tracking tool to keep track of your personal records
+ * as a workout generator to generate exercises based on your personal records
+
+Given below are my contributions to the project.
+
+* **New Feature**: Generate command ([PR #118](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/118))
+ * What it does: Generates workout suggestions for users based on their personal records, based on difficulty level specified.
+ * Justification: This feature makes use of the information provided by users to generate a personalised workout session. It makes sensible use of users' personal records.
+ * Highlights:
+ * Applied Command pattern in designing how the suggested workout is generated.
+ * Supports both indexes and exercise names as input.
+ * Added tests and increased project coverage by +5.73% ([PR #227](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/227))
+
+* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=ee-suan&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+
+* **Enhancements to existing features**:
+ * Added Weight as field for Exercise ([PR #51](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/51))
+ * Added unit tests for Weight ([PR #63](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/63))
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for `:gen` command ([PR #129](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/129))
+ * Updated UML diagrams to reflect current implementation ([PR #213](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/213))
+
+ * Developer Guide:
+ * Added implementation and design details to support `:gen` command ([PR #104](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/104))
+
+* **Contribution to team-based tasks**:
+ * Reviewed Team Member's PRs
+ * Suggested alternative implementation to abide by Open-Closed Principle ([PR #100](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/100))
+ * Provided ideas and contributed in discussions during weekly team meeting.
+ * Suggested alternative phrasing for clarification in UG ([Issue #150](https://github.com/AY2223S1-CS2103T-T15-4/tp/issues/150))
+
+* **Tools**:
+ * PlantUML: Creating UML diagrams.
diff --git a/docs/team/ervink123.md b/docs/team/ervink123.md
new file mode 100644
index 00000000000..76b4bd87f8e
--- /dev/null
+++ b/docs/team/ervink123.md
@@ -0,0 +1,61 @@
+---
+layout: page
+title: Ervin Kin's Project Portfolio Page
+---
+
+### Project: Gim
+
+* Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Gim allows you to keep track your exercises and Personal Records in an efficient way.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Recognised Exercises list in bottom right of the GUI ([PR #107](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/107))
+ * What it does: Provides User with an idea of what unique exercises they have inputted in the system, updated in real time.
+ * Justification: This is especially important when the exercise list is populated/ the user has been using the app for a long time. He may accidentally misspell an exercise name when keying it in, causing the system to miss out on a potential personal record/entry.
+ * Highlights:
+ * Added Exercise Keys Class which handles logic behind ExerciseHashMap keys.
+ * Used Observer Pattern design in facilitating real time interaction between ExerciseHashMap and UI.
+ * Update ExerciseHashMap with relevant methods and fields to support Observer pattern.
+ * Conducted testing (specifically for ExerciseKeys class and added onto ExerciseHashMap Tests) while designing the feature such that code coverage remained consistent (no change in coverage after push).
+* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=ervink123&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Managed issue tracker for v1.3 and v1.4
+
+* **Enhancements to existing features**:
+ * Redesigned Help Window ([PR #115](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/115), [PR #141](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/141), [PR #142](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/142))
+ * What I did:
+ * Help window now provides a brief summary of available commands on top of a link to the user guide
+ * Justification:
+ * Provides convenience as users can now get some basic help in the help window instead of being redirected to the user guide. This enhancement saves clicks and time in the long run.
+ * Redesigned User Interface ([PR #84](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/84), [PR #94](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/94), [PR #116](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/116))
+ * What I did:
+ * Moved Result Display window to the right and added Saved Exercises List window.
+ * Justification:
+ * For moving the Result Display window and adding the Saved Exercises List window, the previous UI had a lot of wasted real estate as the cards were taking up a lot of space. Furthermore, the result display window was too small and needed to scroll, especially when a long error message came up, which was inconvenient.
+ * What I did:
+ * Separated Date as a standalone label in the Exercise Card UI.
+ * Justification:
+ * The date of an exercise is especially important when keeping track of progress, hence making sure it is easily visible and distinct from the rest of the fields is important.
+ * Added Reps as a field to the creation of an Exercise. ([PR #67](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/67))
+ * In charge of REGEX and operations relating to reps
+ * Removing edit feature and tests relating to it ([PR #127](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/127))
+
+* **Documentation**:
+ * User Guide:
+ * Standardised pictures in User guide, included callouts for important information in each picture. ([PR #202](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/202)).
+ * Wrote entire section for GUI orientation.
+ * Helped with formatting of commands (placement of tips, phrasing of parameter constraints).
+
+ * Developer Guide:
+ * Wrote section on listing of unique stored Exercises in the graphical UI ([PR #216](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/216), [PR #102](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/102))
+ * Added UML diagrams (class and sequence diagrams) for recognised exercise list implementation.
+ * Added User stories as discussed.
+
+* **Contribution to team-based tasks**:
+ * Reviewed Team Member's PRs and provided feedback where appropriate.
+ * PED bugs triaging.
+
+* **Tools**:
+ * PlantUML: Creating UML diagrams.
+ * SceneBuilder: Edit FXML files which Gim UI components use.
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
index 773a07794e2..b65036e4c90 100644
--- a/docs/team/johndoe.md
+++ b/docs/team/johndoe.md
@@ -3,9 +3,9 @@ layout: page
title: John Doe's Project Portfolio Page
---
-### Project: AddressBook Level 3
+### Project: ExerciseTracker Level 3
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
+ExerciseTracker - Level 3 is a desktop exercise tracker application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
Given below are my contributions to the project.
diff --git a/docs/team/kavantan.md b/docs/team/kavantan.md
new file mode 100644
index 00000000000..ce1651dd270
--- /dev/null
+++ b/docs/team/kavantan.md
@@ -0,0 +1,49 @@
+---
+layout: page
+title: Kavan Tan's Project Portfolio Page
+---
+
+### Project: Gim
+
+Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Gim allows you to keep track your exercises and Personal Records in an efficient way.
+
+Given below are my contributions to the project.
+
+* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=kavantan&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **New Addition**: Exercise Hashmap ([PR #85](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/85))
+ * What it does: Stores exercise data in the form of a hashmap, where the key is the exercise name and the value is a list of exercises with that exercise name.
+ * Justification: This hashmap is crucial because it creates precedence for the implementation for many of our other features. For instance, we can now easily find all the exercises (for a certain exercise name) and find the exercise instance with the highest weight amongst them.
+ * Highlights: The exercise hashmap was very challenging to implement, because a strong understanding of how data is loaded/saved was necessary. Furthermore, upon tracing the code to learn of this system, it took meticulous effort to ensure that the existing commands/features work in conjunction with the exercise hashmap as well (e.g. add, delete, clear etc.).
+
+* **New Feature**: Personal Record Listing ([PR #114](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/114) and [PR #117](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/117))
+ * What it does: Finds the personal record of certain exercises
+ * Justification: With this command, users will be able to keep track of their progression of certain exercises in the gym.
+ * Highlights: This command was tricky to implement because of two reasons. Firstly, this command has to 'perform calculations' based on specific exercises from the exercise tracker, hence it was difficult tricky to understand and implement the retrieval of these specific exercises. Secondly, because of the nature of how the command is parsed, there were many considerations to be made. E.g. If the name input was not in the exercise tracker, if there were multiple similar entries etc.
+
+
+* **Enhancements to existing features**:
+ * Refactor AB3-specific references in original AB3 to relatable references for Gim ([PR #49](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/49) and [PR #65](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/65))
+ * What I did: Refactored 'seedu.address' package to 'gim', instances of 'person' to 'exercise' and 'address book' references to 'exercise tracker'
+ * Justification: In Gim, we are storing exercises instead of persons. Hence, to eliminate future confusion using the OOP paradigm, it was best that we refactored the no-longer-relevant references from the original AB3 to more relevant references that we would be use in Gim.
+ * Allow addition of duplicate exercises ([PR #69](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/69))
+ * What I did: Allow users to add 'duplicate' exercises into the system.
+ * Justification: Unlike in the original address book (where users cannot add duplicate names), users should be able to add the same exercise into the system to signify that they performed that exercise on multiple occasions.
+ * Implement mandatory use of confirm flag for 'clear' function ([PR #138](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/138))
+ * What I did: Mandate that a user must type in 'confirm' before they can successfully clear their exercise tracker.
+ * Justification: For new users that may be unfamiliar with the 'clear' command, they might not understand what it does and may accidentally clear their exercise tracker unknowingly. The mandatory 'confirm' serves as a safety warning, aiming to prevent such accidental deletions of the exercise tracker.
+
+* **Documentation**:
+ * User Guide:
+ * Notes about command format ([PR #124](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/124))
+ * Personal Record ([PR #124](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/124))
+ * Useful notations ([PR #195](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/195))
+
+ * Developer Guide:
+ * Exercise Model ([PR #98](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/98))
+ * Exercise Hashmap ([PR #214](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/214))
+ * Listing of Personal Records ([PR #120](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/120))
+
+* **Contribution to team-based tasks**
+ * Reviewed Team Members' PRs.
+ * Provided ideas and contributed in discussions during weekly team meeting.
diff --git a/docs/team/stevenlimhw.md b/docs/team/stevenlimhw.md
new file mode 100644
index 00000000000..698eb9ba31b
--- /dev/null
+++ b/docs/team/stevenlimhw.md
@@ -0,0 +1,66 @@
+---
+layout: page
+title: Steven's Project Portfolio Page
+---
+
+### Project: Gim
+
+Gim is a **desktop app for managing gym exercises, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). Gim builds on the commands of Vim so if you can type fast and are an avid Vim user, Gim can optimize your exercise routines to a much greater capacity than traditional GUI apps.
+
+Given below are my contributions to the project.
+
+* **New Feature**: `:range start/START_DATE end/END_DATE` command (PR [#92](https://github.com/AY2223S1-CS2103T-T15-4/tp/issues/92), [#119](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/119))
+ * What it does: Displays a list of exercises within the time period defined by the start date and end date.
+ * Justification: This allows users to easily view exercise within a time period, which is a common operation.
+ * Highlights: Tracing of the original AB3 code base is necessary to understand how a new command can be added. The integration
+ of Java's LocalDate APIs is crucial to this command as well.
+
+
+* **New Feature**: `:range last/NUMBER_OF_DAYS` (PR [#92](https://github.com/AY2223S1-CS2103T-T15-4/tp/issues/92), [#119](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/119))
+ * What it does: Displays a list of exercises the last N days, where N is the number of days the user inputs.
+ * Justification: This allows users to easily view exercise within a time period, without inputting the specific dates.
+ * Highlights: Tracing of the original AB3 code base is necessary to understand how a new command can be added. The integration
+ of Java's LocalDate APIs is crucial to this command as well.
+
+* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=stevenlimhw&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Enhancements to existing features**:
+ * Allow date to be optional when adding an exercise using `:add` (PR [#86](https://github.com/AY2223S1-CS2103T-T15-4/tp/issues/86), [#108](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/108))
+ * What I did:
+ * When the date field for `:add` is empty, the date will be set as today's date by default.
+ * Justification:
+ * This is to make the `:add` command shorter and increases speed of adding an exercise
+ when one wants to add exercises for today.
+ * Allow a variety of common date formats (PR [#108](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/108), [#119](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/119))
+ * What I did:
+ * Allow different separators, such as `15/05/2002`, `15 05 2002`,
+ `15-05-2022`.
+ * Allow Day-Month-Year and Year-Month-Day formats, such as
+ `15/05/2022` or `2022/05/15`.
+ * Justification:
+ * Improve user experience by allowing more input variations.
+
+* **Testing**:
+ * Add unit tests for `Date`, `FormatterList`, `RegexList`, `RangeCommand`, `RangeCommandParser`,
+ `AddCommandParser`, `DateWithinRangePredicate` to improve Codecov code coverage. (PR [#217](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/217) etc.)
+
+* **Documentation**:
+ * User Guide:
+ * Common date formats accepted for command inputs. (PR [#130](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/130))
+ * Explanation on `:range start/START_DATE end/END_DATE` command. (PR [#105](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/105), [#128](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/128))
+ * Explanation on `:range last/NUMBER_OF_DAYS` command. (PR [#128](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/128))
+
+ * Developer Guide:
+ * Class diagram showing the relationship between `Date`, `RegexList` and `FormatterList`.
+ * Sequence diagram for `:range` command.
+ * Explanation on the implementation of `:range start/START_DATE end/END_DATE` command.
+ * Explanation on the implementation of `:range last/NUMBER_OF_DAYS` command.
+ * Explanation on the implementation of the `Date` class.
+
+* **Community**:
+ * Add a guide for the team to refer to on how to use Gradle to run tests and checkstyle for
+ CI checks locally before making a pull request. (PR [#29](https://github.com/AY2223S1-CS2103T-T15-4/tp/pull/29))
+ * Add a guide for using Codecov to see which parts of the code base can be covered better. (Issue [#224](https://github.com/AY2223S1-CS2103T-T15-4/tp/issues/224))
+ * Discussed design choices with their benefits and trade-offs and implementation options with the team.
+ * Analyzed team member's Pull Requests (PRs) and provided feedback.
+ * Brainstormed on the potential bugs in the app and ways to rigorously test the features.
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
index 880c701042f..ccbc9ec6a5a 100644
--- a/docs/tutorials/AddRemark.md
+++ b/docs/tutorials/AddRemark.md
@@ -5,7 +5,7 @@ title: "Tutorial: Adding a command"
Let's walk you through the implementation of a new command — `remark`.
-This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format:
+This command allows users of the ExerciseTracker application to add optional remarks to people in their exercise tracker and edit it if required. The command should have the following format:
`remark INDEX r/REMARK` (e.g., `remark 2 r/Likes baseball`)
@@ -23,12 +23,12 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu
**`RemarkCommand.java`:**
``` java
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import seedu.address.model.Model;
+import gim.model.Model;
/**
- * Changes the remark of an existing person in the address book.
+ * Changes the remark of an existing exercise in the exercise tracker.
*/
public class RemarkCommand extends Command {
@@ -43,9 +43,9 @@ public class RemarkCommand extends Command {
### Hook `RemarkCommand` into the application
-Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`.
+Now that we have our `RemarkCommand` ready to be executed, we need to update `ExerciseTrackerParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`.
-You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee).
+You can refer to the changes in this [diff](https://github.com/se-edu/exercisetracker-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-399c284cb892c20b7c04a69116fcff6ccc0666c5230a1db8e4a9145def8fa4ee).
### Run the application
@@ -65,8 +65,8 @@ Following the convention in other commands, we add relevant messages as constant
``` java
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Edits the remark of the person identified "
- + "by the index number used in the last person listing. "
+ + ": Edits the remark of the exercise identified "
+ + "by the index number used in the last exercise listing. "
+ "Existing remark will be overwritten by the input.\n"
+ "Parameters: INDEX (must be a positive integer) "
+ "r/ [REMARK]\n"
@@ -91,7 +91,7 @@ Let’s change `RemarkCommand` to parse input from the user.
We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
//...
public class RemarkCommand extends Command {
//...
@@ -101,8 +101,8 @@ public class RemarkCommand extends Command {
private final String remark;
/**
- * @param index of the person in the filtered person list to edit the remark
- * @param remark of the person to be updated to
+ * @param index of the exercise in the filtered exercise list to edit the remark
+ * @param remark of the exercise to be updated to
*/
public RemarkCommand(Index index, String remark) {
requireAllNonNull(index, remark);
@@ -136,13 +136,13 @@ public class RemarkCommand extends Command {
}
```
-Your code should look something like [this](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-a8e35af8f9c251525063fae36c9852922a7e7195763018eacec60f3a4d87c594) after you are done.
+Your code should look something like [this](https://github.com/se-edu/exercisetracker-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-a8e35af8f9c251525063fae36c9852922a7e7195763018eacec60f3a4d87c594) after you are done.
### Parse user input
Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user.
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface.
+Create a `RemarkCommandParser` class in the `gim.logic.parser` package. The class must extend the `Parser` interface.
![The relationship between Parser and RemarkCommandParser](../images/add-remark/ParserInterface.png)
@@ -216,22 +216,22 @@ public RemarkCommand parse(String args) throws ParseException {
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
+:information_source: Don’t forget to update `ExerciseTrackerParser` to use our new `RemarkCommandParser`!
If you are stuck, check out the sample
-[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a).
+[here](https://github.com/se-edu/exercisetracker-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a).
## Add `Remark` to the model
-Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person.
+Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of exercise data. We achieve that by working with the `Exercise` model. Each field in an Exercise is implemented as a separate class (e.g. a `Name` object represents the exercise’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to an exercise.
### Add a new `Remark` class
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
+Create a new `Remark` in `gim.model.exercise`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
-A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input
+A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/exercisetracker-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input
validation.
### Make use of `Remark`
@@ -240,11 +240,11 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark`
## Add a placeholder element for remark to the UI
-Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
+Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each exercise.
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
+Simply add the following to [`gim.ui.ExerciseCard`](https://github.com/se-edu/exercisetracker-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688).
-**`PersonCard.java`:**
+**`ExerciseCard.java`:**
``` java
@FXML
@@ -254,9 +254,9 @@ private Label remark;
`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don’t worry — we will get back to it later.
-Then insert the following into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-d44c4f51c24f6253c277a2bb9bc440b8064d9c15ad7cb7ceda280bca032efce9).
+Then insert the following into [`main/resources/view/ExerciseListCard.fxml`](https://github.com/se-edu/exercisetracker-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-d44c4f51c24f6253c277a2bb9bc440b8064d9c15ad7cb7ceda280bca032efce9).
-**`PersonListCard.fxml`:**
+**`ExerciseListCard.fxml`:**
``` xml
@@ -266,54 +266,54 @@ That’s it! Fire up the application again and you should see something like thi
![$remark shows up in each entry](../images/add-remark/$Remark.png)
-## Modify `Person` to support a `Remark` field
+## Modify `Exercise` to support a `Remark` field
-Since `PersonCard` displays data from a `Person`, we need to update `Person` to get our `Remark` displayed!
+Since `ExerciseCard` displays data from a `Exercise`, we need to update `Exercise` to get our `Remark` displayed!
-### Modify `Person`
+### Modify `Exercise`
-We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition.
+We change the constructor of `Exercise` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition.
-### Update other usages of `Person`
+### Update other usages of `Exercise`
-Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
+Unfortunately, a change to `Exercise` will cause other commands to break, you will have to modify these commands to use the updated `Exercise`!
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
+:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Exercise` class to find these commands.
-Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
+Refer to [this commit](https://github.com/se-edu/exercisetracker-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
## Updating Storage
-AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`!
+ExerciseTracker stores data by serializing `JsonAdaptedExercise` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedExercise` to work with our new `Exercise`!
While the changes to code may be minimal, the test data will have to be updated as well.
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
+:exclamation: You must delete ExerciseTracker’s storage file located at `/data/exercisetracker.json` before running it! Not doing so will cause ExerciseTracker to default to an empty exercise tracker!
-Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
+Check out [this commit](https://github.com/se-edu/exercisetracker-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
to see what the changes entail.
## Finalizing the UI
-Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI.
+Now that we have finalized the `Exercise` class and its dependencies, we can now bind the `Remark` field to the UI.
-Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692)
+Just add [this one line of code!](https://github.com/se-edu/exercisetracker-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692)
-**`PersonCard.java`:**
+**`ExerciseCard.java`:**
``` java
-public PersonCard(Person person, int displayedIndex) {
+public ExerciseCard(Exercise exercise, int displayedIndex) {
//...
- remark.setText(person.getRemark().value);
+ remark.setText(exercise.getRemark().value);
}
```
@@ -325,43 +325,43 @@ After the previous step, we notice a peculiar regression — we went from di
### Update `RemarkCommand` and `RemarkCommandParser`
-In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in a `Person` are immutable, we create a new instance of a `Person` with the values that we want and
-save it with `Model#setPerson()`.
+In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Exercise`. Since all fields in a `Exercise` are immutable, we create a new instance of a `Exercise` with the values that we want and
+save it with `Model#setExercise()`.
**`RemarkCommand.java`:**
``` java
//...
- public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s";
- public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s";
+ public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Exercise: %1$s";
+ public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Exercise: %1$s";
//...
@Override
public CommandResult execute(Model model) throws CommandException {
- List lastShownList = model.getFilteredPersonList();
+ List lastShownList = model.getFilteredExerciseList();
if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
}
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = new Person(
- personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
- personToEdit.getAddress(), remark, personToEdit.getTags());
+ Exercise exerciseToEdit = lastShownList.get(index.getZeroBased());
+ Exercise editedExercise = new Exercise(
+ exerciseToEdit.getName(), exerciseToEdit.getWeight(), exerciseToEdit.getSets(),
+ exerciseToEdit.getAddress(), remark, exerciseToEdit.getTags());
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.setExercise(exerciseToEdit, editedExercise);
+ model.updateFilteredExerciseList(PREDICATE_SHOW_ALL_EXERCISES);
- return new CommandResult(generateSuccessMessage(editedPerson));
+ return new CommandResult(generateSuccessMessage(editedExercise));
}
/**
* Generates a command execution success message based on whether
* the remark is added to or removed from
- * {@code personToEdit}.
+ * {@code exerciseToEdit}.
*/
- private String generateSuccessMessage(Person personToEdit) {
+ private String generateSuccessMessage(Exercise exerciseToEdit) {
String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS;
- return String.format(message, personToEdit);
+ return String.format(message, exerciseToEdit);
}
```
@@ -373,7 +373,7 @@ Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed
Let’s verify the correctness of our code by writing some tests!
-Of course you can simply add the test cases manually, like you've been doing all along this tutorial. The result would be like the test cases in [here](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01). Alternatively, you can get the help of IntelliJ to generate the skeletons of the test cases, as explained in the next section.
+Of course you can simply add the test cases manually, like you've been doing all along this tutorial. The result would be like the test cases in [here](https://github.com/se-edu/exercisetracker-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01). Alternatively, you can get the help of IntelliJ to generate the skeletons of the test cases, as explained in the next section.
### Automatically generating tests
@@ -394,8 +394,8 @@ Following convention, let’s change the name of the generated method to `execut
Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`.
-You should end up with a test that looks something like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01R36-R49).
+You should end up with a test that looks something like [this](https://github.com/se-edu/exercisetracker-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-ff58f7c10338b34f76645df49b71ecb2bafaf7611b20e7ff59ebc98475538a01R36-R49).
## Conclusion
-This concludes the tutorial for adding a new `Command` to AddressBook.
+This concludes the tutorial for adding a new `Command` to ExerciseTracker.
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..7076f32555f 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -8,7 +8,7 @@ title: "Tutorial: Removing Fields"
> — Antoine de Saint-Exupery
When working on an existing code base, you will most likely find that some features that are no longer necessary.
-This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class.
+This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Exercise` class.
@@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+The `address` field in `Exercise` is actually an instance of the `gim.model.exercise.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
@@ -37,11 +37,11 @@ Choose to `View Usages` and you should be presented with a list of `Safe Delete
![List of conflicts](../images/remove/SafeDeleteConflicts.png)
-Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., double-click on the entry (which takes you to the code in concern, right-click on that entity, and choose `Refactor` -> `Safe delete` as before). You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection.
+Remove usages of `Address` by performing `Safe Delete`s on each entry i.e., double-click on the entry (which takes you to the code in concern, right-click on that entity, and choose `Refactor` -> `Safe delete` as before). You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditExerciseDescriptor` may require more careful inspection.
-Let’s try removing references to `Address` in `EditPersonDescriptor`.
+Let’s try removing references to `Address` in `EditExerciseDescriptor`.
-1. Safe delete the field `address` in `EditPersonDescriptor`.
+1. Safe delete the field `address` in `EditExerciseDescriptor`.
1. Select `Yes` when prompted to remove getters and setters.
@@ -52,7 +52,7 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`.
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
+ :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Exercise` class will require you to modify its constructor.
1. Repeat the steps for the remaining usages of `Address`
@@ -63,13 +63,13 @@ After you are done, verify that the application still works by compiling and run
Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`).
-Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
+Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `ExerciseCard` that has not been removed nor identified.
![$address](../images/remove/$address.png)
-A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
+A quick look at the `ExerciseCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
-**`PersonCard.java`**
+**`ExerciseCard.java`**
``` java
...
@@ -78,13 +78,13 @@ private Label address;
...
```
-**`PersonCard.fxml`**
+**`ExerciseCard.fxml`**
``` xml
...
-
+
-
+
...
```
@@ -96,14 +96,14 @@ At this point, your application is working as intended and all your tests are pa
In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
-**`invalidPersonAddressBook.json`:**
+**`invalidExerciseExerciseTracker.json`:**
```json
{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
+ "exercises": [ {
+ "name": "Exercise with invalid name field: Ha!ns Mu@ster",
+ "weight": "9482424",
+ "sets": "hans@example.com",
"address": "4th street"
} ]
}
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..16262313483 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -39,7 +39,7 @@ In our case, we would want to begin the tracing at the very point where the App
-According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
+According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `gim.logic.Logic`.
@@ -48,7 +48,7 @@ According to the sequence diagram you saw earlier (and repeated above for refere
:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
+A quick look at the `gim.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
```java
public interface Logic {
@@ -85,7 +85,7 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres
## Tracing the execution path
-Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
+Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/WEIGHT] [e/SETS] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
@@ -120,14 +120,14 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
CommandResult commandResult;
//Parse user input from String to a Command
- Command command = addressBookParser.parseCommand(commandText);
+ Command command = exerciseTrackerParser.parseCommand(commandText);
//Executes the Command and stores the result
commandResult = command.execute(model);
try {
//We can deduce that the previous line of code modifies model in some way
// since it's being stored here.
- storage.saveAddressBook(model.getAddressBook());
+ storage.saveExerciseTracker(model.getExerciseTracker());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -141,7 +141,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. _Step over_ the logging code since it is of no interest to us now.
![StepOver](../images/tracing/StepOver.png)
-1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below):
+1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `ExerciseTrackerParser#parseCommand()` method (partial code given below):
``` java
public Command parseCommand(String userInput) throws ParseException {
...
@@ -171,7 +171,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
-1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the 'Variables' window.
+1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditExerciseDescriptor`. Recall that we can verify the contents of `editExerciseDesciptor` through the 'Variables' window.
![EditCommand](../images/tracing/EditCommand.png)
1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component.
@@ -189,22 +189,22 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
@Override
public CommandResult execute(Model model) throws CommandException {
...
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ Exercise exerciseToEdit = lastShownList.get(index.getZeroBased());
+ Exercise editedExercise = createEditedExercise(exerciseToEdit, editExerciseDescriptor);
+ if (!exerciseToEdit.isSameExercise(editedExercise) && model.hasExercise(editedExercise)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EXERCISE);
}
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
+ model.setExercise(exerciseToEdit, editedExercise);
+ model.updateFilteredExerciseList(PREDICATE_SHOW_ALL_EXERCISES);
+ return new CommandResult(String.format(MESSAGE_EDIT_EXERCISE_SUCCESS, editedExercise));
}
```
1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically,
- * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data.
- * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked.
+ * it uses the `setExercise()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the exercise data.
+ * it uses the `updateFilteredExerciseList` method to ask the `Model` to populate the 'filtered list' with _all_ exercises.
+ FYI, The 'filtered list' is the list of exercises resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the exercises so that the user can see the edited exercise along with all other exercises. If this was a `find` command, we would be setting that list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of exercises is being tracked.
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component)
@@ -217,29 +217,29 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [
1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component.
-
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
+
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveExerciseTracker(model.getExerciseTracker())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into.
-1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
+1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonExerciseTracker#saveExerciseTracker()` method which calls the `JsonSerializableExerciseTracker` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability):
- **`JsonSerializableAddressBook` constructor:**
+ **`JsonSerializableExerciseTracker` constructor:**
``` java
/**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
+ * Converts a given {@code ReadOnlyExerciseTracker} into this class for Jackson use.
*
* @param source future changes to this will not affect the created
- * {@code JsonSerializableAddressBook}.
+ * {@code JsonSerializableExerciseTracker}.
*/
- public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
- persons.addAll(
- source.getPersonList()
+ public JsonSerializableExerciseTracker(ReadOnlyExerciseTracker source) {
+ exercises.addAll(
+ source.getExerciseList()
.stream()
- .map(JsonAdaptedPerson::new)
+ .map(JsonAdaptedExercise::new)
.collect(Collectors.toList()));
}
```
-1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`.
+1. It appears that a `JsonAdaptedExercise` is created for each `Exercise` and then added to the `JsonSerializableExerciseTracker`.
This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format.
1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
@@ -292,10 +292,10 @@ Here are some quick questions you can try to answer based on your execution path
2. Allow `delete` to remove more than one index at a time
- 3. Save the address book in the CSV format instead
+ 3. Save the exercise tracker in the CSV format instead
4. Add a new command
- 5. Add a new field to `Person`
+ 5. Add a new field to `Exercise`
- 6. Add a new entity to the address book
+ 6. Add a new entity to the exercise tracker
diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/gim/AppParameters.java
similarity index 93%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/gim/AppParameters.java
index ab552c398f3..12f0c56f2ab 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/gim/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package gim;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -6,9 +6,9 @@
import java.util.Objects;
import java.util.logging.Logger;
+import gim.commons.core.LogsCenter;
+import gim.commons.util.FileUtil;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
/**
* Represents the parsed command-line parameters given to the application.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/gim/Main.java
similarity index 97%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/gim/Main.java
index 052a5068631..84c9c153505 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/gim/Main.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package gim;
import javafx.application.Application;
@@ -23,3 +23,4 @@ public static void main(String[] args) {
Application.launch(MainApp.class, args);
}
}
+
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/gim/MainApp.java
similarity index 67%
rename from src/main/java/seedu/address/MainApp.java
rename to src/main/java/gim/MainApp.java
index 4133aaa0151..4119c32bd54 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/gim/MainApp.java
@@ -1,42 +1,42 @@
-package seedu.address;
+package gim;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.logging.Logger;
+import gim.commons.core.Config;
+import gim.commons.core.LogsCenter;
+import gim.commons.core.Version;
+import gim.commons.exceptions.DataConversionException;
+import gim.commons.util.ConfigUtil;
+import gim.commons.util.StringUtil;
+import gim.logic.Logic;
+import gim.logic.LogicManager;
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.UserPrefs;
+import gim.model.util.SampleDataUtil;
+import gim.storage.ExerciseTrackerStorage;
+import gim.storage.JsonExerciseTrackerStorage;
+import gim.storage.JsonUserPrefsStorage;
+import gim.storage.Storage;
+import gim.storage.StorageManager;
+import gim.storage.UserPrefsStorage;
+import gim.ui.Ui;
+import gim.ui.UiManager;
import javafx.application.Application;
import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.util.ConfigUtil;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
/**
* Runs the application.
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 0, true);
+ public static final Version VERSION = new Version(1, 2, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -48,7 +48,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing ExerciseTracker ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -56,8 +56,9 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ ExerciseTrackerStorage exerciseTrackerStorage = new JsonExerciseTrackerStorage(
+ userPrefs.getExerciseTrackerFilePath());
+ storage = new StorageManager(exerciseTrackerStorage, userPrefsStorage);
initLogging(config);
@@ -69,25 +70,26 @@ public void init() throws Exception {
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelManager} with the data from {@code storage}'s exercise tracker and {@code userPrefs}.
+ * The data from the sample exercise tracker will be used instead if {@code storage}'s exercise tracker is not
+ * found, or an empty exercise tracker will be used instead if errors occur when reading {@code storage}'s
+ * exercise tracker.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional exerciseTrackerOptional;
+ ReadOnlyExerciseTracker initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ exerciseTrackerOptional = storage.readExerciseTracker();
+ if (!exerciseTrackerOptional.isPresent()) {
+ logger.info("Data file not found. Will be starting with a sample ExerciseTracker");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = exerciseTrackerOptional.orElseGet(SampleDataUtil::getSampleExerciseTracker);
} catch (DataConversionException e) {
- logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Data file not in the correct format. Will be starting with an empty ExerciseTracker");
+ initialData = new ExerciseTracker();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Problem while reading from the file. Will be starting with an empty ExerciseTracker");
+ initialData = new ExerciseTracker();
}
return new ModelManager(initialData, userPrefs);
@@ -151,7 +153,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
+ "Using default user prefs");
initializedPrefs = new UserPrefs();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
+ logger.warning("Problem while reading from the file. Will be starting with an empty ExerciseTracker");
initializedPrefs = new UserPrefs();
}
@@ -167,14 +169,16 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting ExerciseTracker " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping Exercise Tracker ] =============================");
try {
+ model.resetDisplayedList();
+ storage.saveExerciseTracker(model.getExerciseTracker());
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/gim/commons/core/Config.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/Config.java
rename to src/main/java/gim/commons/core/Config.java
index 91145745521..685877c444b 100644
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ b/src/main/java/gim/commons/core/Config.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package gim.commons.core;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/gim/commons/core/GuiSettings.java
similarity index 98%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/gim/commons/core/GuiSettings.java
index ba33653be67..50a95e838b0 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/gim/commons/core/GuiSettings.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package gim.commons.core;
import java.awt.Point;
import java.io.Serializable;
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/gim/commons/core/LogsCenter.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/gim/commons/core/LogsCenter.java
index 431e7185e76..f16b34325a0 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/gim/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package gim.commons.core;
import java.io.IOException;
import java.util.Arrays;
@@ -18,7 +18,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "exercisetracker.log";
private static Level currentLogLevel = Level.INFO;
private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
diff --git a/src/main/java/gim/commons/core/Messages.java b/src/main/java/gim/commons/core/Messages.java
new file mode 100644
index 00000000000..4144eb0af02
--- /dev/null
+++ b/src/main/java/gim/commons/core/Messages.java
@@ -0,0 +1,29 @@
+package gim.commons.core;
+
+/**
+ * Container for user visible messages.
+ */
+public class Messages {
+
+ public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
+ public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
+ public static final String MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX = "The exercise index provided is invalid";
+ public static final String MESSAGE_EXERCISES_LISTED_OVERVIEW = "%1$d exercises listed!";
+ public static final String MESSAGE_FIND_EMPTY_EXERCISES_LIST = "%1$d exercises listed!\n"
+ + "Note that :filter only filters for you the list shown on the Exercise List Window.\n"
+ + "If you think your keyword is correct, try executing :list before executing :filter again.";
+ public static final String MESSAGE_INCORRECT_INDEX_FORMAT = "Possible incorrect format for index(es)";
+ public static final String MESSAGE_MISSING_LEVEL = "No difficulty level selected!";
+ public static final String MESSAGE_INVALID_LEVEL = "Difficulty level not supported!";
+
+ /**
+ * Specific messages for the {@RangeCommand} feature (variation two).
+ * Note: variation two follows the format :range last/INTEGER
+ */
+ public static final String MESSAGE_RANGE_COMMAND_TWO = "Exercises from the last %1$d days listed!";
+ public static final String MESSAGE_RANGE_COMMAND_TWO_TODAY = "Exercises from today listed!";
+ public static final String MESSAGE_RANGE_COMMAND_TWO_YESTERDAY = "Exercises since yesterday listed!";
+ public static final String MESSAGE_RANGE_COMMAND_TWO_WEEK =
+ "Exercises since the last week (i.e. last 7 days) listed!";
+
+}
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/gim/commons/core/Version.java
similarity index 98%
rename from src/main/java/seedu/address/commons/core/Version.java
rename to src/main/java/gim/commons/core/Version.java
index 12142ec1e32..e41a170f223 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/gim/commons/core/Version.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package gim.commons.core;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/gim/commons/core/index/Index.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/index/Index.java
rename to src/main/java/gim/commons/core/index/Index.java
index 19536439c09..3dd86965267 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/gim/commons/core/index/Index.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core.index;
+package gim.commons.core.index;
/**
* Represents a zero-based or one-based index.
diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/gim/commons/exceptions/DataConversionException.java
similarity index 84%
rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java
rename to src/main/java/gim/commons/exceptions/DataConversionException.java
index 1f689bd8e3f..843c6a8af1c 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java
+++ b/src/main/java/gim/commons/exceptions/DataConversionException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package gim.commons.exceptions;
/**
* Represents an error during conversion of data from one format to another
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/gim/commons/exceptions/IllegalValueException.java
similarity index 93%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/gim/commons/exceptions/IllegalValueException.java
index 19124db485c..e54961271f2 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/gim/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package gim.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/gim/commons/util/AppUtil.java
similarity index 94%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/gim/commons/util/AppUtil.java
index 87aa89c0326..bfdf39f6faf 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/gim/commons/util/AppUtil.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import static java.util.Objects.requireNonNull;
+import gim.MainApp;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
/**
* A container for App specific utility functions
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/gim/commons/util/CollectionUtil.java
similarity index 96%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/gim/commons/util/CollectionUtil.java
index eafe4dfd681..8ce04df26c6 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/gim/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/gim/commons/util/ConfigUtil.java
similarity index 77%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/gim/commons/util/ConfigUtil.java
index f7f8a2bd44c..7d17da67d0d 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/gim/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataConversionException;
+import gim.commons.core.Config;
+import gim.commons.exceptions.DataConversionException;
/**
* A class for accessing the Config File.
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/gim/commons/util/FileUtil.java
similarity index 98%
rename from src/main/java/seedu/address/commons/util/FileUtil.java
rename to src/main/java/gim/commons/util/FileUtil.java
index b1e2767cdd9..756ce3f03c0 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/gim/commons/util/FileUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import java.io.IOException;
import java.nio.file.Files;
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/gim/commons/util/JsonUtil.java
similarity index 97%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/gim/commons/util/JsonUtil.java
index 8ef609f055d..2f1e47eccd1 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/gim/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
+import gim.commons.core.LogsCenter;
+import gim.commons.exceptions.DataConversionException;
/**
* Converts a Java object instance to JSON and vice versa
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/gim/commons/util/StringUtil.java
similarity index 95%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/gim/commons/util/StringUtil.java
index 61cc8c9a1cb..0ac4b346653 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/gim/commons/util/StringUtil.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.commons.util.AppUtil.checkArgument;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
diff --git a/src/main/java/gim/logic/Logic.java b/src/main/java/gim/logic/Logic.java
new file mode 100644
index 00000000000..0c03a5601a5
--- /dev/null
+++ b/src/main/java/gim/logic/Logic.java
@@ -0,0 +1,59 @@
+package gim.logic;
+
+import java.nio.file.Path;
+
+import gim.commons.core.GuiSettings;
+import gim.logic.commands.CommandResult;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import javafx.collections.ObservableList;
+
+/**
+ * API of the Logic component
+ */
+public interface Logic {
+ /**
+ * Executes the command and returns the result.
+ * @param commandText The command as entered by the user.
+ * @return the result of the command execution.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ */
+ CommandResult execute(String commandText) throws CommandException, ParseException;
+
+ /**
+ * Returns the ExerciseTracker.
+ *
+ * @see gim.model.Model#getExerciseTracker()
+ */
+ ReadOnlyExerciseTracker getExerciseTracker();
+
+ /**
+ * Returns an unmodifiable view of the filtered list of exercises.
+ */
+ ObservableList getFilteredExerciseList();
+
+ /**
+ * Returns a copy of the hashmap of exercises stored.
+ */
+ ExerciseHashMap getExerciseHashmap();
+
+ /**
+ * Returns the user prefs' exercise tracker file path.
+ */
+ Path getExerciseTrackerFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/gim/logic/LogicManager.java
similarity index 54%
rename from src/main/java/seedu/address/logic/LogicManager.java
rename to src/main/java/gim/logic/LogicManager.java
index 9d9c6d15bdc..85951a439e7 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/gim/logic/LogicManager.java
@@ -1,21 +1,22 @@
-package seedu.address.logic;
+package gim.logic;
import java.io.IOException;
import java.nio.file.Path;
import java.util.logging.Logger;
+import gim.commons.core.GuiSettings;
+import gim.commons.core.LogsCenter;
+import gim.logic.commands.Command;
+import gim.logic.commands.CommandResult;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.parser.ExerciseTrackerParser;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.Model;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.storage.Storage;
import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.AddressBookParser;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-import seedu.address.storage.Storage;
/**
* The main LogicManager of the app.
@@ -26,7 +27,7 @@ public class LogicManager implements Logic {
private final Model model;
private final Storage storage;
- private final AddressBookParser addressBookParser;
+ private final ExerciseTrackerParser exerciseTrackerParser;
/**
* Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
@@ -34,7 +35,7 @@ public class LogicManager implements Logic {
public LogicManager(Model model, Storage storage) {
this.model = model;
this.storage = storage;
- addressBookParser = new AddressBookParser();
+ exerciseTrackerParser = new ExerciseTrackerParser();
}
@Override
@@ -42,11 +43,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
logger.info("----------------[USER COMMAND][" + commandText + "]");
CommandResult commandResult;
- Command command = addressBookParser.parseCommand(commandText);
+ Command command = exerciseTrackerParser.parseCommand(commandText);
commandResult = command.execute(model);
try {
- storage.saveAddressBook(model.getAddressBook());
+ storage.saveExerciseTracker(model.getExerciseTracker());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -55,18 +56,18 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
}
@Override
- public ReadOnlyAddressBook getAddressBook() {
- return model.getAddressBook();
+ public ReadOnlyExerciseTracker getExerciseTracker() {
+ return model.getExerciseTracker();
}
@Override
- public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
+ public ObservableList getFilteredExerciseList() {
+ return model.getFilteredExerciseList();
}
@Override
- public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
+ public Path getExerciseTrackerFilePath() {
+ return model.getExerciseTrackerFilePath();
}
@Override
@@ -78,4 +79,8 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ public ExerciseHashMap getExerciseHashmap() {
+ return model.getExerciseHashMap();
+ }
}
diff --git a/src/main/java/gim/logic/commands/AddCommand.java b/src/main/java/gim/logic/commands/AddCommand.java
new file mode 100644
index 00000000000..dde87e4fe3e
--- /dev/null
+++ b/src/main/java/gim/logic/commands/AddCommand.java
@@ -0,0 +1,59 @@
+package gim.logic.commands;
+
+import static gim.logic.parser.CliSyntax.PREFIX_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+import static gim.logic.parser.CliSyntax.PREFIX_REPS;
+import static gim.logic.parser.CliSyntax.PREFIX_SETS;
+import static gim.logic.parser.CliSyntax.PREFIX_WEIGHT;
+import static java.util.Objects.requireNonNull;
+
+import gim.logic.commands.exceptions.CommandException;
+import gim.model.Model;
+import gim.model.exercise.Exercise;
+
+
+/**
+ * Adds an exercise to Gim.
+ */
+public class AddCommand extends Command {
+
+ public static final String COMMAND_WORD = ":add";
+
+ public static final String MESSAGE_USAGE =
+ COMMAND_WORD + " -> Adds an exercise to Gim. "
+ + "Parameters: " + PREFIX_NAME
+ + "NAME " + PREFIX_WEIGHT
+ + "WEIGHT " + PREFIX_SETS
+ + "SETS " + PREFIX_REPS
+ + "REPS [" + PREFIX_DATE
+ + "DATE]\n"
+ + "Example usage:\n" + COMMAND_WORD + " "
+ + PREFIX_NAME + "Squat " + PREFIX_WEIGHT + "60 " + PREFIX_SETS + "1 "
+ + PREFIX_REPS + "5 " + PREFIX_DATE + "25/01/2022";
+
+ public static final String MESSAGE_SUCCESS = "New exercise '%s' added as: %s";
+
+ private final Exercise toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Exercise}
+ */
+ public AddCommand(Exercise exercise) {
+ requireNonNull(exercise);
+ toAdd = exercise;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Exercise added = model.addExercise(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.getName().toString(), added));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddCommand // instanceof handles nulls
+ && toAdd.equals(((AddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/gim/logic/commands/ClearCommand.java b/src/main/java/gim/logic/commands/ClearCommand.java
new file mode 100644
index 00000000000..7e2ff6c0adc
--- /dev/null
+++ b/src/main/java/gim/logic/commands/ClearCommand.java
@@ -0,0 +1,37 @@
+package gim.logic.commands;
+
+import static gim.logic.parser.CliSyntax.PREFIX_CONFIRM;
+import static java.util.Objects.requireNonNull;
+
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.exercise.ExerciseHashMap;
+
+/**
+ * Clears the exercise tracker.
+ */
+public class ClearCommand extends Command {
+
+ public static final String COMMAND_WORD = ":clear";
+
+ public static final String MESSAGE_USAGE = "Please confirm that you want to clear the exercise tracker.\n"
+ + "Parameters: " + PREFIX_CONFIRM + "\n"
+ + "Example usage:\n" + COMMAND_WORD
+ + " " + PREFIX_CONFIRM;
+
+ public static final String MESSAGE_SUCCESS = "Exercise tracker has been cleared!";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ ExerciseHashMap cleared = model.getExerciseHashMap().clearExerciseHashMap();
+ model.setExerciseTracker(new ExerciseTracker(cleared));
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ClearCommand; // instanceof handles nulls
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/gim/logic/commands/Command.java
similarity index 78%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/gim/logic/commands/Command.java
index 64f18992160..3a124b6294c 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/gim/logic/commands/Command.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import gim.logic.commands.exceptions.CommandException;
+import gim.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/gim/logic/commands/CommandResult.java
similarity index 97%
rename from src/main/java/seedu/address/logic/commands/CommandResult.java
rename to src/main/java/gim/logic/commands/CommandResult.java
index 92f900b7916..eece5a3833d 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/gim/logic/commands/CommandResult.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/gim/logic/commands/DeleteCommand.java b/src/main/java/gim/logic/commands/DeleteCommand.java
new file mode 100644
index 00000000000..3303a9b9614
--- /dev/null
+++ b/src/main/java/gim/logic/commands/DeleteCommand.java
@@ -0,0 +1,53 @@
+package gim.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import gim.commons.core.Messages;
+import gim.commons.core.index.Index;
+import gim.logic.commands.exceptions.CommandException;
+import gim.model.Model;
+import gim.model.exercise.Exercise;
+
+/**
+ * Deletes an exercise identified using it's displayed index from the exercise tracker.
+ */
+public class DeleteCommand extends Command {
+
+ public static final String COMMAND_WORD = ":del";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + " -> Deletes the exercise identified by the index number used in the displayed exercise list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example usage:\n" + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_EXERCISE_SUCCESS = "Deleted Exercise: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredExerciseList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
+ }
+
+ Exercise exerciseToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteExercise(exerciseToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_EXERCISE_SUCCESS, exerciseToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/gim/logic/commands/ExitCommand.java
similarity index 61%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/gim/logic/commands/ExitCommand.java
index 3dd85a8ba90..b19d08bdc61 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/gim/logic/commands/ExitCommand.java
@@ -1,18 +1,19 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import seedu.address.model.Model;
+import gim.model.Model;
/**
* Terminates the program.
*/
public class ExitCommand extends Command {
- public static final String COMMAND_WORD = "exit";
+ public static final String COMMAND_WORD = ":wq";
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
+ public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Exercise Tracker as requested ...";
@Override
public CommandResult execute(Model model) {
+ model.resetDisplayedList();
return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
}
diff --git a/src/main/java/gim/logic/commands/FilterCommand.java b/src/main/java/gim/logic/commands/FilterCommand.java
new file mode 100644
index 00000000000..211de2ae3ce
--- /dev/null
+++ b/src/main/java/gim/logic/commands/FilterCommand.java
@@ -0,0 +1,46 @@
+package gim.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import gim.commons.core.Messages;
+import gim.model.Model;
+import gim.model.exercise.NameContainsKeywordsPredicate;
+
+/**
+ * Filters and lists all exercises in exercise tracker whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FilterCommand extends Command {
+
+ public static final String COMMAND_WORD = ":filter";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " -> Filters all exercises whose names contain any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [KEYWORD]\n"
+ + "Example usage:\n" + COMMAND_WORD + " Squat Deadlift";
+
+ private final NameContainsKeywordsPredicate predicate;
+
+ public FilterCommand(NameContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.filterFilteredExerciseList(predicate);
+ if (model.getFilteredExerciseList().size() == 0) {
+ return new CommandResult(
+ String.format(Messages.MESSAGE_FIND_EMPTY_EXERCISES_LIST, model.getFilteredExerciseList().size()));
+ }
+ return new CommandResult(
+ String.format(Messages.MESSAGE_EXERCISES_LISTED_OVERVIEW, model.getFilteredExerciseList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FilterCommand // instanceof handles nulls
+ && predicate.equals(((FilterCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/gim/logic/commands/GenerateCommand.java b/src/main/java/gim/logic/commands/GenerateCommand.java
new file mode 100644
index 00000000000..71b946eae07
--- /dev/null
+++ b/src/main/java/gim/logic/commands/GenerateCommand.java
@@ -0,0 +1,149 @@
+package gim.logic.commands;
+
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+import static gim.logic.parser.CliSyntax.PREFIX_LEVEL;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import gim.commons.core.Messages;
+import gim.commons.core.index.Index;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.generators.Generator;
+import gim.logic.generators.GeneratorFactory;
+import gim.logic.generators.ValidLevel;
+import gim.model.Model;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+
+/**
+ * Generates a sample workout based on existing PRs of the specified exercises,
+ * according to the difficulty level specified.
+ * Difficulty levels supported: {easy, medium, hard}.
+ */
+public class GenerateCommand extends Command {
+
+ public static final String COMMAND_WORD = ":gen";
+ public static final String DIFFICULTY_LEVELS = "{easy, medium, hard}";
+
+ public static final String MESSAGE_USAGE = "The generate feature has two variations: \n\n"
+ + COMMAND_WORD + " INDEX [, INDEX]... " + PREFIX_LEVEL + "LEVEL\n"
+ + "Each INDEX must be a positive integer; LEVEL must be one of " + DIFFICULTY_LEVELS + ".\n"
+ + "Example usage: \n" + COMMAND_WORD + " 2, 3 " + PREFIX_LEVEL + "easy\n\n"
+ + COMMAND_WORD + " n/NAME [n/NAME]... level/LEVEL\n"
+ + "NAME must be of an exercise found in the system; LEVEL must be one of " + DIFFICULTY_LEVELS + ".\n"
+ + "Example usage: \n" + COMMAND_WORD + " n/squat n/deadlift " + PREFIX_LEVEL + "easy";
+
+ public static final String MESSAGE_GENERATE_SUCCESS = " workout session generated: \n";
+
+ private final ArrayList indices;
+ private final ValidLevel level;
+ private final Set nameSet;
+
+
+ /**
+ * @param indices of the exercises in the filtered exercise list.
+ * @param level difficulty level of the workout generated.
+ */
+ public GenerateCommand(ArrayList indices, ValidLevel level) {
+ requireAllNonNull(indices, level);
+ this.indices = indices;
+ this.level = level;
+ this.nameSet = null;
+ }
+
+ /**
+ * @param nameSet containing names of the exercises given by user, not verified as valid.
+ * @param level difficulty level of the workout generated.
+ */
+ public GenerateCommand(Set nameSet, ValidLevel level) {
+ requireAllNonNull(nameSet, level);
+ this.nameSet = nameSet;
+ this.level = level;
+ this.indices = null;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ if (nameSet != null) {
+ return getCommandResultFromNames(model);
+ }
+ return getCommandResultFromIndices(model);
+ }
+
+ private CommandResult getCommandResultFromIndices(Model model) throws CommandException {
+ List lastShownList = model.getFilteredExerciseList();
+ StringBuilder fullSuggestion = new StringBuilder();
+ HashSet uniqueNameSet = new HashSet<>();
+ for (Index index : indices) {
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
+ }
+ Exercise exerciseToEdit = lastShownList.get(index.getZeroBased());
+ Name exerciseName = exerciseToEdit.getName();
+ if (uniqueNameSet.contains(exerciseName)) {
+ continue;
+ }
+ uniqueNameSet.add(exerciseName);
+ Exercise exercisePR = model.getExercisePR(exerciseName);
+ Generator generator = GeneratorFactory.getGenerator(exercisePR, level);
+ String suggestion = requireNonNull(generator).suggest();
+ fullSuggestion.append(suggestion).append("\n");
+ }
+ return new CommandResult(level + MESSAGE_GENERATE_SUCCESS + fullSuggestion);
+ }
+
+ private CommandResult getCommandResultFromNames(Model model) throws CommandException {
+ StringBuilder fullSuggestion = new StringBuilder();
+ HashSet uniqueNameSet = new HashSet<>();
+ boolean atLeastOneExerciseFound = false;
+ for (Name name : nameSet) {
+ if (uniqueNameSet.contains(name)) {
+ continue;
+ }
+ uniqueNameSet.add(name);
+ Exercise exercisePR = model.getExercisePR(name);
+ if (exercisePR == null) { // cannot find name in system
+ continue;
+ }
+ atLeastOneExerciseFound = true;
+ Generator generator = GeneratorFactory.getGenerator(exercisePR, level);
+ String suggestion = requireNonNull(generator).suggest();
+ fullSuggestion.append(suggestion).append("\n");
+ }
+ if (!atLeastOneExerciseFound) { // failed to find any exercise in model
+ throw new CommandException(PrCommand.MESSAGE_FAILURE);
+ }
+ return new CommandResult(level + MESSAGE_GENERATE_SUCCESS + fullSuggestion);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof GenerateCommand)) {
+ return false;
+ }
+
+ // state check
+ GenerateCommand e = (GenerateCommand) other;
+ if (indices != null) {
+ return indices.equals(e.indices) && level.equals(e.level);
+ } else {
+ return nameSet.equals(e.nameSet) && level.equals(e.level);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return indices.toString() + " l/" + level;
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/gim/logic/commands/HelpCommand.java
similarity index 55%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/gim/logic/commands/HelpCommand.java
index bf824f91bd0..0a9860bbde6 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/gim/logic/commands/HelpCommand.java
@@ -1,16 +1,16 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import seedu.address.model.Model;
+import gim.model.Model;
/**
* Format full help instructions for every command for display.
*/
public class HelpCommand extends Command {
- public static final String COMMAND_WORD = "help";
+ public static final String COMMAND_WORD = ":help";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
- + "Example: " + COMMAND_WORD;
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " -> Shows program usage instructions.\n"
+ + "Example usage: " + COMMAND_WORD;
public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
diff --git a/src/main/java/gim/logic/commands/ListCommand.java b/src/main/java/gim/logic/commands/ListCommand.java
new file mode 100644
index 00000000000..2ec2ddfeb09
--- /dev/null
+++ b/src/main/java/gim/logic/commands/ListCommand.java
@@ -0,0 +1,24 @@
+package gim.logic.commands;
+
+import static gim.model.Model.PREDICATE_SHOW_ALL_EXERCISES;
+import static java.util.Objects.requireNonNull;
+
+import gim.model.Model;
+
+/**
+ * Lists all exercises in the exercise tracker to the user.
+ */
+public class ListCommand extends Command {
+
+ public static final String COMMAND_WORD = ":list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all exercises";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredExerciseList(PREDICATE_SHOW_ALL_EXERCISES);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/gim/logic/commands/PrCommand.java b/src/main/java/gim/logic/commands/PrCommand.java
new file mode 100644
index 00000000000..87daa634ad0
--- /dev/null
+++ b/src/main/java/gim/logic/commands/PrCommand.java
@@ -0,0 +1,109 @@
+package gim.logic.commands;
+
+import static gim.logic.parser.CliSyntax.PREFIX_ALL;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Set;
+
+import gim.model.Model;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+
+/**
+ * For all exercises whose name contains any of the argument keywords, find the personal record (highest weight
+ * achieved) of each exercise.
+ * Keyword matching is case-insensitive.
+ */
+public class PrCommand extends Command {
+
+ public static final String COMMAND_WORD = ":pr";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + " -> Find the personal record of each inputted exercise.\n"
+ + "Parameters: " + PREFIX_NAME
+ + "NAME " + "[" + PREFIX_NAME
+ + "NAME]..." + "\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "squat " + PREFIX_NAME + "bench press\n"
+ + "OR \n"
+ + "List personal records for all exercises.\n"
+ + "Parameters: " + PREFIX_ALL + "\n"
+ + "Example usage: " + COMMAND_WORD + " " + PREFIX_ALL;
+
+ public static final String MESSAGE_SUCCESS = "Listing PRs:\n%s";
+
+ public static final String MESSAGE_FAILURE = "Exercise(s) not registered in system!\nTry adding it first!";
+
+ private final Set nameSet;
+
+ /**
+ * Creates a PRCommand to add the specified {@code Exercise}
+ */
+ public PrCommand(Set nameSet) {
+ requireNonNull(nameSet);
+ this.nameSet = nameSet;
+ }
+
+ /**
+ * Returns the pretty PrCommand output for an individual Exercise
+ * @param exercise Exercise
+ * @return Display PrCommand Exercise output.
+ */
+ public static String prExerciseStringify(Exercise exercise) {
+ return exercise.getName() + ": " + exercise.getWeight() + "kg" + "\n";
+ }
+
+ /**
+ * Given the appropriate ArrayList, returns a prettier String output for PrCommand.
+ * @param list ArrayList.
+ * @return Display PrCommand overall output.
+ */
+ public static String prettyStringifyArrayList(ArrayList list) {
+ list.sort(Comparator.comparing(Exercise::getName)); // Sort the List by Name (Alphabetically)
+ StringBuilder returnString = new StringBuilder();
+ for (Exercise exercise : list) {
+ returnString.append(prExerciseStringify(exercise));
+ }
+ return returnString.toString();
+ }
+
+ /**
+ * Generate an ArrayList containing Exercises which are PRs.
+ * @param nameSet nameSet.
+ * @param model Model.
+ * @return ArrayList containing Exercise PRs.
+ */
+ public ArrayList generateOutputArrayList(Set nameSet, Model model) {
+ if (nameSet.isEmpty()) {
+ return model.getAllExercisePRs();
+ } else {
+ ArrayList outputList = new ArrayList<>();
+ for (Name name : nameSet) {
+ Exercise exerciseWithPR = model.getExercisePR(name);
+ if (exerciseWithPR != null) {
+ outputList.add(exerciseWithPR);
+ }
+ }
+ return outputList;
+ }
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ ArrayList outputList = generateOutputArrayList(nameSet, model);
+ if (outputList.isEmpty()) {
+ return new CommandResult(MESSAGE_FAILURE);
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, prettyStringifyArrayList(outputList)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof PrCommand // instanceof handles nulls
+ && nameSet.equals(((PrCommand) other).nameSet));
+ }
+}
diff --git a/src/main/java/gim/logic/commands/RangeCommand.java b/src/main/java/gim/logic/commands/RangeCommand.java
new file mode 100644
index 00000000000..8bb844c569b
--- /dev/null
+++ b/src/main/java/gim/logic/commands/RangeCommand.java
@@ -0,0 +1,89 @@
+package gim.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import gim.commons.core.Messages;
+import gim.model.Model;
+import gim.model.exercise.DateWithinRangePredicate;
+
+/**
+ * Finds all exercises with dates that are within the specified start date
+ * and end date (both inclusive).
+ */
+public class RangeCommand extends Command {
+
+ public static final String COMMAND_WORD = ":range";
+
+ public static final String MESSAGE_USAGE = "The range feature has two variations: \n\n"
+ + COMMAND_WORD + " start/START_DATE end/END_DATE\n"
+ + "displays all exercises with dates that are within "
+ + "the specified start date and end date.\n"
+ + "Example: \n" + COMMAND_WORD + " start/10/10/2022 end/22/10/2022\n\n"
+ + COMMAND_WORD + " last/NUMBER_OF_DAYS\n"
+ + "displays all exercises within the last NUMBER_OF_DAYS days and and today's exercises.\n"
+ + "NUMBER_OF_DAYS can only take positive integer values, up to 6 digits.\n"
+ + "Example usage: \n" + COMMAND_WORD + " last/7";
+
+ public static final String MESSAGE_USAGE_TWO = "The range feature has two variations: \n\n"
+ + COMMAND_WORD + " last/NUMBER_OF_DAYS\n"
+ + "displays all exercises within the last NUMBER_OF_DAYS days and and today's exercises.\n"
+ + "NUMBER_OF_DAYS can only take positive integer values, up to 6 digits.\n"
+ + "Example: \n" + COMMAND_WORD + " last/7\n\n"
+ + COMMAND_WORD + " start/START_DATE end/END_DATE\n"
+ + "displays all exercises with dates that are within "
+ + "the specified start date and end date.\n"
+ + "Example: \n" + COMMAND_WORD + " start/10/10/2022 end/22/10/2022";
+
+ public final boolean isAdvanced;
+ private final DateWithinRangePredicate predicate;
+
+ /**
+ * Default constructor for basic version.
+ * @param predicate {@code DateWithinRangePredicate} object to determine the range
+ */
+ public RangeCommand(DateWithinRangePredicate predicate) {
+ this.predicate = predicate;
+ this.isAdvanced = false;
+ }
+
+ /**
+ * Extra constructor for basic version and advanced version.
+ *
+ * @param predicate {@code DateWithinRangePredicate} object to determine the range
+ * @param isAdvanced value is true for basic version and false for advanced version of the {@code RangeCommand}
+ */
+ public RangeCommand(DateWithinRangePredicate predicate, boolean isAdvanced) {
+ this.predicate = predicate;
+ this.isAdvanced = isAdvanced;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.sortFilteredExerciseList(predicate);
+ int rangeInDays = predicate.getRangeSizeInDays();
+
+ // Display different message to user when command used is the advanced version
+ if (isAdvanced && rangeInDays == 0) {
+ return new CommandResult(Messages.MESSAGE_RANGE_COMMAND_TWO_TODAY);
+ } else if (isAdvanced && rangeInDays == 1) {
+ return new CommandResult(Messages.MESSAGE_RANGE_COMMAND_TWO_YESTERDAY);
+ } else if (isAdvanced && rangeInDays == 7) {
+ return new CommandResult(Messages.MESSAGE_RANGE_COMMAND_TWO_WEEK);
+ } else if (isAdvanced && rangeInDays > 1) {
+ return new CommandResult(
+ String.format(Messages.MESSAGE_RANGE_COMMAND_TWO, rangeInDays));
+ }
+
+ // Display different message to user when command used is the basic version
+ return new CommandResult(
+ String.format(Messages.MESSAGE_EXERCISES_LISTED_OVERVIEW, model.getFilteredExerciseList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof RangeCommand // instanceof handles nulls
+ && predicate.equals(((RangeCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/gim/logic/commands/SortCommand.java b/src/main/java/gim/logic/commands/SortCommand.java
new file mode 100644
index 00000000000..8e71786ae1d
--- /dev/null
+++ b/src/main/java/gim/logic/commands/SortCommand.java
@@ -0,0 +1,24 @@
+package gim.logic.commands;
+
+import static gim.model.Model.PREDICATE_SHOW_ALL_EXERCISES;
+import static java.util.Objects.requireNonNull;
+
+import gim.model.Model;
+
+/**
+ * Sorts the exercises displayed according to the date of exercises done.
+ */
+public class SortCommand extends Command {
+
+ public static final String COMMAND_WORD = ":sort";
+
+ public static final String MESSAGE_SUCCESS = "Sorted all exercises by date";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.sortFilteredExerciseList(PREDICATE_SHOW_ALL_EXERCISES);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/gim/logic/commands/exceptions/CommandException.java
similarity index 89%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/gim/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..68439fd99ef 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/gim/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands.exceptions;
+package gim.logic.commands.exceptions;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/gim/logic/generators/EasyGenerator.java b/src/main/java/gim/logic/generators/EasyGenerator.java
new file mode 100644
index 00000000000..43ad9feeab5
--- /dev/null
+++ b/src/main/java/gim/logic/generators/EasyGenerator.java
@@ -0,0 +1,40 @@
+package gim.logic.generators;
+
+import gim.model.exercise.Exercise;
+
+
+/**
+ * Generator for easy workout session.
+ */
+public class EasyGenerator implements Generator {
+ private Exercise exercisePR;
+
+ public EasyGenerator(Exercise exercisePR) {
+ this.exercisePR = exercisePR;
+ }
+
+ @Override
+ public String suggest() {
+ String setsAndReps = "3sets x 8reps";
+ double factor = 0.5;
+ double suggestedWeight = factor * Double.parseDouble(exercisePR.getWeight().value);
+ return String.format("%s: %.2fkg %s", exercisePR.getName(), suggestedWeight, setsAndReps);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EasyGenerator)) {
+ return false;
+ }
+
+ // state check
+ EasyGenerator e = (EasyGenerator) other;
+ return exercisePR.equals(e.exercisePR);
+ }
+}
diff --git a/src/main/java/gim/logic/generators/Generator.java b/src/main/java/gim/logic/generators/Generator.java
new file mode 100644
index 00000000000..750c48a48c1
--- /dev/null
+++ b/src/main/java/gim/logic/generators/Generator.java
@@ -0,0 +1,10 @@
+package gim.logic.generators;
+
+
+/**
+ * Represents a generator, with the ability to generate a suggestion based on level specified and exercise name.
+ * Each generator can implement its suggest() logic separately.
+ */
+public interface Generator {
+ String suggest();
+}
diff --git a/src/main/java/gim/logic/generators/GeneratorFactory.java b/src/main/java/gim/logic/generators/GeneratorFactory.java
new file mode 100644
index 00000000000..8458e12e6b0
--- /dev/null
+++ b/src/main/java/gim/logic/generators/GeneratorFactory.java
@@ -0,0 +1,31 @@
+package gim.logic.generators;
+
+import static gim.logic.generators.ValidLevel.EASY;
+import static gim.logic.generators.ValidLevel.HARD;
+import static gim.logic.generators.ValidLevel.MEDIUM;
+
+import gim.model.exercise.Exercise;
+
+/**
+ * Selects and creates the appropriate generator based on a {@link ValidLevel}.
+ */
+public class GeneratorFactory {
+
+ /**
+ * @param exercisePR the PR of the exercise to generate a workout for.
+ * @param level difficulty level of the workout to be generated.
+ */
+ public static Generator getGenerator(Exercise exercisePR, ValidLevel level) {
+ if (level.equals(EASY)) {
+ return new EasyGenerator(exercisePR);
+ }
+ if (level.equals(MEDIUM)) {
+ return new MediumGenerator(exercisePR);
+ }
+ if (level.equals(HARD)) {
+ return new HardGenerator(exercisePR);
+ }
+ assert false : "cannot create generator of invalid difficulty level: " + level;
+ return null;
+ }
+}
diff --git a/src/main/java/gim/logic/generators/HardGenerator.java b/src/main/java/gim/logic/generators/HardGenerator.java
new file mode 100644
index 00000000000..3ed373783f2
--- /dev/null
+++ b/src/main/java/gim/logic/generators/HardGenerator.java
@@ -0,0 +1,39 @@
+package gim.logic.generators;
+
+import gim.model.exercise.Exercise;
+
+/**
+ * Generator for easy workout session.
+ */
+public class HardGenerator implements Generator {
+ private Exercise exercisePR;
+
+ public HardGenerator(Exercise exercisePR) {
+ this.exercisePR = exercisePR;
+ }
+
+ @Override
+ public String suggest() {
+ String setsAndReps = "3sets x 8reps";
+ double factor = 0.8;
+ double suggestedWeight = factor * Double.parseDouble(exercisePR.getWeight().value);
+ return String.format("%s: %.2fkg %s", exercisePR.getName(), suggestedWeight, setsAndReps);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof HardGenerator)) {
+ return false;
+ }
+
+ // state check
+ HardGenerator e = (HardGenerator) other;
+ return exercisePR.equals(e.exercisePR);
+ }
+}
diff --git a/src/main/java/gim/logic/generators/MediumGenerator.java b/src/main/java/gim/logic/generators/MediumGenerator.java
new file mode 100644
index 00000000000..cccf093db49
--- /dev/null
+++ b/src/main/java/gim/logic/generators/MediumGenerator.java
@@ -0,0 +1,40 @@
+package gim.logic.generators;
+
+import gim.model.exercise.Exercise;
+
+/**
+ * Generator for easy workout session.
+ */
+public class MediumGenerator implements Generator {
+ private Exercise exercisePR;
+
+ public MediumGenerator(Exercise exercisePR) {
+ this.exercisePR = exercisePR;
+ }
+
+ @Override
+ public String suggest() {
+ String setsAndReps = "3sets x 8reps";
+ double factor = 0.7;
+ double suggestedWeight = factor * Double.parseDouble(exercisePR.getWeight().value);
+ return String.format("%s: %.2fkg %s", exercisePR.getName(), suggestedWeight, setsAndReps);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof MediumGenerator)) {
+ return false;
+ }
+
+ // state check
+ MediumGenerator e = (MediumGenerator) other;
+ return exercisePR.equals(e.exercisePR);
+ }
+}
+
diff --git a/src/main/java/gim/logic/generators/ValidLevel.java b/src/main/java/gim/logic/generators/ValidLevel.java
new file mode 100644
index 00000000000..ee5f46762cf
--- /dev/null
+++ b/src/main/java/gim/logic/generators/ValidLevel.java
@@ -0,0 +1,11 @@
+package gim.logic.generators;
+
+/**
+ * Difficulty levels which are valid / supported.
+ * Generation of workout suggestion must be done based on a {@code ValidLevel}.
+ */
+public enum ValidLevel {
+ EASY,
+ MEDIUM,
+ HARD;
+}
diff --git a/src/main/java/gim/logic/parser/AddCommandParser.java b/src/main/java/gim/logic/parser/AddCommandParser.java
new file mode 100644
index 00000000000..206ac7159c6
--- /dev/null
+++ b/src/main/java/gim/logic/parser/AddCommandParser.java
@@ -0,0 +1,67 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.logic.parser.CliSyntax.PREFIX_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+import static gim.logic.parser.CliSyntax.PREFIX_REPS;
+import static gim.logic.parser.CliSyntax.PREFIX_SETS;
+import static gim.logic.parser.CliSyntax.PREFIX_WEIGHT;
+
+import java.util.stream.Stream;
+
+import gim.logic.commands.AddCommand;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.date.Date;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+import gim.model.exercise.Reps;
+import gim.model.exercise.Sets;
+import gim.model.exercise.Weight;
+
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class AddCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddCommand
+ * and returns an AddCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_WEIGHT, PREFIX_SETS,
+ PREFIX_REPS, PREFIX_DATE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_REPS, PREFIX_WEIGHT, PREFIX_SETS)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ }
+
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ Weight weight = ParserUtil.parseWeight(argMultimap.getValue(PREFIX_WEIGHT).get());
+ Sets sets = ParserUtil.parseSets(argMultimap.getValue(PREFIX_SETS).get());
+ Reps reps = ParserUtil.parseRep(argMultimap.getValue(PREFIX_REPS).get());
+ Date date;
+ Exercise exercise;
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get());
+ exercise = new Exercise(name, weight, sets, reps, date);
+ } else {
+ // if date is not supplied by user, we set the default date (which is today's date)
+ exercise = new Exercise(name, weight, sets, reps, new Date());
+ }
+
+ return new AddCommand(exercise);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/gim/logic/parser/ArgumentMultimap.java
similarity index 98%
rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
rename to src/main/java/gim/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..d8851992a63 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/gim/logic/parser/ArgumentMultimap.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/gim/logic/parser/ArgumentTokenizer.java
similarity index 99%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/gim/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..33155fb4fec 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/gim/logic/parser/ArgumentTokenizer.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/main/java/gim/logic/parser/ClearCommandParser.java b/src/main/java/gim/logic/parser/ClearCommandParser.java
new file mode 100644
index 00000000000..a01cce5c1a0
--- /dev/null
+++ b/src/main/java/gim/logic/parser/ClearCommandParser.java
@@ -0,0 +1,36 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.logic.parser.CliSyntax.PREFIX_CONFIRM;
+
+import java.util.stream.Stream;
+
+import gim.logic.commands.ClearCommand;
+import gim.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new PRCommand object
+ */
+public class ClearCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ClearCommand
+ * and returns an ClearCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ClearCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CONFIRM);
+ if (!arePrefixesPresent(argMultimap, PREFIX_CONFIRM)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ClearCommand.MESSAGE_USAGE));
+ }
+ return new ClearCommand();
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+}
diff --git a/src/main/java/gim/logic/parser/CliSyntax.java b/src/main/java/gim/logic/parser/CliSyntax.java
new file mode 100644
index 00000000000..7dbb00cedf2
--- /dev/null
+++ b/src/main/java/gim/logic/parser/CliSyntax.java
@@ -0,0 +1,20 @@
+package gim.logic.parser;
+
+/**
+ * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
+ */
+public class CliSyntax {
+
+ /* Prefix definitions */
+ public static final Prefix PREFIX_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_WEIGHT = new Prefix("w/");
+ public static final Prefix PREFIX_SETS = new Prefix("s/");
+ public static final Prefix PREFIX_REPS = new Prefix("r/");
+ public static final Prefix PREFIX_DATE = new Prefix("d/");
+ public static final Prefix PREFIX_START_DATE = new Prefix("start/");
+ public static final Prefix PREFIX_END_DATE = new Prefix("end/");
+ public static final Prefix PREFIX_RANGE_VARIATION_TWO = new Prefix("last/");
+ public static final Prefix PREFIX_LEVEL = new Prefix("level/");
+ public static final Prefix PREFIX_ALL = new Prefix("all/");
+ public static final Prefix PREFIX_CONFIRM = new Prefix("confirm/");
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/gim/logic/parser/DeleteCommandParser.java
similarity index 73%
rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
rename to src/main/java/gim/logic/parser/DeleteCommandParser.java
index 522b93081cc..80496f386e2 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/gim/logic/parser/DeleteCommandParser.java
@@ -1,10 +1,10 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
+import gim.commons.core.index.Index;
+import gim.logic.commands.DeleteCommand;
+import gim.logic.parser.exceptions.ParseException;
/**
* Parses input arguments and creates a new DeleteCommand object
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/gim/logic/parser/ExerciseTrackerParser.java
similarity index 55%
rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java
rename to src/main/java/gim/logic/parser/ExerciseTrackerParser.java
index 1e466792b46..79ae96780d3 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/gim/logic/parser/ExerciseTrackerParser.java
@@ -1,26 +1,29 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
+import gim.logic.commands.AddCommand;
+import gim.logic.commands.ClearCommand;
+import gim.logic.commands.Command;
+import gim.logic.commands.DeleteCommand;
+import gim.logic.commands.ExitCommand;
+import gim.logic.commands.FilterCommand;
+import gim.logic.commands.GenerateCommand;
+import gim.logic.commands.HelpCommand;
+import gim.logic.commands.ListCommand;
+import gim.logic.commands.PrCommand;
+import gim.logic.commands.RangeCommand;
+import gim.logic.commands.SortCommand;
+import gim.logic.parser.exceptions.ParseException;
/**
* Parses user input.
*/
-public class AddressBookParser {
+public class ExerciseTrackerParser {
/**
* Used for initial separation of command word and args.
@@ -47,17 +50,14 @@ public Command parseCommand(String userInput) throws ParseException {
case AddCommand.COMMAND_WORD:
return new AddCommandParser().parse(arguments);
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
-
case DeleteCommand.COMMAND_WORD:
return new DeleteCommandParser().parse(arguments);
case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
+ return new ClearCommandParser().parse(arguments);
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
+ case FilterCommand.COMMAND_WORD:
+ return new FilterCommandParser().parse(arguments);
case ListCommand.COMMAND_WORD:
return new ListCommand();
@@ -68,9 +68,20 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case RangeCommand.COMMAND_WORD:
+ return new RangeCommandParser().parse(arguments);
+
+ case GenerateCommand.COMMAND_WORD:
+ return new GenerateCommandParser().parse(arguments);
+
+ case PrCommand.COMMAND_WORD:
+ return new PrCommandParser().parse(arguments);
+
+ case SortCommand.COMMAND_WORD:
+ return new SortCommand();
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
}
-
}
diff --git a/src/main/java/gim/logic/parser/FilterCommandParser.java b/src/main/java/gim/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..adfe329fdff
--- /dev/null
+++ b/src/main/java/gim/logic/parser/FilterCommandParser.java
@@ -0,0 +1,33 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import gim.logic.commands.FilterCommand;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.exercise.NameContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FilterCommand object
+ */
+public class FilterCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterCommand
+ * and returns a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ return new FilterCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+
+}
diff --git a/src/main/java/gim/logic/parser/GenerateCommandParser.java b/src/main/java/gim/logic/parser/GenerateCommandParser.java
new file mode 100644
index 00000000000..ec70dbf25b8
--- /dev/null
+++ b/src/main/java/gim/logic/parser/GenerateCommandParser.java
@@ -0,0 +1,124 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INCORRECT_INDEX_FORMAT;
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.commons.core.Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX;
+import static gim.commons.core.Messages.MESSAGE_INVALID_LEVEL;
+import static gim.commons.core.Messages.MESSAGE_MISSING_LEVEL;
+import static gim.logic.parser.CliSyntax.PREFIX_LEVEL;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import gim.commons.core.index.Index;
+import gim.commons.exceptions.IllegalValueException;
+import gim.logic.commands.GenerateCommand;
+import gim.logic.generators.ValidLevel;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.exercise.Name;
+
+
+/**
+ * Parses input arguments and creates a new {@code GenerateCommand} object
+ */
+public class GenerateCommandParser implements Parser {
+
+ static final String INDEX_VALIDATION_REGEX = "^ *\\d+ *(?:, *\\d+ *)*$";
+
+
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code RemarkCommand}
+ * and returns a {@code RemarkCommand} object for execution.
+ * @throws ParseException if the user input does not conform to the expected format.
+ */
+ public GenerateCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, GenerateCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_LEVEL);
+ // parse names
+ if (arePrefixesPresent(argMultimap, PREFIX_NAME)) {
+ return getGenerateCommandWithNames(argMultimap);
+ }
+ // parse indices
+ return getGenerateCommandWithIndices(argMultimap);
+ }
+
+ // return a GenerateCommand with a list of indices
+ private GenerateCommand getGenerateCommandWithIndices(ArgumentMultimap argMultimap) throws ParseException {
+ String indicesAsString = argMultimap.getPreamble();
+ String level = argMultimap.getValue(PREFIX_LEVEL).orElse("");
+ ArrayList indices = getIndices(indicesAsString);
+ ValidLevel validLevel = validateLevel(level);
+ return new GenerateCommand(indices, validLevel);
+ }
+
+ // return a GenerateCommand with a set of names
+ private GenerateCommand getGenerateCommandWithNames(ArgumentMultimap argMultimap) throws ParseException {
+ Set nameSet;
+ try {
+ nameSet = ParserUtil.parseNames(argMultimap.getAllValues(PREFIX_NAME));
+ } catch (ParseException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GenerateCommand.MESSAGE_USAGE));
+ }
+ String level = argMultimap.getValue(PREFIX_LEVEL).orElse("");
+ ValidLevel validLevel = validateLevel(level);
+ return new GenerateCommand(nameSet, validLevel);
+ }
+
+ // validate and return ArrayList of indices if all indices are valid
+ private ArrayList getIndices(String indicesAsString) throws ParseException {
+ if (!indicesAsString.matches(INDEX_VALIDATION_REGEX)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ GenerateCommand.MESSAGE_USAGE + "\n\n" + MESSAGE_INCORRECT_INDEX_FORMAT));
+ }
+ try {
+ String[] indicesArr = indicesAsString.split(",");
+ return parseIndices(indicesArr);
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ GenerateCommand.MESSAGE_USAGE + "\n\n" + MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX), ive);
+ }
+ }
+
+ // parses array of Strings into array of Indexes
+ private ArrayList parseIndices(String[] indicesArr) throws ParseException {
+ ArrayList indices = new ArrayList<>();
+ for (String indexString : indicesArr) {
+ Index index = ParserUtil.parseIndex(indexString);
+ indices.add(index);
+ }
+ return indices;
+ }
+
+ // validate against ValidLevels and return the level if valid
+ private ValidLevel validateLevel(String level) throws ParseException {
+ if (level.isBlank()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ GenerateCommand.MESSAGE_USAGE + "\n\n" + MESSAGE_MISSING_LEVEL));
+ }
+ for (ValidLevel validLevel : ValidLevel.values()) {
+ if (validLevel.name().equalsIgnoreCase(level)) {
+ return validLevel;
+ }
+ }
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ GenerateCommand.MESSAGE_USAGE + "\n\n" + MESSAGE_INVALID_LEVEL));
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/gim/logic/parser/Parser.java
similarity index 72%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/gim/logic/parser/Parser.java
index d6551ad8e3f..a330f0bb479 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/gim/logic/parser/Parser.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import gim.logic.commands.Command;
+import gim.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
diff --git a/src/main/java/gim/logic/parser/ParserUtil.java b/src/main/java/gim/logic/parser/ParserUtil.java
new file mode 100644
index 00000000000..4c3ec90bfa2
--- /dev/null
+++ b/src/main/java/gim/logic/parser/ParserUtil.java
@@ -0,0 +1,133 @@
+package gim.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.format.DateTimeParseException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import gim.commons.core.index.Index;
+import gim.commons.util.StringUtil;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.date.Date;
+import gim.model.exercise.Name;
+import gim.model.exercise.Reps;
+import gim.model.exercise.Sets;
+import gim.model.exercise.Weight;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+
+ public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+
+ /**
+ * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
+ * trimmed.
+ *
+ * @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
+ */
+ public static Index parseIndex(String oneBasedIndex) throws ParseException {
+ String trimmedIndex = oneBasedIndex.trim();
+ if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+
+ /**
+ * Parses a {@code String name} into a {@code Name}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static Name parseName(String name) throws ParseException {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ if (!Name.isValidName(trimmedName)) {
+ throw new ParseException(Name.MESSAGE_CONSTRAINTS);
+ }
+ return new Name(trimmedName);
+ }
+
+ /**
+ * Parses a {@code String weight} into a {@code Weight}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code weight} is invalid.
+ */
+ public static Weight parseWeight(String weight) throws ParseException {
+ requireNonNull(weight);
+ String trimmedWeight = weight.trim();
+ if (!Weight.isValidWeight(trimmedWeight)) {
+ throw new ParseException(Weight.MESSAGE_CONSTRAINTS);
+ }
+ return new Weight(trimmedWeight);
+ }
+
+ /**
+ * Parses a {@code String reps} into an {@code Reps}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code reps} is invalid.
+ */
+ public static Reps parseRep(String reps) throws ParseException {
+ requireNonNull(reps);
+ String trimmedRep = reps.trim();
+ if (!Reps.isValidReps(trimmedRep)) {
+ throw new ParseException(Reps.MESSAGE_CONSTRAINTS);
+ }
+ return new Reps(trimmedRep);
+ }
+
+ /**
+ * Parses a {@code String sets} into an {@code Sets}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code sets} is invalid.
+ */
+ public static Sets parseSets(String sets) throws ParseException {
+ requireNonNull(sets);
+ String trimmedSets = sets.trim();
+ if (!Sets.isValidSets(trimmedSets)) {
+ throw new ParseException(Sets.MESSAGE_CONSTRAINTS);
+ }
+ return new Sets(trimmedSets);
+ }
+
+ /**
+ * Parses a {@code String date} into a {@code Date}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code date} is invalid.
+ */
+ public static Date parseDate(String date) throws ParseException {
+ // Remove beginning or trailing whitespaces
+ String trimmedDate = date.trim();
+ if (!Date.isValidDateByRegex(trimmedDate)) {
+ throw new ParseException(Date.MESSAGE_CONSTRAINTS_FORMAT);
+ }
+ Date parsedDate;
+ // Prevent non-existent dates that follow the format from being added
+ try {
+ parsedDate = new Date(trimmedDate);
+ } catch (DateTimeParseException | IllegalArgumentException e) {
+ throw new ParseException(Date.MESSAGE_CONSTRAINTS_INVALID);
+ }
+ return parsedDate;
+ }
+
+ /**
+ * Parses {@code Collection names} into a {@code Set}.
+ */
+ public static Set parseNames(Collection names) throws ParseException {
+ requireNonNull(names);
+ final Set nameSet = new HashSet<>();
+ for (String name : names) {
+ nameSet.add(parseName(name));
+ }
+ return nameSet;
+ }
+}
diff --git a/src/main/java/gim/logic/parser/PrCommandParser.java b/src/main/java/gim/logic/parser/PrCommandParser.java
new file mode 100644
index 00000000000..745b34ab698
--- /dev/null
+++ b/src/main/java/gim/logic/parser/PrCommandParser.java
@@ -0,0 +1,44 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.logic.parser.CliSyntax.PREFIX_ALL;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import gim.logic.commands.PrCommand;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.exercise.Name;
+
+/**
+ * Parses input arguments and creates a new PRCommand object
+ */
+public class PrCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the PRCommand
+ * and returns an PRCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public PrCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ALL);
+ if (arePrefixesPresent(argMultimap, PREFIX_ALL)) {
+ return new PrCommand(new HashSet<>());
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, PrCommand.MESSAGE_USAGE));
+ }
+ Set nameSet = ParserUtil.parseNames(argMultimap.getAllValues(PREFIX_NAME));
+ return new PrCommand(nameSet);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/gim/logic/parser/Prefix.java
similarity index 95%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/gim/logic/parser/Prefix.java
index c859d5fa5db..406f1d2768d 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/gim/logic/parser/Prefix.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package gim.logic.parser;
/**
* A prefix that marks the beginning of an argument in an arguments string.
diff --git a/src/main/java/gim/logic/parser/RangeCommandParser.java b/src/main/java/gim/logic/parser/RangeCommandParser.java
new file mode 100644
index 00000000000..91562184173
--- /dev/null
+++ b/src/main/java/gim/logic/parser/RangeCommandParser.java
@@ -0,0 +1,100 @@
+package gim.logic.parser;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static gim.logic.parser.CliSyntax.PREFIX_END_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_RANGE_VARIATION_TWO;
+import static gim.logic.parser.CliSyntax.PREFIX_START_DATE;
+
+import java.util.stream.Stream;
+
+import gim.logic.commands.RangeCommand;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.date.Date;
+import gim.model.exercise.DateWithinRangePredicate;
+
+/**
+ * Parses input arguments and creates a new RangeCommand object
+ */
+public class RangeCommandParser implements Parser {
+ private enum Variation { ONE, TWO }
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the RangeCommand
+ * and returns a RangeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public RangeCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_START_DATE, PREFIX_END_DATE, PREFIX_RANGE_VARIATION_TWO);
+ Variation variation = parseArguments(argMultimap);
+
+ if (variation.equals(Variation.TWO)) {
+ return getVariationTwo(argMultimap);
+ } else if (variation.equals(Variation.ONE)) {
+ return getVariationOne(argMultimap);
+ }
+
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RangeCommand.MESSAGE_USAGE));
+ }
+
+ private Variation parseArguments(ArgumentMultimap argMultimap) throws ParseException {
+ // invalid command format if user inputs all keywords or mixes the keywords
+ if (arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE, PREFIX_RANGE_VARIATION_TWO)
+ || arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_RANGE_VARIATION_TWO)
+ || arePrefixesPresent(argMultimap, PREFIX_END_DATE, PREFIX_RANGE_VARIATION_TWO)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RangeCommand.MESSAGE_USAGE));
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_RANGE_VARIATION_TWO)
+ && !arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE)) {
+ return Variation.TWO;
+ }
+ if (!arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RangeCommand.MESSAGE_USAGE));
+ }
+ return Variation.ONE;
+ }
+
+ private RangeCommand getVariationOne(ArgumentMultimap argMultimap) throws ParseException {
+ Date startDate;
+ Date endDate;
+ // basic version: the user inputs both start date with prefix start/ and end date with prefix end/
+ // both date inputs are compulsory for the basic version
+ try {
+ startDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_START_DATE).get());
+ endDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_END_DATE).get());
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RangeCommand.MESSAGE_USAGE));
+ }
+ return new RangeCommand(new DateWithinRangePredicate(startDate, endDate));
+ }
+
+ private RangeCommand getVariationTwo(ArgumentMultimap argMultimap) throws ParseException {
+ int days;
+ // advanced version: when the user does not input any of the dates, but only inputs an integer with prefix last/
+ try {
+ days = Integer.parseInt(argMultimap.getValue(PREFIX_RANGE_VARIATION_TWO).get());
+ } catch (NumberFormatException e) {
+ // Ensure that the argument is a number and not other characters
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ RangeCommand.MESSAGE_USAGE_TWO));
+ }
+
+ // Only accept non-negative integers and up to 5 digits for the number of days
+ if (days < 0 || days > 99999) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ RangeCommand.MESSAGE_USAGE_TWO));
+ }
+ Date today = new Date();
+ Date startDate = today.getPreviousDaysDate(days);
+ return new RangeCommand(new DateWithinRangePredicate(startDate, today), true);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/gim/logic/parser/exceptions/ParseException.java
similarity index 73%
rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
rename to src/main/java/gim/logic/parser/exceptions/ParseException.java
index 158a1a54c1c..c8bcd17393b 100644
--- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
+++ b/src/main/java/gim/logic/parser/exceptions/ParseException.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.parser.exceptions;
+package gim.logic.parser.exceptions;
-import seedu.address.commons.exceptions.IllegalValueException;
+import gim.commons.exceptions.IllegalValueException;
/**
* Represents a parse error encountered by a parser.
diff --git a/src/main/java/gim/model/ExerciseTracker.java b/src/main/java/gim/model/ExerciseTracker.java
new file mode 100644
index 00000000000..15e4357ac15
--- /dev/null
+++ b/src/main/java/gim/model/ExerciseTracker.java
@@ -0,0 +1,172 @@
+package gim.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.ExerciseList;
+import gim.model.exercise.Name;
+import javafx.collections.ObservableList;
+
+/**
+ * Wraps all data at the exercise-tracker level.
+ *
+ * Duplicates are allowed.
+ */
+public class ExerciseTracker implements ReadOnlyExerciseTracker {
+
+ private final ExerciseList exerciseList;
+ private ExerciseHashMap exerciseHashMap;
+
+ /**
+ * Creates an ExerciseTracker
+ */
+ public ExerciseTracker() {
+ exerciseList = new ExerciseList();
+ exerciseHashMap = new ExerciseHashMap();
+ }
+
+ /**
+ * Creates an ExerciseTracker using a given HashMap {@code ehm}
+ * @param ehm ExerciseHashMap
+ */
+ public ExerciseTracker(ExerciseHashMap ehm) {
+ exerciseList = new ExerciseList();
+ exerciseHashMap = ehm;
+ }
+
+ /**
+ * Creates an ExerciseTracker using the Exercises in the {@code toBeCopied}
+ */
+ public ExerciseTracker(ReadOnlyExerciseTracker toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// list overwrite operations
+
+ /**
+ * Replaces the contents of the ExerciseList with {@code exercises}.
+ * {@code exercises} can contain duplicate Exercises.
+ */
+ public void setExercises(List exercises) {
+ this.exerciseList.setExercises(exercises);
+ this.exerciseHashMap.setExercises(exercises);
+ }
+
+ /**
+ * Resets the existing data of this {@code ExerciseTracker} with {@code newData}.
+ */
+ public void resetData(ReadOnlyExerciseTracker newData) {
+ requireNonNull(newData);
+
+ setExercises(newData.getExerciseList());
+ }
+
+ //// exercise-level operations
+
+ /**
+ * Returns true if an Exercise with the same identity as {@code exercise} exists in the exercise tracker.
+ */
+ public boolean hasExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ return exerciseHashMap.contains(exercise);
+ }
+
+ /**
+ * Returns the Exercise with the highest weight, with Name {@code exercises}.
+ * @param exerciseName Name of exercise.
+ * @return Exercise containing the highest weight.
+ */
+ public Exercise getExercisePR(Name exerciseName) {
+ return exerciseHashMap.getExercisePR(exerciseName);
+ }
+
+ /**
+ * Returns all unique Exercises with their respective highest weights.
+ * @return ArrayList containing all Exercises with the highest weights.
+ */
+ public ArrayList getAllExercisePRs() {
+ return exerciseHashMap.getAllExercisePRs();
+ }
+
+ /**
+ * Adds an Exercise to the exercise tracker.
+ * The Exercise can already exist in the exercise tracker.
+ */
+ public Exercise addExercise(Exercise p) {
+ Exercise toAdd = exerciseHashMap.add(p);
+ exerciseList.add(toAdd);
+ return toAdd;
+ }
+
+ /**
+ * Removes {@code key} from this {@code ExerciseTracker}.
+ * {@code key} must exist in the exercise tracker.
+ */
+ public void removeExercise(Exercise key) {
+ exerciseHashMap.remove(key);
+ exerciseList.remove(key);
+ }
+
+ //// util methods
+
+ @Override
+ public String toString() {
+ return exerciseList.asDisplayedList().size() + " exercises";
+ // TODO: refine later
+ }
+
+ @Override
+ public ObservableList getExerciseList() {
+ return exerciseList.asDisplayedList();
+ }
+
+ /**
+ * Returns a duplicated displayed list of type ObservableList in ExerciseList.
+ * */
+ public ObservableList getDuplicatedDisplayedList() {
+ return exerciseList.asDuplicatedDisplayedList();
+ }
+
+ /**
+ * Sorts the displayed exercise list according to chronological order of exercise dates.
+ */
+ public void sortDisplayedList() {
+ exerciseList.sortDisplayedList();
+ }
+
+ /**
+ * Resets the displayed exercise list to the default ordering.
+ */
+ public void resetDisplayedList() {
+ exerciseList.resetDisplayedList();
+ }
+
+ /**
+ * Filters the displayed exercise list.
+ */
+ public void filterDisplayedList(ObservableList filteredList) {
+ exerciseList.filterDisplayedList(filteredList);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ExerciseTracker // instanceof handles nulls
+ && exerciseList.equals(((ExerciseTracker) other).exerciseList));
+ }
+
+ @Override
+ public int hashCode() {
+ return exerciseList.hashCode();
+ }
+
+ public ExerciseHashMap getExerciseHashMap() {
+ return exerciseHashMap;
+ }
+
+}
diff --git a/src/main/java/gim/model/Model.java b/src/main/java/gim/model/Model.java
new file mode 100644
index 00000000000..679c7522d6c
--- /dev/null
+++ b/src/main/java/gim/model/Model.java
@@ -0,0 +1,123 @@
+package gim.model;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+import gim.commons.core.GuiSettings;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.Name;
+import javafx.collections.ObservableList;
+
+/**
+ * The API of the Model component.
+ */
+public interface Model {
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_EXERCISES = unused -> true;
+
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' exercise tracker file path.
+ */
+ Path getExerciseTrackerFilePath();
+
+ /**
+ * Sets the user prefs' exercise tracker file path.
+ */
+ void setExerciseTrackerFilePath(Path exerciseTrackerFilePath);
+
+ /**
+ * Replaces exercise tracker data with the data in {@code exerciseTracker}.
+ */
+ void setExerciseTracker(ReadOnlyExerciseTracker exerciseTracker);
+
+ /** Returns the ExerciseTracker */
+ ReadOnlyExerciseTracker getExerciseTracker();
+
+ /**
+ * Returns true if an exercise with the same identity as {@code exercise} exists in the exercise tracker.
+ */
+ boolean hasExercise(Exercise exercise);
+
+ /**
+ * Deletes the given exercise.
+ * The exercise must exist in the exercise tracker.
+ */
+ void deleteExercise(Exercise target);
+
+ /**
+ * Returns the Exercise with the highest weight, with Name {@code exercises}.
+ * @param exerciseName Name of exercise.
+ * @return Exercise containing the highest weight.
+ */
+ Exercise getExercisePR(Name exerciseName);
+
+ /**
+ * Returns all unique Exercises with their respective highest weights.
+ * @return ArrayList containing all Exercises with the highest weights.
+ */
+ ArrayList getAllExercisePRs();
+
+ /**
+ * Adds the given exercise.
+ * {@code exercise} can already exist in the exercise tracker.
+ */
+ Exercise addExercise(Exercise exercise);
+
+ /**
+ * Returns a view of the filtered exercise list.
+ */
+ ObservableList getFilteredExerciseList();
+
+ /**
+ * Returns a copy of the hashmap of Exercises stored.
+ */
+ ExerciseHashMap getExerciseHashMap();
+
+ /**
+ * Resets the filtered exercise list to default order and updates filter of filtered list
+ * by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredExerciseList(Predicate predicate);
+
+ /**
+ * Filters the filtered exercise list by updating the filter of filtered list
+ * by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void filterFilteredExerciseList(Predicate predicate);
+
+ /**
+ * Sorts the filtered exercise list and updates filter of filtered list
+ * by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void sortFilteredExerciseList(Predicate predicate);
+
+ /**
+ * Resets the displayed exercise list to the default ordering.
+ */
+ void resetDisplayedList();
+}
diff --git a/src/main/java/gim/model/ModelManager.java b/src/main/java/gim/model/ModelManager.java
new file mode 100644
index 00000000000..3b97d89a3b3
--- /dev/null
+++ b/src/main/java/gim/model/ModelManager.java
@@ -0,0 +1,183 @@
+package gim.model;
+
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+
+import gim.commons.core.GuiSettings;
+import gim.commons.core.LogsCenter;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.Name;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+
+/**
+ * Represents the in-memory model of the exercise tracker data.
+ */
+public class ModelManager implements Model {
+ private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+
+ private final ExerciseTracker exerciseTracker;
+ private final UserPrefs userPrefs;
+ private final FilteredList filteredExercises;
+
+ /**
+ * Initializes a ModelManager with the given exerciseTracker and userPrefs.
+ */
+ public ModelManager(ReadOnlyExerciseTracker exerciseTracker, ReadOnlyUserPrefs userPrefs) {
+ requireAllNonNull(exerciseTracker, userPrefs);
+
+ logger.fine("Initializing with exercise tracker: " + exerciseTracker + " and user prefs " + userPrefs);
+
+ this.exerciseTracker = new ExerciseTracker(exerciseTracker);
+ this.userPrefs = new UserPrefs(userPrefs);
+ filteredExercises = new FilteredList<>(this.exerciseTracker.getExerciseList());
+ }
+
+ public ModelManager() {
+ this(new ExerciseTracker(), new UserPrefs());
+ }
+
+ //=========== UserPrefs ==================================================================================
+
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return userPrefs.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ requireNonNull(guiSettings);
+ userPrefs.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public Path getExerciseTrackerFilePath() {
+ return userPrefs.getExerciseTrackerFilePath();
+ }
+
+ @Override
+ public void setExerciseTrackerFilePath(Path exerciseTrackerFilePath) {
+ requireNonNull(exerciseTrackerFilePath);
+ userPrefs.setExerciseTrackerFilePath(exerciseTrackerFilePath);
+ }
+
+ //=========== ExerciseTracker ================================================================================
+
+ @Override
+ public void setExerciseTracker(ReadOnlyExerciseTracker exerciseTracker) {
+ this.exerciseTracker.resetData(exerciseTracker);
+ }
+
+ @Override
+ public ReadOnlyExerciseTracker getExerciseTracker() {
+ return exerciseTracker;
+ }
+
+ @Override
+ public boolean hasExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ return exerciseTracker.hasExercise(exercise);
+ }
+
+ @Override
+ public void deleteExercise(Exercise target) {
+ exerciseTracker.removeExercise(target);
+ }
+
+ @Override
+ public Exercise getExercisePR(Name exerciseName) {
+ return exerciseTracker.getExercisePR(exerciseName);
+ }
+
+ @Override
+ public ArrayList getAllExercisePRs() {
+ return exerciseTracker.getAllExercisePRs();
+ }
+
+ @Override
+ public Exercise addExercise(Exercise exercise) {
+ Exercise added = exerciseTracker.addExercise(exercise);
+ updateFilteredExerciseList(PREDICATE_SHOW_ALL_EXERCISES);
+ return added;
+ }
+
+ //=========== Filtered Exercise List Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Exercise} backed by the internal list of
+ * {@code versionedExerciseTracker}
+ */
+ @Override
+ public ObservableList getFilteredExerciseList() {
+ return filteredExercises;
+ }
+
+ @Override
+ public void updateFilteredExerciseList(Predicate predicate) {
+ requireNonNull(predicate);
+ resetDisplayedList();
+ filteredExercises.setPredicate(predicate);
+ }
+
+ @Override
+ public void filterFilteredExerciseList(Predicate predicate) {
+ requireNonNull(predicate);
+ FilteredList filteredList = new FilteredList<>(this.exerciseTracker.getDuplicatedDisplayedList());
+ filteredList.setPredicate(predicate);
+ exerciseTracker.filterDisplayedList(filteredList);
+ }
+
+ @Override
+ public void sortFilteredExerciseList(Predicate predicate) {
+ requireNonNull(predicate);
+ exerciseTracker.sortDisplayedList();
+ FilteredList filteredList = new FilteredList<>(this.exerciseTracker.getDuplicatedDisplayedList());
+ filteredList.setPredicate(predicate);
+ exerciseTracker.filterDisplayedList(filteredList);
+ }
+
+ @Override
+ public void resetDisplayedList() {
+ exerciseTracker.resetDisplayedList();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // short circuit if same object
+ if (obj == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(obj instanceof ModelManager)) {
+ return false;
+ }
+
+ // state check
+ ModelManager other = (ModelManager) obj;
+ return exerciseTracker.equals(other.exerciseTracker)
+ && userPrefs.equals(other.userPrefs)
+ && filteredExercises.equals(other.filteredExercises);
+ }
+
+ public ExerciseHashMap getExerciseHashMap() {
+ return exerciseTracker.getExerciseHashMap();
+ }
+}
diff --git a/src/main/java/gim/model/ReadOnlyExerciseTracker.java b/src/main/java/gim/model/ReadOnlyExerciseTracker.java
new file mode 100644
index 00000000000..90bfbe4bd5f
--- /dev/null
+++ b/src/main/java/gim/model/ReadOnlyExerciseTracker.java
@@ -0,0 +1,17 @@
+package gim.model;
+
+import gim.model.exercise.Exercise;
+import javafx.collections.ObservableList;
+
+/**
+ * Unmodifiable view of an exercise tracker
+ */
+public interface ReadOnlyExerciseTracker {
+
+ /**
+ * Returns an unmodifiable view of the exercises list.
+ * This list may contain duplicate exercises.
+ */
+ ObservableList getExerciseList();
+
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/gim/model/ReadOnlyUserPrefs.java
similarity index 57%
rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
rename to src/main/java/gim/model/ReadOnlyUserPrefs.java
index befd58a4c73..ce572dc5972 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/gim/model/ReadOnlyUserPrefs.java
@@ -1,8 +1,8 @@
-package seedu.address.model;
+package gim.model;
import java.nio.file.Path;
-import seedu.address.commons.core.GuiSettings;
+import gim.commons.core.GuiSettings;
/**
* Unmodifiable view of user prefs.
@@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs {
GuiSettings getGuiSettings();
- Path getAddressBookFilePath();
+ Path getExerciseTrackerFilePath();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/gim/model/UserPrefs.java
similarity index 69%
rename from src/main/java/seedu/address/model/UserPrefs.java
rename to src/main/java/gim/model/UserPrefs.java
index 25a5fd6eab9..ab5f67a51fa 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/gim/model/UserPrefs.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package gim.model;
import static java.util.Objects.requireNonNull;
@@ -6,7 +6,7 @@
import java.nio.file.Paths;
import java.util.Objects;
-import seedu.address.commons.core.GuiSettings;
+import gim.commons.core.GuiSettings;
/**
* Represents User's preferences.
@@ -14,7 +14,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path exerciseTrackerFilePath = Paths.get("data" , "exercisetracker.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) {
public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
- setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setExerciseTrackerFilePath(newUserPrefs.getExerciseTrackerFilePath());
}
public GuiSettings getGuiSettings() {
@@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) {
this.guiSettings = guiSettings;
}
- public Path getAddressBookFilePath() {
- return addressBookFilePath;
+ public Path getExerciseTrackerFilePath() {
+ return exerciseTrackerFilePath;
}
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- this.addressBookFilePath = addressBookFilePath;
+ public void setExerciseTrackerFilePath(Path exerciseTrackerFilePath) {
+ requireNonNull(exerciseTrackerFilePath);
+ this.exerciseTrackerFilePath = exerciseTrackerFilePath;
}
@Override
@@ -68,19 +68,19 @@ public boolean equals(Object other) {
UserPrefs o = (UserPrefs) other;
return guiSettings.equals(o.guiSettings)
- && addressBookFilePath.equals(o.addressBookFilePath);
+ && exerciseTrackerFilePath.equals(o.exerciseTrackerFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, exerciseTrackerFilePath);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal data file location : " + exerciseTrackerFilePath);
return sb.toString();
}
diff --git a/src/main/java/gim/model/date/Date.java b/src/main/java/gim/model/date/Date.java
new file mode 100644
index 00000000000..08c3c742a3c
--- /dev/null
+++ b/src/main/java/gim/model/date/Date.java
@@ -0,0 +1,139 @@
+package gim.model.date;
+
+import static gim.commons.util.AppUtil.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.ResolverStyle;
+
+/**
+ * Represents a Date of an Exercise in Gim.
+ * Guarantees: immutable; name is valid as declared in {@link #isValidDateByRegex(String)}
+ */
+public class Date {
+ public static final String DEFAULT_DATE_PATTERN = "dd/MM/uuuu";
+ public static final String MESSAGE_CONSTRAINTS_INVALID = "Date is non-existent. Please input a valid date.";
+ public static final String MESSAGE_CONSTRAINTS_FORMAT = "Date input format is invalid." + "\n"
+ + "Accepted Formats: \n"
+ + "DAY/MONTH/YEAR or YEAR/MONTH/DAY\n"
+ + "DAY-MONTH-YEAR or YEAR-MONTH-DAY\n"
+ + "DAY MONTH YEAR or YEAR MONTH DAY\n\n"
+ + "DAY: 1 or 2 digits allowed\n"
+ + "MONTH: 1 or 2 digits allowed\n"
+ + "YEAR: 4 digits allowed\n";
+
+ public static final FormatterList FORMATTER_LIST = FormatterList.getFormatterList();
+ public static final RegexList REGEX_LIST = RegexList.getRegexList();
+ /**
+ * The formatter uses uuuu instead of yyyy to ensure a stricter formatting restriction for the year.
+ * The strict ResolverStyle is used to prevent non-existent dates from being added.
+ */
+ private static final DateTimeFormatter defaultFormatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)
+ .withResolverStyle(ResolverStyle.STRICT);
+
+ public final LocalDate date;
+
+ /**
+ * Constructs a {@code Date}.
+ *
+ * @param date A date with valid format.
+ */
+ public Date(String date) {
+ requireNonNull(date);
+ String standardDate = standardizeDate(date);
+ checkArgument(isValidDateByRegex(standardDate), MESSAGE_CONSTRAINTS_FORMAT);
+ LocalDate parsedDate = FORMATTER_LIST.validateDateWithFormatters(standardDate);
+ if (parsedDate == null) {
+ // may change to DateTimeParseException
+ throw new IllegalArgumentException(MESSAGE_CONSTRAINTS_FORMAT);
+ }
+ // The year 0000 does not exist in the Gregorian calendar.
+ if (parsedDate.getYear() == 0) {
+ throw new IllegalArgumentException(MESSAGE_CONSTRAINTS_INVALID);
+ }
+ this.date = parsedDate;
+ }
+
+ /**
+ * Constructs a Date object that is initialized to today's date.
+ *
+ * This is used when the user did not input any date.
+ */
+ public Date() {
+ LocalDate today = LocalDate.now();
+ String todayText = today.format(defaultFormatter);
+ this.date = LocalDate.parse(todayText, defaultFormatter);;
+ }
+
+ /**
+ * Returns a string that replaces all whitespaces within a date string to a single whitespace.
+ * @param date date string
+ * @return date string that has been standardized
+ */
+ private String standardizeDate(String date) {
+ // Remove trailing whitespaces or multiple whitespaces between chars to a single whitespace
+ return date.trim().replaceAll("\\s+", " ");
+ }
+
+ /**
+ * Returns true if a given string is a valid date.
+ */
+ public static boolean isValidDateByRegex(String test) {
+ return REGEX_LIST.isValidDateByRegex(test);
+ }
+
+ /**
+ * Obtains the day of week.
+ * Example: 21/08/2022 is a Friday
+ * @return string representing the day of week
+ */
+ public String getDayString() {
+ // Convert "SATURDAY" to "Saturday"
+ String dayString = date.getDayOfWeek().toString();
+ dayString = dayString.charAt(0) + dayString.substring(1).toLowerCase();
+ return dayString;
+ }
+
+ /**
+ * Checks whether this Date instance is within a given range.
+ * Note that this method includes both start and end parameters.
+ * @param start starting date (inclusive)
+ * @param end ending date (inclusive)
+ * @return true or false depending on the check
+ */
+ public boolean checkWithinRange(Date start, Date end) {
+ return !this.date.isBefore(start.date) && !this.date.isAfter(end.date);
+ }
+
+ /**
+ * Computes and returns the Date that is {@code days} days before this Date.
+ * @param days the number of days
+ * @return {@code Date} with date value of {@code days} days before the current date value.
+ */
+ public Date getPreviousDaysDate(int days) {
+ String dateString = this.date.minusDays(days).format(defaultFormatter);
+ return new Date(dateString);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Date // instanceof handles nulls
+ && date.equals(((Date) other).date)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return date.hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ @Override
+ public String toString() {
+ return date.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN));
+ }
+
+}
diff --git a/src/main/java/gim/model/date/FormatterList.java b/src/main/java/gim/model/date/FormatterList.java
new file mode 100644
index 00000000000..0649ada0b03
--- /dev/null
+++ b/src/main/java/gim/model/date/FormatterList.java
@@ -0,0 +1,91 @@
+package gim.model.date;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Singleton class that contains the formatters needed to parse dates.
+ */
+public class FormatterList {
+ private static FormatterList formatterListInstance = null;
+ private static List formatterList = null;
+
+ private static final String[] validDateFormats = {
+ "uuuu/MM/dd", "uuuu/M/dd", "uuuu/MM/d", "uuuu/M/d",
+ "dd/MM/uuuu", "dd/M/uuuu", "d/MM/uuuu", "d/M/uuuu",
+
+ "dd-MM-uuuu", "dd-M-uuuu", "d-MM-uuuu", "d-M-uuuu",
+ "uuuu-MM-dd", "uuuu-M-dd", "uuuu-MM-d", "uuuu-M-d",
+
+ "dd MM uuuu", "dd M uuuu", "d MM uuuu", "d M uuuu",
+ "uuuu MM dd", "uuuu M dd", "uuuu MM d", "uuuu M d",
+
+ };
+
+ private FormatterList() {
+ formatterList = Arrays.stream(validDateFormats)
+ .map(pattern -> DateTimeFormatter.ofPattern(pattern).withResolverStyle(ResolverStyle.STRICT))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Factory method to obtain singleton instance of FormatterList.
+ * @return FormatterList object
+ */
+ public static FormatterList getFormatterList() {
+ if (formatterListInstance == null) {
+ formatterListInstance = new FormatterList();
+ }
+ return formatterListInstance;
+ }
+
+ /**
+ * If date string does not match any formatter, return null.
+ * @param date date string
+ * @return LocalDate object that has been formatted using a valid formatter
+ */
+ public LocalDate validateDateWithFormatters(String date) {
+ for (DateTimeFormatter formatter : formatterList) {
+ LocalDate parsedDate = parseDateByFormatter(date, formatter);
+ if (parsedDate != null) {
+ return parsedDate;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parses the date string using the specified formatter.
+ *
+ * LocalDate.parse will throw DateTimeParseException if the date string does not conform to the
+ * date format of the formatter. In this case, null is returned to signify that the date string
+ * does not match the formatter.
+ *
+ * If there is a valid match between the date and the formatter, a LocalDate object created from
+ * the date string parsed using the formatter will be returned.
+ *
+ * Justification for usage of try-catch block:
+ *
+ * The try-catch block in this method is necessary as the Java API for LocalDate does not provide
+ * a method that returns a boolean to check for the validity of the date, but the method parse
+ * instead returns an exception which makes if-else statements to work for this case.
+ *
+ * @param date string representation of the date
+ * @param formatter DateTimeFormatter object
+ * @return null if date does not conform to formatter and LocalDate if it does
+ */
+ private LocalDate parseDateByFormatter(String date, DateTimeFormatter formatter) {
+ LocalDate parsedDate;
+ try {
+ parsedDate = LocalDate.parse(date, formatter);
+ } catch (DateTimeParseException e) {
+ return null;
+ }
+ return parsedDate;
+ }
+}
diff --git a/src/main/java/gim/model/date/RegexList.java b/src/main/java/gim/model/date/RegexList.java
new file mode 100644
index 00000000000..2dac81c0397
--- /dev/null
+++ b/src/main/java/gim/model/date/RegexList.java
@@ -0,0 +1,74 @@
+package gim.model.date;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Singleton class that contains the valid regular expressions (regex) needed to validate dates.
+ */
+public class RegexList {
+ private static RegexList regexListInstance = null;
+ private static List regexList = null;
+
+ private RegexList() {
+ regexList = new ArrayList<>();
+ /*
+ Examples (DAY/MONTH/YEAR):
+ 01/01/2022, 01/1/2022, 1/01/2022, 1/1/2022
+ */
+ regexList.add("^(0?[0-9]||[1-2][0-9]||3[0-1])/(0?[0-9]||1[0-2])/([0-9][0-9])[0-9][0-9]$");
+
+ /*
+ Examples (YEAR/MONTH/DAY):
+ 2022/01/01, 2022/1/01, 2022/01/1, 2022/1/1
+ */
+ regexList.add("^([0-9][0-9])[0-9][0-9]/(0?[0-9]||1[0-2])/(0?[0-9]||[1-2][0-9]||3[0-1])$");
+ /*
+ Examples (DAY-MONTH-YEAR):
+ 01-01-2022, 01-1-2022, 1-01-2022, 1-1-2022
+ */
+ regexList.add("^(0?[0-9]||[1-2][0-9]||3[0-1]).*\\-.*(0?[0-9]||1[0-2]).*\\-.*([0-9][0-9])[0-9][0-9]$");
+
+ /*
+ Examples (YEAR-MONTH-DAY):
+ 2022-01-01, 2022-1-01, 2022-01-1, 2022-1-1
+ */
+ regexList.add("^([0-9][0-9])[0-9][0-9].*\\-.*(0?[0-9]||1[0-2]).*\\-.*(0?[0-9]||[1-2][0-9]||3[0-1])$");
+ /*
+ Examples (DAY MONTH YEAR):
+ 01 01 2022, 01 1 2022, 1 01 2022, 1 1 2022
+ */
+ regexList.add("^(0?[0-9]||[1-2][0-9]||3[0-1])\\s+(0?[0-9]||1[0-2])\\s+([0-9][0-9])[0-9][0-9]$");
+
+ /*
+ Examples (YEAR MONTH DAY):
+ 2022 01 01, 2022 1 01, 2022 01 1, 2022 1 1
+
+ Note: multiple whitespaces have been substituted to a single whitespace in ParserUtil::parseDate
+ */
+ regexList.add("^([0-9][0-9])[0-9][0-9]\\s+(0?[0-9]||1[0-2])\\s+(0?[0-9]||[1-2][0-9]||3[0-1])$");
+ }
+
+ /**
+ * Factory method to obtain singleton instance of RegexList.
+ * @return RegexList object
+ */
+ public static RegexList getRegexList() {
+ if (regexListInstance == null) {
+ regexListInstance = new RegexList();
+ }
+ return regexListInstance;
+ }
+
+ /**
+ * Returns true if a given string is a valid date according to the regex list.
+ */
+ public boolean isValidDateByRegex(String test) {
+ for (String regex : regexList) {
+ if (test.matches(regex)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/gim/model/exercise/DateWithinRangePredicate.java b/src/main/java/gim/model/exercise/DateWithinRangePredicate.java
new file mode 100644
index 00000000000..4ae25ca7ece
--- /dev/null
+++ b/src/main/java/gim/model/exercise/DateWithinRangePredicate.java
@@ -0,0 +1,48 @@
+package gim.model.exercise;
+
+import static java.time.temporal.ChronoUnit.DAYS;
+
+import java.util.function.Predicate;
+
+import gim.model.date.Date;
+
+/**
+ * Tests that a {@code Exercise}'s {@code Date} is between the
+ * startDate (inclusive) and endDate (inclusive).
+ */
+public class DateWithinRangePredicate implements Predicate {
+ private final Date startDate;
+ private final Date endDate;
+
+ /**
+ * Constructor for DateWithinRangePredicate
+ * @param start start date (inclusive)
+ * @param end end date (inclusive)
+ */
+ public DateWithinRangePredicate(Date start, Date end) {
+ this.startDate = start;
+ this.endDate = end;
+ }
+
+ /**
+ * Computes the number of logical calendar days between startDate and endDate.
+ * @return number of days
+ */
+ public int getRangeSizeInDays() {
+ return (int) DAYS.between(startDate.date, endDate.date);
+ }
+
+ @Override
+ public boolean test(Exercise exercise) {
+ return exercise.getDate().checkWithinRange(startDate, endDate);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DateWithinRangePredicate // instanceof handles nulls
+ && ((DateWithinRangePredicate) other).startDate.equals(startDate)
+ && ((DateWithinRangePredicate) other).endDate.equals(endDate));
+ }
+
+}
diff --git a/src/main/java/gim/model/exercise/Exercise.java b/src/main/java/gim/model/exercise/Exercise.java
new file mode 100644
index 00000000000..7501b28b972
--- /dev/null
+++ b/src/main/java/gim/model/exercise/Exercise.java
@@ -0,0 +1,133 @@
+package gim.model.exercise;
+
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Objects;
+
+import gim.model.date.Date;
+
+/**
+ * Represents an Exercise in the exercise tracker.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Exercise implements Comparable {
+
+ private final Name name;
+ private final Weight weight;
+ private final Sets sets;
+ private final Reps reps;
+ private final Date date;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Exercise(Name name, Weight weight, Sets sets, Reps reps, Date date) {
+ requireAllNonNull(name, weight, sets, reps, date);
+ this.name = name;
+ this.weight = weight;
+ this.sets = sets;
+ this.reps = reps;
+ this.date = date;
+ }
+
+ public Name getName() {
+ return name;
+ }
+
+ public Weight getWeight() {
+ return weight;
+ }
+
+ public Sets getSets() {
+ return sets;
+ }
+
+ public Reps getReps() {
+ return reps;
+ }
+
+ /**
+ * Returns an immutable Date object, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Date getDate() {
+ return date;
+ }
+
+ /**
+ * Returns the String representation of the date object.
+ * @return date String
+ */
+ public String getDateString() {
+ return date.toString();
+ }
+
+ /**
+ * Returns true if both Exercises have the same Name.
+ * This defines a weaker notion of equality between two Exercises.
+ *
+ * Two Names are equal if, after removal of whitespaces and being set to lowercase, their String values are equal.
+ */
+ public boolean isSameExercise(Exercise otherExercise) {
+ if (otherExercise == this) {
+ return true;
+ }
+
+ return otherExercise != null
+ && otherExercise.getName().equals(getName());
+ }
+
+ /**
+ * Returns true if both Exercises have the same identity and data fields.
+ * This defines a stronger notion of equality between two exercises.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Exercise)) {
+ return false;
+ }
+
+ Exercise otherExercise = (Exercise) other;
+ return otherExercise.getName().equals(getName())
+ && otherExercise.getWeight().equals(getWeight())
+ && otherExercise.getSets().equals(getSets())
+ && otherExercise.getReps().equals(getReps())
+ && otherExercise.getDate().equals(getDate());
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(name, weight, sets, reps, date);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(getName())
+ .append("; Weight: ")
+ .append(getWeight())
+ .append("kg")
+ .append("; Sets: ")
+ .append(getSets())
+ .append("; Reps: ")
+ .append(getReps());
+
+ Date date = getDate();
+ if (!(date == null)) {
+ builder.append("; Date: ");
+ builder.append(date);
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public int compareTo(Exercise o) {
+ // Does not handle if both same weight yet
+ return this.getWeight().compareTo(o.getWeight());
+ }
+}
diff --git a/src/main/java/gim/model/exercise/ExerciseHashMap.java b/src/main/java/gim/model/exercise/ExerciseHashMap.java
new file mode 100644
index 00000000000..37015e5f527
--- /dev/null
+++ b/src/main/java/gim/model/exercise/ExerciseHashMap.java
@@ -0,0 +1,232 @@
+package gim.model.exercise;
+
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import gim.commons.core.LogsCenter;
+import gim.model.exercise.exceptions.ExerciseNotFoundException;
+import gim.ui.Observer;
+
+/**
+ * An Exercise HashMap to categorise Exercises, with the same Name, together. For instance, if a user adds an
+ * Exercise with Name.toString() == 'Squat' and another Exercise with Name.toString() == 'squat', they will be put
+ * into the exerciseHashMap under the same key 'Squat', where 'Squat' is a Name object.
+ *
+ * Two Names are equal if, after removal of whitespaces and being set to lowercase, their String values are equal.
+ */
+public class ExerciseHashMap {
+
+ private static final Logger logger = LogsCenter.getLogger(ExerciseHashMap.class);
+ private final HashMap> exerciseHashMap;
+
+ private final ArrayList observerArrayList;
+
+ /**
+ * Constructs a {@code ExerciseHashMap}.
+ */
+ public ExerciseHashMap() {
+ logger.info("Initialising new Exercise Hashmap");
+ exerciseHashMap = new HashMap<>();
+ observerArrayList = new ArrayList();
+ }
+
+ /**
+ * Constructs a {@code ExerciseHashMap} with a given ArrayList of Observers
+ * @param arrL
+ */
+ public ExerciseHashMap(ArrayList arrL) {
+ assert arrL != null;
+ logger.info("Initialising new Exercise Hashmap with given ArrayList");
+ this.exerciseHashMap = new HashMap<>();
+ this.observerArrayList = arrL;
+ }
+
+ /**
+ * Notifies observers who have 'subscribed' whenever there are changes in the state of the ExerciseHashMap
+ */
+ public void notifyObservers() {
+ for (Observer o: observerArrayList) {
+ o.update();
+ }
+ }
+
+ /**
+ * Allows an Observer object to 'subscribe' to changes in the state of the ExerciseHashMap.
+ * @param o Observer object
+ */
+ public void addUi(Observer o) {
+ observerArrayList.add(o);
+ }
+
+ /**
+ * Returns true if Exercise {@code toCheck} has a Name equal to a Name in the exerciseHashMap key-set.
+ */
+ public boolean contains(Exercise toCheck) {
+ requireNonNull(toCheck);
+ return exerciseHashMap.containsKey(toCheck.getName());
+ }
+
+ /**
+ * Returns true if Name {@code exerciseNameToCheck} is equal to a Name in the exerciseHashMap key-set.
+ */
+ public boolean containsName(Name exerciseNameToCheck) {
+ requireNonNull(exerciseNameToCheck);
+ return exerciseHashMap.containsKey(exerciseNameToCheck);
+ }
+
+ /**
+ * Returns the Exercise with the highest weight, with Name {@code exercises}.
+ * @param exerciseName Name of exercise.
+ * @return Exercise containing the highest weight.
+ */
+ public Exercise getExercisePR(Name exerciseName) {
+ if (!containsName(exerciseName)) {
+ return null;
+ }
+ return Collections.max(exerciseHashMap.get(exerciseName));
+ }
+
+ /**
+ * Returns all unique Exercises with their respective highest weights.
+ * @return ArrayList containing all Exercises with the highest weights.
+ */
+ public ArrayList getAllExercisePRs() {
+ ArrayList returnList = new ArrayList<>();
+ for (Name name : exerciseHashMap.keySet()) {
+ returnList.add(getExercisePR(name));
+ }
+ return returnList;
+ }
+
+ /**
+ * Adds an Exercise to the exerciseHashMap.
+ * If the Exercise already exists, i.e. two Exercises with the same Name, categorise them together. Returns the
+ * Exercise added to the exerciseHashMap.
+ */
+ public Exercise add(Exercise toAdd) {
+ requireNonNull(toAdd);
+
+ Name toStoreName = toAdd.getName();
+ if (!contains(toAdd)) {
+ exerciseHashMap.put(toAdd.getName(), new ArrayList<>()); // Initialise key with empty ArrayList
+ } else {
+ toStoreName = getHashmapKey(toStoreName);
+ }
+ toAdd = new Exercise(toStoreName, toAdd.getWeight(), toAdd.getSets(), toAdd.getReps(), toAdd.getDate());
+ exerciseHashMap.get(toStoreName).add(toAdd); // add Exercise to arraylist
+ this.notifyObservers();
+ return toAdd;
+ }
+
+ /**
+ * Removes the equivalent Exercise from the exerciseHashMap.
+ * The Exercise must exist in the exerciseHashMap.
+ */
+ public void remove(Exercise toRemove) {
+ requireNonNull(toRemove);
+ if (!contains(toRemove)) {
+ throw new ExerciseNotFoundException();
+ } else {
+ exerciseHashMap.get(toRemove.getName()).remove(toRemove);
+ if (exerciseHashMap.get(toRemove.getName()).isEmpty()) { // Remove Exercise from hashmap
+ exerciseHashMap.remove(toRemove.getName()); // If no more Exercises in key's ArrayList, delete key
+ }
+ this.notifyObservers();
+ }
+ }
+
+ /**
+ * Replaces the contents of this List and exerciseHashMap with {@code exercises}.
+ */
+ public void setExercises(List exercises) {
+ requireAllNonNull(exercises);
+ for (Exercise exercise : exercises) {
+ Name toStoreName = exercise.getName();
+ if (!contains(exercise)) {
+ exerciseHashMap.put(exercise.getName(),
+ new ArrayList<>()); // Initialise key with empty ArrayList
+ } else {
+ toStoreName = getHashmapKey(toStoreName);
+ }
+ exercise = new Exercise(toStoreName, exercise.getWeight(), exercise.getSets(),
+ exercise.getReps(), exercise.getDate());
+ exerciseHashMap.get(toStoreName).add(exercise); // add Exercise to arraylist
+ }
+ this.notifyObservers();
+ }
+
+ /**
+ * Retrieve Name of first Exercise instance (Key of exerciseHashMap)
+ * @param toStoreName Name of current Exercise
+ * @return Returns the Name of the first Exercise instance
+ */
+ public Name getHashmapKey(Name toStoreName) {
+ Name returnName = toStoreName;
+ for (Name key: exerciseHashMap.keySet()) {
+ if (toStoreName.equals(key)) {
+ returnName = key;
+ break;
+ }
+ }
+ return returnName;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ExerciseHashMap // instanceof handles nulls
+ && exerciseHashMap.equals(((ExerciseHashMap) other).exerciseHashMap));
+ }
+
+ @Override
+ public int hashCode() {
+ return exerciseHashMap.hashCode();
+ }
+
+
+ /**
+ * Returns an Alphabetically sorted ArrayList of all key values in ExerciseHashMap
+ * @return Returns ArrayList of String
+ */
+ public ArrayList getAllKeys() {
+ Set keySet = exerciseHashMap.keySet();
+ ArrayList toReturn = new ArrayList<>();
+ if (keySet.isEmpty()) {
+ return toReturn;
+ }
+ for (Name keyName: keySet) {
+ toReturn.add(keyName.toString());
+ }
+ Collections.sort(toReturn);
+ return toReturn;
+ }
+
+ /**
+ * Returns the hashmap but all keys and values associated to keys are cleared.
+ * @return ExerciseHashMap
+ */
+ public ExerciseHashMap clearExerciseHashMap() {
+ this.exerciseHashMap.clear();
+ return this;
+ }
+
+ /**
+ * Returns the number of elements in a hashmap
+ * @return integer value
+ */
+ public int numOfValues() {
+ int result = 0;
+ for (ArrayList arrL : exerciseHashMap.values()) {
+ result += arrL.size();
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/gim/model/exercise/ExerciseKeys.java b/src/main/java/gim/model/exercise/ExerciseKeys.java
new file mode 100644
index 00000000000..04a419533c4
--- /dev/null
+++ b/src/main/java/gim/model/exercise/ExerciseKeys.java
@@ -0,0 +1,39 @@
+package gim.model.exercise;
+
+import java.util.ArrayList;
+
+/**
+ * A class that contains the names of Keys in the Exercise Hashmap.
+ */
+public class ExerciseKeys {
+
+ private ArrayList keyArrayList;
+
+ /**
+ * Creates a {@code ExerciseKeys}.
+ */
+ public ExerciseKeys(ArrayList arrL) {
+ assert arrL != null;
+ keyArrayList = arrL;
+ }
+
+ /**
+ * Returns the formatted display for UI SavedExerciseListWindow based on the current state of the Hashmap.
+ * @return String
+ */
+ public String getDisplay() {
+ if (keyArrayList.size() == 0) {
+ return "You have no registered exercise in the system.";
+ }
+ StringBuilder sb = new StringBuilder("Unique registered exercise(s):\n");
+ for (int i = 1; i < keyArrayList.size() + 1; i++) {
+ sb.append(i);
+ sb.append(". ");
+ sb.append(keyArrayList.get(i - 1));
+ sb.append("\n");
+ }
+ sb.append("You have " + keyArrayList.size() + " unique exercise(s) registered with the system.\n");
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/gim/model/exercise/ExerciseList.java b/src/main/java/gim/model/exercise/ExerciseList.java
new file mode 100644
index 00000000000..b3ae78d8f43
--- /dev/null
+++ b/src/main/java/gim/model/exercise/ExerciseList.java
@@ -0,0 +1,152 @@
+package gim.model.exercise;
+
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import gim.model.exercise.exceptions.ExerciseNotFoundException;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * A list of exercises that does not allow nulls.
+ *
+ * Supports a minimal set of list operations.
+ *
+ */
+public class ExerciseList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+ private final ObservableList displayedList = FXCollections.observableArrayList(internalUnmodifiableList);
+
+ /**
+ * Returns true if the list contains an equivalent exercise as the given argument.
+ */
+ public boolean contains(Exercise toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameExercise);
+ }
+
+ /**
+ * Adds an Exercise to the List and exerciseHashMap.
+ * If the Exercise already exists, i.e. two Exercises with the same Name, categorise them together.
+ */
+ public void add(Exercise toAdd) {
+ requireNonNull(toAdd);
+ internalList.add(toAdd);
+ displayedList.setAll(internalUnmodifiableList);
+ }
+
+ /**
+ * Replaces the Exercise {@code target} in the list with {@code editedExercise}.
+ * {@code target} must exist in the list.
+ */
+ public void setExercise(Exercise target, Exercise editedExercise) {
+ // Might be removing this?
+ requireAllNonNull(target, editedExercise);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new ExerciseNotFoundException();
+ }
+ internalList.set(index, editedExercise);
+ }
+
+ /**
+ * Removes the equivalent exercise from the list.
+ * The exercise must exist in the list.
+ */
+ public void remove(Exercise toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new ExerciseNotFoundException();
+ }
+ displayedList.setAll(internalUnmodifiableList);
+ }
+
+ public void setExercises(ExerciseList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ displayedList.setAll(internalUnmodifiableList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code exercises}.
+ * {@code exercises} can contain duplicate exercises.
+ */
+ public void setExercises(List exercises) {
+ requireAllNonNull(exercises);
+ internalList.setAll(exercises);
+ displayedList.setAll(internalUnmodifiableList);
+ }
+
+ /**
+ * Returns the displayed list {@code ObservableList}.
+ */
+ public ObservableList asDisplayedList() {
+ return displayedList;
+ }
+
+ /**
+ * Returns a duplicated displayed list {@code ObservableList}.
+ */
+ public ObservableList asDuplicatedDisplayedList() {
+ ObservableList duplicatedDisplayedList = FXCollections.observableArrayList(displayedList);
+ return duplicatedDisplayedList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ExerciseList // instanceof handles nulls
+ && internalList.equals(((ExerciseList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Sorts the displayedList according to the chronological order of the date field of exercise.
+ *
+ * Algorithm Description:
+ * (1) List is sorted by the comparator in increasing chronological order of date. Then, the list is sorted in
+ * decreasing alphabetical order by the comparator.
+ * (2) Then the call {@code Collections.reverse(displayedList)} will reverse the list. The resulting list
+ * will be sorted from the latest chronological date (at the top of the UI) to the oldest chronological
+ * date (at the bottom of the UI). For exercises with the same date in the resulting list, they are sorted
+ * by increasing alphabetical order.
+ */
+ public void sortDisplayedList() {
+ Comparator comparator = Comparator.comparing(e -> e.getDate().date);
+ comparator = comparator.thenComparing((e1, e2) -> e2.getName().fullName.compareTo(e1.getName().fullName));
+ Collections.sort(displayedList, comparator);
+ Collections.reverse(displayedList);
+ }
+
+ /**
+ * Resets the displayedList to the default order (internalUnmodifiableList).
+ */
+ public void resetDisplayedList() {
+ displayedList.setAll(internalUnmodifiableList);
+ }
+
+ /**
+ * Filters the displayedList based on the filtered list that user filtered.
+ */
+ public void filterDisplayedList(ObservableList filteredList) {
+ displayedList.setAll(filteredList);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/gim/model/exercise/Name.java
similarity index 69%
rename from src/main/java/seedu/address/model/person/Name.java
rename to src/main/java/gim/model/exercise/Name.java
index 79244d71cf7..95afc4d3d2e 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/gim/model/exercise/Name.java
@@ -1,13 +1,13 @@
-package seedu.address.model.person;
+package gim.model.exercise;
+import static gim.commons.util.AppUtil.checkArgument;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
/**
- * Represents a Person's name in the address book.
+ * Represents an Exercise's name in the exercise tracker.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
-public class Name {
+public class Name implements Comparable {
public static final String MESSAGE_CONSTRAINTS =
"Names should only contain alphanumeric characters and spaces, and it should not be blank";
@@ -48,12 +48,17 @@ public String toString() {
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof Name // instanceof handles nulls
- && fullName.equals(((Name) other).fullName)); // state check
+ && fullName.toLowerCase().replaceAll("\\s", "").equals(((Name) other).fullName
+ .toLowerCase().replaceAll("\\s", ""))); // equals after spaces removed & lowercase set
}
@Override
public int hashCode() {
- return fullName.hashCode();
+ return fullName.toLowerCase().replaceAll("\\s", "").hashCode();
}
+ @Override
+ public int compareTo(Name name) {
+ return this.fullName.compareTo(name.fullName);
+ }
}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/gim/model/exercise/NameContainsKeywordsPredicate.java
similarity index 73%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/gim/model/exercise/NameContainsKeywordsPredicate.java
index c9b5868427c..f2965eb4db6 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/gim/model/exercise/NameContainsKeywordsPredicate.java
@@ -1,14 +1,14 @@
-package seedu.address.model.person;
+package gim.model.exercise;
import java.util.List;
import java.util.function.Predicate;
-import seedu.address.commons.util.StringUtil;
+import gim.commons.util.StringUtil;
/**
- * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ * Tests that a {@code Exercise}'s {@code Name} matches any of the keywords given.
*/
-public class NameContainsKeywordsPredicate implements Predicate {
+public class NameContainsKeywordsPredicate implements Predicate {
private final List keywords;
public NameContainsKeywordsPredicate(List keywords) {
@@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) {
}
@Override
- public boolean test(Person person) {
+ public boolean test(Exercise exercise) {
return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(exercise.getName().fullName, keyword));
}
@Override
diff --git a/src/main/java/gim/model/exercise/Reps.java b/src/main/java/gim/model/exercise/Reps.java
new file mode 100644
index 00000000000..5791e1e321e
--- /dev/null
+++ b/src/main/java/gim/model/exercise/Reps.java
@@ -0,0 +1,58 @@
+package gim.model.exercise;
+
+import static gim.commons.util.AppUtil.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Exercise's Reps in the exercise tracker.
+ * Guarantees: immutable; is valid as declared in {@link #isValidReps(String)}
+ */
+public class Reps {
+
+ public static final String MESSAGE_CONSTRAINTS = "Reps can only take positive integer values, up to 3 digits, "
+ + "leading zeros are not allowed";
+
+ /*
+ * The first character of the Reps must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^(?:([1-9])|([1-9][0-9])|([1-9][0-9][0-9]))$";
+
+ public final String value;
+
+ /**
+ * Constructs {@code Reps}.
+ *
+ * @param reps A valid number of Reps.
+ */
+ public Reps(String reps) {
+ requireNonNull(reps);
+ checkArgument(isValidReps(reps), MESSAGE_CONSTRAINTS);
+ value = reps;
+ }
+
+ /**
+ * Returns true if a given String is a valid number of Reps.
+ */
+ public static boolean isValidReps(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Reps // instanceof handles nulls
+ && value.equals(((Reps) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/gim/model/exercise/Sets.java b/src/main/java/gim/model/exercise/Sets.java
new file mode 100644
index 00000000000..99fd6db3d65
--- /dev/null
+++ b/src/main/java/gim/model/exercise/Sets.java
@@ -0,0 +1,54 @@
+package gim.model.exercise;
+
+import static gim.commons.util.AppUtil.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents an Exercise's sets in Gim.
+ * Guarantees: immutable; is valid as declared in {@link #isValidSets(String)}
+ */
+public class Sets {
+
+ public static final String MESSAGE_CONSTRAINTS = "Sets can only take positive integer values, up to 3 digits, "
+ + "leading zeros are not allowed.";
+ public static final String VALIDATION_REGEX = "^(?:([1-9])|([1-9][0-9])|([1-9][0-9][0-9]))$";
+
+
+ public final String value;
+
+ /**
+ * Constructs {@code Sets}.
+ *
+ * @param sets A valid number of Sets.
+ */
+ public Sets(String sets) {
+ requireNonNull(sets);
+ checkArgument(isValidSets(sets), MESSAGE_CONSTRAINTS);
+ value = sets;
+ }
+
+ /**
+ * Returns if a given String is a valid number of Sets.
+ */
+ public static boolean isValidSets(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Sets // instanceof handles nulls
+ && value.equals(((Sets) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/gim/model/exercise/Weight.java b/src/main/java/gim/model/exercise/Weight.java
new file mode 100644
index 00000000000..bc872e07874
--- /dev/null
+++ b/src/main/java/gim/model/exercise/Weight.java
@@ -0,0 +1,58 @@
+package gim.model.exercise;
+
+import static gim.commons.util.AppUtil.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents an Exercise's weight number in the exercise tracker.
+ * Guarantees: immutable; is valid as declared in {@link #isValidWeight(String)}
+ */
+public class Weight implements Comparable {
+
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Weight must be a positive decimal number, up to 3 digits for the whole number "
+ + "and up to 2 digits for the decimal place";
+ public static final String VALIDATION_REGEX = "\\d{1,3}(\\.\\d{1,2})?";
+ public final String value;
+
+ /**
+ * Constructs a {@code Weight}.
+ *
+ * @param weight A valid Weight.
+ */
+ public Weight(String weight) {
+ requireNonNull(weight);
+ checkArgument(isValidWeight(weight), MESSAGE_CONSTRAINTS);
+ value = weight;
+ }
+
+ /**
+ * Returns true if a given String is a valid Weight.
+ */
+ public static boolean isValidWeight(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Weight // instanceof handles nulls
+ && value.equals(((Weight) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(Weight o) {
+ return (int) Math.ceil(Double.parseDouble(this.value) - Double.parseDouble(o.value));
+ }
+}
diff --git a/src/main/java/gim/model/exercise/exceptions/ExerciseNotFoundException.java b/src/main/java/gim/model/exercise/exceptions/ExerciseNotFoundException.java
new file mode 100644
index 00000000000..7c0699f989a
--- /dev/null
+++ b/src/main/java/gim/model/exercise/exceptions/ExerciseNotFoundException.java
@@ -0,0 +1,6 @@
+package gim.model.exercise.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified exercise.
+ */
+public class ExerciseNotFoundException extends RuntimeException {}
diff --git a/src/main/java/gim/model/util/SampleDataUtil.java b/src/main/java/gim/model/util/SampleDataUtil.java
new file mode 100644
index 00000000000..f4f7ca2e5f3
--- /dev/null
+++ b/src/main/java/gim/model/util/SampleDataUtil.java
@@ -0,0 +1,48 @@
+package gim.model.util;
+
+import gim.model.ExerciseTracker;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.date.Date;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+import gim.model.exercise.Reps;
+import gim.model.exercise.Sets;
+import gim.model.exercise.Weight;
+
+/**
+ * Contains utility methods for populating {@code ExerciseTracker} with sample data.
+ */
+public class SampleDataUtil {
+ public static Exercise[] getSampleExercises() {
+ return new Exercise[] {
+ new Exercise(new Name("Squat"), new Weight("65"), new Sets("1"),
+ new Reps("1"),
+ new Date("14/10/2022")),
+ new Exercise(new Name("Squat"), new Weight("60"), new Sets("1"),
+ new Reps("1"),
+ new Date("13/10/2022")),
+ new Exercise(new Name("Bench Press"), new Weight("40"), new Sets("1"),
+ new Reps("1"),
+ new Date("13/10/2022")),
+ new Exercise(new Name("Deadlift"), new Weight("70"), new Sets("1"),
+ new Reps("1"),
+ new Date("13/10/2022")),
+ };
+ }
+
+ public static ReadOnlyExerciseTracker getSampleExerciseTracker() {
+ ExerciseTracker sampleAb = new ExerciseTracker();
+ for (Exercise sampleExercise : getSampleExercises()) {
+ sampleAb.addExercise(sampleExercise);
+ }
+ return sampleAb;
+ }
+
+ /**
+ * Returns a date containing the string given.
+ */
+ public static Date getDate(String string) {
+ return new Date(string);
+ }
+
+}
diff --git a/src/main/java/gim/storage/ExerciseTrackerStorage.java b/src/main/java/gim/storage/ExerciseTrackerStorage.java
new file mode 100644
index 00000000000..d7cf0bce112
--- /dev/null
+++ b/src/main/java/gim/storage/ExerciseTrackerStorage.java
@@ -0,0 +1,45 @@
+package gim.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import gim.commons.exceptions.DataConversionException;
+import gim.model.ReadOnlyExerciseTracker;
+
+/**
+ * Represents a storage for {@link gim.model.ExerciseTracker}.
+ */
+public interface ExerciseTrackerStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getExerciseTrackerFilePath();
+
+ /**
+ * Returns ExerciseTracker data as a {@link ReadOnlyExerciseTracker}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ * @throws DataConversionException if the data in storage is not in the expected format.
+ * @throws IOException if there was any problem when reading from the storage.
+ */
+ Optional readExerciseTracker() throws DataConversionException, IOException;
+
+ /**
+ * @see #getExerciseTrackerFilePath()
+ */
+ Optional readExerciseTracker(Path filePath) throws DataConversionException, IOException;
+
+ /**
+ * Saves the given {@link ReadOnlyExerciseTracker} to the storage.
+ * @param exerciseTracker cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker) throws IOException;
+
+ /**
+ * @see #saveExerciseTracker(ReadOnlyExerciseTracker)
+ */
+ void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker, Path filePath) throws IOException;
+
+}
diff --git a/src/main/java/gim/storage/JsonAdaptedDate.java b/src/main/java/gim/storage/JsonAdaptedDate.java
new file mode 100644
index 00000000000..94b57de9d5e
--- /dev/null
+++ b/src/main/java/gim/storage/JsonAdaptedDate.java
@@ -0,0 +1,48 @@
+package gim.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import gim.commons.exceptions.IllegalValueException;
+import gim.model.date.Date;
+
+/**
+ * Jackson-friendly version of {@link Date}.
+ */
+class JsonAdaptedDate {
+
+ private final String dateString;
+
+ /**
+ * Constructs a {@code JsonAdaptedDate} with the given {@code dateString}.
+ */
+ @JsonCreator
+ public JsonAdaptedDate(String dateString) {
+ this.dateString = dateString;
+ }
+
+ /**
+ * Converts a given {@code Date} into this class for Jackson use.
+ */
+ public JsonAdaptedDate(Date source) {
+ dateString = source.toString();
+ }
+
+ @JsonValue
+ public String getDateString() {
+ return dateString;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted date object into the model's {@code Date} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted date.
+ */
+ public Date toModelType() throws IllegalValueException {
+ if (!Date.isValidDateByRegex(dateString)) {
+ throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS_FORMAT);
+ }
+ return new Date(dateString);
+ }
+
+}
diff --git a/src/main/java/gim/storage/JsonAdaptedExercise.java b/src/main/java/gim/storage/JsonAdaptedExercise.java
new file mode 100644
index 00000000000..6a2919505c6
--- /dev/null
+++ b/src/main/java/gim/storage/JsonAdaptedExercise.java
@@ -0,0 +1,101 @@
+package gim.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import gim.commons.exceptions.IllegalValueException;
+import gim.model.date.Date;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+import gim.model.exercise.Reps;
+import gim.model.exercise.Sets;
+import gim.model.exercise.Weight;
+
+
+/**
+ * Jackson-friendly version of {@link Exercise}.
+ */
+class JsonAdaptedExercise {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Exercise's %s field is missing!";
+
+ private final String name;
+ private final String weight;
+ private final String sets;
+ private final String reps;
+ private final JsonAdaptedDate date;
+
+ /**
+ * Constructs a {@code JsonAdaptedExercise} with the given exercise details.
+ */
+ @JsonCreator
+ public JsonAdaptedExercise(@JsonProperty("name") String name, @JsonProperty("weight") String weight,
+ @JsonProperty("sets") String sets, @JsonProperty("reps") String reps,
+ @JsonProperty("date") JsonAdaptedDate date) {
+ this.name = name;
+ this.weight = weight;
+ this.sets = sets;
+ this.reps = reps;
+ this.date = date;
+ }
+
+ /**
+ * Converts a given {@code Exercise} into this class for Jackson use.
+ */
+ public JsonAdaptedExercise(Exercise source) {
+ name = source.getName().fullName;
+ weight = source.getWeight().value;
+ sets = source.getSets().value;
+ reps = source.getReps().value;
+ date = new JsonAdaptedDate(source.getDate());
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted exercise object into the model's {@code Exercise} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted exercise.
+ */
+ public Exercise toModelType() throws IllegalValueException {
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(name)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ final Name modelName = new Name(name);
+
+ if (weight == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Weight.class.getSimpleName()));
+ }
+ if (!Weight.isValidWeight(weight)) {
+ throw new IllegalValueException(Weight.MESSAGE_CONSTRAINTS);
+ }
+ final Weight modelWeight = new Weight(weight);
+
+ if (sets == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Sets.class.getSimpleName()));
+ }
+ if (!Sets.isValidSets(sets)) {
+ throw new IllegalValueException(Sets.MESSAGE_CONSTRAINTS);
+ }
+ final Sets modelSets = new Sets(sets);
+
+ if (reps == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Reps.class.getSimpleName()));
+ }
+ if (!Reps.isValidReps(reps)) {
+ throw new IllegalValueException(Reps.MESSAGE_CONSTRAINTS);
+ }
+ final Reps modelReps = new Reps(reps);
+
+ if (date == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName()));
+ }
+ if (!Date.isValidDateByRegex(date.toModelType().toString())) {
+ throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS_FORMAT);
+ }
+ final Date modelDates = date.toModelType();
+ return new Exercise(modelName, modelWeight, modelSets, modelReps, modelDates);
+ }
+
+}
diff --git a/src/main/java/gim/storage/JsonExerciseTrackerStorage.java b/src/main/java/gim/storage/JsonExerciseTrackerStorage.java
new file mode 100644
index 00000000000..9faada3c456
--- /dev/null
+++ b/src/main/java/gim/storage/JsonExerciseTrackerStorage.java
@@ -0,0 +1,80 @@
+package gim.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import gim.commons.core.LogsCenter;
+import gim.commons.exceptions.DataConversionException;
+import gim.commons.exceptions.IllegalValueException;
+import gim.commons.util.FileUtil;
+import gim.commons.util.JsonUtil;
+import gim.model.ReadOnlyExerciseTracker;
+
+/**
+ * A class to access ExerciseTracker data stored as a json file on the hard disk.
+ */
+public class JsonExerciseTrackerStorage implements ExerciseTrackerStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonExerciseTrackerStorage.class);
+
+ private Path filePath;
+
+ public JsonExerciseTrackerStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public Path getExerciseTrackerFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readExerciseTracker() throws DataConversionException {
+ return readExerciseTracker(filePath);
+ }
+
+ /**
+ * Similar to {@link #readExerciseTracker()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataConversionException if the file is not in the correct format.
+ */
+ public Optional readExerciseTracker(Path filePath) throws DataConversionException {
+ requireNonNull(filePath);
+
+ Optional jsonExerciseTracker = JsonUtil.readJsonFile(
+ filePath, JsonSerializableExerciseTracker.class);
+ if (!jsonExerciseTracker.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonExerciseTracker.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataConversionException(ive);
+ }
+ }
+
+ @Override
+ public void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker) throws IOException {
+ saveExerciseTracker(exerciseTracker, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveExerciseTracker(ReadOnlyExerciseTracker)}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker, Path filePath) throws IOException {
+ requireNonNull(exerciseTracker);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableExerciseTracker(exerciseTracker), filePath);
+ }
+
+}
diff --git a/src/main/java/gim/storage/JsonSerializableExerciseTracker.java b/src/main/java/gim/storage/JsonSerializableExerciseTracker.java
new file mode 100644
index 00000000000..82e14b4abd4
--- /dev/null
+++ b/src/main/java/gim/storage/JsonSerializableExerciseTracker.java
@@ -0,0 +1,55 @@
+package gim.storage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+import gim.commons.exceptions.IllegalValueException;
+import gim.model.ExerciseTracker;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.exercise.Exercise;
+
+/**
+ * An Immutable ExerciseTracker that is serializable to JSON format.
+ */
+@JsonRootName(value = "exercisetracker")
+class JsonSerializableExerciseTracker {
+
+ private final List exercises = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableExerciseTracker} with the given exercises.
+ */
+ @JsonCreator
+ public JsonSerializableExerciseTracker(@JsonProperty("exercises") List exercises) {
+ this.exercises.addAll(exercises);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyExerciseTracker} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableExerciseTracker}.
+ */
+ public JsonSerializableExerciseTracker(ReadOnlyExerciseTracker source) {
+ exercises.addAll(source.getExerciseList().stream().map(JsonAdaptedExercise::new).collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this exercise tracker into the model's {@code ExerciseTracker} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public ExerciseTracker toModelType() throws IllegalValueException {
+ ExerciseTracker exerciseTracker = new ExerciseTracker();
+ for (JsonAdaptedExercise jsonAdaptedExercise : exercises) {
+ Exercise exercise = jsonAdaptedExercise.toModelType();
+ exerciseTracker.addExercise(exercise);
+ }
+ return exerciseTracker;
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/gim/storage/JsonUserPrefsStorage.java
similarity index 83%
rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
rename to src/main/java/gim/storage/JsonUserPrefsStorage.java
index bc2bbad84aa..ae7da5cba77 100644
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ b/src/main/java/gim/storage/JsonUserPrefsStorage.java
@@ -1,13 +1,13 @@
-package seedu.address.storage;
+package gim.storage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import gim.commons.exceptions.DataConversionException;
+import gim.commons.util.JsonUtil;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.UserPrefs;
/**
* A class to access UserPrefs stored in the hard disk as a json file
diff --git a/src/main/java/gim/storage/Storage.java b/src/main/java/gim/storage/Storage.java
new file mode 100644
index 00000000000..31753e1e7ce
--- /dev/null
+++ b/src/main/java/gim/storage/Storage.java
@@ -0,0 +1,32 @@
+package gim.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import gim.commons.exceptions.DataConversionException;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.UserPrefs;
+
+/**
+ * API of the Storage component
+ */
+public interface Storage extends ExerciseTrackerStorage, UserPrefsStorage {
+
+ @Override
+ Optional readUserPrefs() throws DataConversionException, IOException;
+
+ @Override
+ void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
+
+ @Override
+ Path getExerciseTrackerFilePath();
+
+ @Override
+ Optional readExerciseTracker() throws DataConversionException, IOException;
+
+ @Override
+ void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker) throws IOException;
+
+}
diff --git a/src/main/java/gim/storage/StorageManager.java b/src/main/java/gim/storage/StorageManager.java
new file mode 100644
index 00000000000..d560fd050fa
--- /dev/null
+++ b/src/main/java/gim/storage/StorageManager.java
@@ -0,0 +1,79 @@
+package gim.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import gim.commons.core.LogsCenter;
+import gim.commons.exceptions.DataConversionException;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.UserPrefs;
+
+/**
+ * Manages storage of ExerciseTracker data in local storage.
+ */
+public class StorageManager implements Storage {
+
+ private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
+ private ExerciseTrackerStorage exerciseTrackerStorage;
+ private UserPrefsStorage userPrefsStorage;
+
+ /**
+ * Creates a {@code StorageManager} with the given {@code ExerciseTrackerStorage} and {@code UserPrefStorage}.
+ */
+ public StorageManager(ExerciseTrackerStorage exerciseTrackerStorage, UserPrefsStorage userPrefsStorage) {
+ this.exerciseTrackerStorage = exerciseTrackerStorage;
+ this.userPrefsStorage = userPrefsStorage;
+ }
+
+ // ================ UserPrefs methods ==============================
+
+ @Override
+ public Path getUserPrefsFilePath() {
+ return userPrefsStorage.getUserPrefsFilePath();
+ }
+
+ @Override
+ public Optional readUserPrefs() throws DataConversionException, IOException {
+ return userPrefsStorage.readUserPrefs();
+ }
+
+ @Override
+ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
+ userPrefsStorage.saveUserPrefs(userPrefs);
+ }
+
+
+ // ================ ExerciseTracker methods ==============================
+
+ @Override
+ public Path getExerciseTrackerFilePath() {
+ return exerciseTrackerStorage.getExerciseTrackerFilePath();
+ }
+
+ @Override
+ public Optional readExerciseTracker() throws DataConversionException, IOException {
+ return readExerciseTracker(exerciseTrackerStorage.getExerciseTrackerFilePath());
+ }
+
+ @Override
+ public Optional readExerciseTracker(Path filePath) throws DataConversionException,
+ IOException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return exerciseTrackerStorage.readExerciseTracker(filePath);
+ }
+
+ @Override
+ public void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker) throws IOException {
+ saveExerciseTracker(exerciseTracker, exerciseTrackerStorage.getExerciseTrackerFilePath());
+ }
+
+ @Override
+ public void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ exerciseTrackerStorage.saveExerciseTracker(exerciseTracker, filePath);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/gim/storage/UserPrefsStorage.java
similarity index 71%
rename from src/main/java/seedu/address/storage/UserPrefsStorage.java
rename to src/main/java/gim/storage/UserPrefsStorage.java
index 29eef178dbc..605c1abdd0e 100644
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ b/src/main/java/gim/storage/UserPrefsStorage.java
@@ -1,15 +1,15 @@
-package seedu.address.storage;
+package gim.storage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import gim.commons.exceptions.DataConversionException;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.UserPrefs;
/**
- * Represents a storage for {@link seedu.address.model.UserPrefs}.
+ * Represents a storage for {@link gim.model.UserPrefs}.
*/
public interface UserPrefsStorage {
@@ -27,7 +27,7 @@ public interface UserPrefsStorage {
Optional readUserPrefs() throws DataConversionException, IOException;
/**
- * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage.
+ * Saves the given {@link gim.model.ReadOnlyUserPrefs} to the storage.
* @param userPrefs cannot be null.
* @throws IOException if there was any problem writing to the file.
*/
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/gim/ui/CommandBox.java
similarity index 89%
rename from src/main/java/seedu/address/ui/CommandBox.java
rename to src/main/java/gim/ui/CommandBox.java
index 9e75478664b..af92d2676f4 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/gim/ui/CommandBox.java
@@ -1,12 +1,12 @@
-package seedu.address.ui;
+package gim.ui;
+import gim.logic.commands.CommandResult;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.parser.exceptions.ParseException;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
/**
* The UI component that is responsible for receiving user command inputs.
@@ -77,7 +77,7 @@ public interface CommandExecutor {
/**
* Executes the command and returns the result.
*
- * @see seedu.address.logic.Logic#execute(String)
+ * @see gim.logic.Logic#execute(String)
*/
CommandResult execute(String commandText) throws CommandException, ParseException;
}
diff --git a/src/main/java/gim/ui/ExerciseCard.java b/src/main/java/gim/ui/ExerciseCard.java
new file mode 100644
index 00000000000..ce963f3ddae
--- /dev/null
+++ b/src/main/java/gim/ui/ExerciseCard.java
@@ -0,0 +1,75 @@
+package gim.ui;
+
+import gim.model.exercise.Exercise;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+
+/**
+ * An UI component that displays information of a {@code Exercise}.
+ */
+public class ExerciseCard extends UiPart {
+
+ private static final String FXML = "ExerciseListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on ExerciseTracker
+ * level 4
+ */
+
+ public final Exercise exercise;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label weight;
+ @FXML
+ private Label reps;
+ @FXML
+ private Label sets;
+ @FXML
+ private FlowPane dates;
+
+ /**
+ * Creates a {@code ExerciseCard} with the given {@code Exercise} and index to display.
+ */
+ public ExerciseCard(Exercise exercise, int displayedIndex) {
+ super(FXML);
+ this.exercise = exercise;
+ id.setText(displayedIndex + ". ");
+ name.setText(exercise.getName().fullName);
+ weight.setText("Weight: " + exercise.getWeight().value + "kg");
+ reps.setText("Reps: " + exercise.getReps().value);
+ sets.setText("Sets: " + exercise.getSets().value);
+ dates.getChildren().add(new Label(exercise.getDate().getDayString()));
+ dates.getChildren().add(new Label(exercise.getDateString()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ExerciseCard)) {
+ return false;
+ }
+
+ // state check
+ ExerciseCard card = (ExerciseCard) other;
+ return id.getText().equals(card.id.getText())
+ && exercise.equals(card.exercise);
+ }
+}
diff --git a/src/main/java/gim/ui/ExerciseListPanel.java b/src/main/java/gim/ui/ExerciseListPanel.java
new file mode 100644
index 00000000000..c341d80c5f1
--- /dev/null
+++ b/src/main/java/gim/ui/ExerciseListPanel.java
@@ -0,0 +1,49 @@
+package gim.ui;
+
+import java.util.logging.Logger;
+
+import gim.commons.core.LogsCenter;
+import gim.model.exercise.Exercise;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+
+/**
+ * Panel containing the list of exercises.
+ */
+public class ExerciseListPanel extends UiPart {
+ private static final String FXML = "ExerciseListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(ExerciseListPanel.class);
+
+ @FXML
+ private ListView exerciseListView;
+
+ /**
+ * Creates a {@code ExerciseListPanel} with the given {@code ObservableList}.
+ */
+ public ExerciseListPanel(ObservableList exerciseList) {
+ super(FXML);
+ exerciseListView.setItems(exerciseList);
+ exerciseListView.setCellFactory(listView -> new ExerciseListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Exercise} using a {@code ExerciseCard}.
+ */
+ class ExerciseListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Exercise exercise, boolean empty) {
+ super.updateItem(exercise, empty);
+
+ if (empty || exercise == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ExerciseCard(exercise, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/gim/ui/HelpWindow.java
similarity index 58%
rename from src/main/java/seedu/address/ui/HelpWindow.java
rename to src/main/java/gim/ui/HelpWindow.java
index 3f16b2fcf26..d5fe3ba49f9 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/gim/ui/HelpWindow.java
@@ -1,23 +1,43 @@
-package seedu.address.ui;
+package gim.ui;
import java.util.logging.Logger;
+import gim.commons.core.LogsCenter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.stage.Stage;
-import seedu.address.commons.core.LogsCenter;
/**
* Controller for a help page
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
- public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
+ public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-t15-4.github.io/tp/UserGuide.html";
+ public static final String HELP_MESSAGE = USERGUIDE_URL;
+ public static final String HELP_PARA = "Hi there, welcome to GIM! Here is a list of supported commands.\n"
+ + "1) :add - Adds an exercise\n"
+ + "2) :del - Deletes an exercise\n"
+ + "3) :list - Lists all exercise entries\n"
+ + "4) :filter - Filters exercises by their name\n"
+ + "5) :clear - Clears the saved exercises and resets the data in the system\n"
+ + "6) :sort - Sorts the exercises by date, if they have the same date, by name \n"
+ + "7) :range - Displays the exercises saved over a range of days\n"
+ + "8) :pr - Displays the stored Personal Records\n"
+ + "9) :gen - Generates a sample workout based on your Personal Records\n"
+ + "10) :help - Displays the help menu\n"
+ + "11) :wq - Exits the application\n"
+ + "Notes:\n"
+ + "- For Add, Delete, Filter, Range, Pr, Gen, simply type the command without"
+ + " parameters and example usages will appear "
+ + "in the result box on the top right.\n"
+ + "- Other commands function without parameter inputs.\n"
+ + "- Gen, Filter and PR commands support one or multiple exercises\n"
+ + "If you have any more questions, feel free to visit our user guide below.";
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
private static final String FXML = "HelpWindow.fxml";
@@ -27,6 +47,9 @@ public class HelpWindow extends UiPart {
@FXML
private Label helpMessage;
+ @FXML
+ private TextArea helpPara;
+
/**
* Creates a new HelpWindow.
*
@@ -35,6 +58,7 @@ public class HelpWindow extends UiPart {
public HelpWindow(Stage root) {
super(FXML, root);
helpMessage.setText(HELP_MESSAGE);
+ helpPara.setText(HELP_PARA);
}
/**
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/gim/ui/MainWindow.java
similarity index 83%
rename from src/main/java/seedu/address/ui/MainWindow.java
rename to src/main/java/gim/ui/MainWindow.java
index 9106c3aa6e5..1462488b938 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/gim/ui/MainWindow.java
@@ -1,7 +1,13 @@
-package seedu.address.ui;
+package gim.ui;
import java.util.logging.Logger;
+import gim.commons.core.GuiSettings;
+import gim.commons.core.LogsCenter;
+import gim.logic.Logic;
+import gim.logic.commands.CommandResult;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.parser.exceptions.ParseException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.MenuItem;
@@ -10,12 +16,6 @@
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.Logic;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
/**
* The Main Window. Provides the basic application layout containing
@@ -31,10 +31,12 @@ public class MainWindow extends UiPart {
private Logic logic;
// Independent Ui parts residing in this Ui container
- private PersonListPanel personListPanel;
+ private ExerciseListPanel exerciseListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private SavedExerciseListWindow exerciseListWindow;
+
@FXML
private StackPane commandBoxPlaceholder;
@@ -42,7 +44,7 @@ public class MainWindow extends UiPart {
private MenuItem helpMenuItem;
@FXML
- private StackPane personListPanelPlaceholder;
+ private StackPane exerciseListPanelPlaceholder;
@FXML
private StackPane resultDisplayPlaceholder;
@@ -50,6 +52,9 @@ public class MainWindow extends UiPart {
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private StackPane savedExerciseListPlaceholder;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -110,17 +115,21 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
- personListPanel = new PersonListPanel(logic.getFilteredPersonList());
- personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
-
+ exerciseListPanel = new ExerciseListPanel(logic.getFilteredExerciseList());
+ exerciseListPanelPlaceholder.getChildren().add(exerciseListPanel.getRoot());
resultDisplay = new ResultDisplay();
+ resultDisplay.sendWelcomeMessage();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
- StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
+ StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getExerciseTrackerFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+
+ exerciseListWindow = new SavedExerciseListWindow();
+ savedExerciseListPlaceholder.getChildren().add(exerciseListWindow.getRoot());
+ exerciseListWindow.setExerciseHashMap(logic.getExerciseHashmap());
}
/**
@@ -163,14 +172,14 @@ private void handleExit() {
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
+ public ExerciseListPanel getExerciseListPanel() {
+ return exerciseListPanel;
}
/**
* Executes the command and returns the result.
*
- * @see seedu.address.logic.Logic#execute(String)
+ * @see gim.logic.Logic#execute(String)
*/
private CommandResult executeCommand(String commandText) throws CommandException, ParseException {
try {
diff --git a/src/main/java/gim/ui/Observer.java b/src/main/java/gim/ui/Observer.java
new file mode 100644
index 00000000000..25b90bfd7d8
--- /dev/null
+++ b/src/main/java/gim/ui/Observer.java
@@ -0,0 +1,8 @@
+package gim.ui;
+
+/**
+ * An Observer interface for UI components to observe the Exercise Hashmap
+ */
+public interface Observer {
+ public void update();
+}
diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/gim/ui/ResultDisplay.java
similarity index 66%
rename from src/main/java/seedu/address/ui/ResultDisplay.java
rename to src/main/java/gim/ui/ResultDisplay.java
index 7d98e84eedf..c602e143551 100644
--- a/src/main/java/seedu/address/ui/ResultDisplay.java
+++ b/src/main/java/gim/ui/ResultDisplay.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package gim.ui;
import static java.util.Objects.requireNonNull;
@@ -12,6 +12,8 @@
public class ResultDisplay extends UiPart {
private static final String FXML = "ResultDisplay.fxml";
+ private static final String welcomeMessage = "Welcome to GIM!\nWhat are you waiting for?\n"
+ + "Start your exercise tracking right now!\nBe Fitter, Healthier, Happier with GIM!";
@FXML
private TextArea resultDisplay;
@@ -25,4 +27,8 @@ public void setFeedbackToUser(String feedbackToUser) {
resultDisplay.setText(feedbackToUser);
}
+ public void sendWelcomeMessage() {
+ resultDisplay.setText(welcomeMessage);
+ }
+
}
diff --git a/src/main/java/gim/ui/SavedExerciseListWindow.java b/src/main/java/gim/ui/SavedExerciseListWindow.java
new file mode 100644
index 00000000000..d5ef9492fa6
--- /dev/null
+++ b/src/main/java/gim/ui/SavedExerciseListWindow.java
@@ -0,0 +1,52 @@
+package gim.ui;
+
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.ExerciseKeys;
+import javafx.fxml.FXML;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.Region;
+
+/**
+ * A ui for the Saved Exercises List Window that is displayed at the Bottom Right of the application.
+ */
+public class SavedExerciseListWindow extends UiPart implements Observer {
+
+ private static final String FXML = "SavedExerciseListWindow.fxml";
+
+ private ExerciseKeys exerciseKeys;
+
+ @FXML
+ private TextArea savedExerciseList;
+
+ private ExerciseHashMap exerciseHashMap;
+
+ /**
+ * Creates a {@code SavedExerciseListWindow}.
+ */
+ public SavedExerciseListWindow() {
+ super(FXML);
+ }
+
+ /**
+ * Sets a copy of ExerciseHashMap in SavedExerciseListWindow.
+ * Subscribes the window to changes in the ExerciseHashmap and displays its current state.
+ * @param ehm ExerciseHashMap
+ */
+ public void setExerciseHashMap(ExerciseHashMap ehm) {
+ exerciseHashMap = ehm;
+ ehm.addUi(this);
+ ehm.notifyObservers();
+ }
+
+ /**
+ * Defines the response to be taken when there is a change in the ExerciseHashMap the window is subscribed to.
+ */
+ @Override
+ public void update() {
+ exerciseKeys = new ExerciseKeys(exerciseHashMap.getAllKeys());
+ savedExerciseList.setText(exerciseKeys.getDisplay() + "\n"
+ + "You have done " + exerciseHashMap.numOfValues() + " exercise(s) till date.");
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/gim/ui/StatusBarFooter.java
similarity index 96%
rename from src/main/java/seedu/address/ui/StatusBarFooter.java
rename to src/main/java/gim/ui/StatusBarFooter.java
index b577f829423..190cd28c2fd 100644
--- a/src/main/java/seedu/address/ui/StatusBarFooter.java
+++ b/src/main/java/gim/ui/StatusBarFooter.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package gim.ui;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/gim/ui/Ui.java
similarity index 86%
rename from src/main/java/seedu/address/ui/Ui.java
rename to src/main/java/gim/ui/Ui.java
index 17aa0b494fe..910e406a12f 100644
--- a/src/main/java/seedu/address/ui/Ui.java
+++ b/src/main/java/gim/ui/Ui.java
@@ -1,4 +1,4 @@
-package seedu.address.ui;
+package gim.ui;
import javafx.stage.Stage;
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/gim/ui/UiManager.java
similarity index 91%
rename from src/main/java/seedu/address/ui/UiManager.java
rename to src/main/java/gim/ui/UiManager.java
index fdf024138bc..1fefca667de 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/gim/ui/UiManager.java
@@ -1,16 +1,16 @@
-package seedu.address.ui;
+package gim.ui;
import java.util.logging.Logger;
+import gim.MainApp;
+import gim.commons.core.LogsCenter;
+import gim.commons.util.StringUtil;
+import gim.logic.Logic;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.stage.Stage;
-import seedu.address.MainApp;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
/**
* The manager of the UI component.
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/clean.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/gim/ui/UiPart.java
similarity index 97%
rename from src/main/java/seedu/address/ui/UiPart.java
rename to src/main/java/gim/ui/UiPart.java
index fc820e01a9c..a277a037975 100644
--- a/src/main/java/seedu/address/ui/UiPart.java
+++ b/src/main/java/gim/ui/UiPart.java
@@ -1,12 +1,12 @@
-package seedu.address.ui;
+package gim.ui;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.net.URL;
+import gim.MainApp;
import javafx.fxml.FXMLLoader;
-import seedu.address.MainApp;
/**
* Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc.
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
deleted file mode 100644
index 1deb3a1e469..00000000000
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package seedu.address.commons.core;
-
-/**
- * Container for user visible messages.
- */
-public class Messages {
-
- public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
- public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
-}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
deleted file mode 100644
index 92cd8fa605a..00000000000
--- a/src/main/java/seedu/address/logic/Logic.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package seedu.address.logic;
-
-import java.nio.file.Path;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-
-/**
- * API of the Logic component
- */
-public interface Logic {
- /**
- * Executes the command and returns the result.
- * @param commandText The command as entered by the user.
- * @return the result of the command execution.
- * @throws CommandException If an error occurs during command execution.
- * @throws ParseException If an error occurs during parsing.
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
-
- /**
- * Returns the AddressBook.
- *
- * @see seedu.address.model.Model#getAddressBook()
- */
- ReadOnlyAddressBook getAddressBook();
-
- /** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Set the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
deleted file mode 100644
index 71656d7c5c8..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Adds a person to the address book.
- */
-public class AddCommand extends Command {
-
- public static final String COMMAND_WORD = "add";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
- + PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
- + PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
-
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
-
- private final Person toAdd;
-
- /**
- * Creates an AddCommand to add the specified {@code Person}
- */
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
-
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.addPerson(toAdd);
- return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddCommand // instanceof handles nulls
- && toAdd.equals(((AddCommand) other).toAdd));
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
deleted file mode 100644
index 9c86b1fa6e4..00000000000
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-
-/**
- * Clears the address book.
- */
-public class ClearCommand extends Command {
-
- public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
deleted file mode 100644
index 02fd256acba..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Deletes a person identified using it's displayed index from the address book.
- */
-public class DeleteCommand extends Command {
-
- public static final String COMMAND_WORD = "delete";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
-
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
-
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
deleted file mode 100644
index 7e36114902f..00000000000
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Edits the details of an existing person in the address book.
- */
-public class EditCommand extends Command {
-
- public static final String COMMAND_WORD = "edit";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
- + "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
-
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
- public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
-
- private final Index index;
- private final EditPersonDescriptor editPersonDescriptor;
-
- /**
- * @param index of the person in the filtered person list to edit
- * @param editPersonDescriptor details to edit the person with
- */
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
-
- this.index = index;
- this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
-
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
-
- /**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
- * edited with {@code editPersonDescriptor}.
- */
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
-
- Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
- Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
- }
-
- /**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
- */
- public static class EditPersonDescriptor {
- private Name name;
- private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
-
- public EditPersonDescriptor() {}
-
- /**
- * Copy constructor.
- * A defensive copy of {@code tags} is used internally.
- */
- public EditPersonDescriptor(EditPersonDescriptor toCopy) {
- setName(toCopy.name);
- setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
- }
-
- /**
- * Returns true if at least one field is edited.
- */
- public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
- }
-
- public void setName(Name name) {
- this.name = name;
- }
-
- public Optional getName() {
- return Optional.ofNullable(name);
- }
-
- public void setPhone(Phone phone) {
- this.phone = phone;
- }
-
- public Optional getPhone() {
- return Optional.ofNullable(phone);
- }
-
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- public Optional getAddress() {
- return Optional.ofNullable(address);
- }
-
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
- }
-
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- // state check
- EditPersonDescriptor e = (EditPersonDescriptor) other;
-
- return getName().equals(e.getName())
- && getPhone().equals(e.getPhone())
- && getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
- }
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index d6b19b0a0de..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof FindCommand // instanceof handles nulls
- && predicate.equals(((FindCommand) other).predicate)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
deleted file mode 100644
index 3b8bfa035e8..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new AddCommand object
- */
-public class AddCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the AddCommand
- * and returns an AddCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
-
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
-
- return new AddCommand(person);
- }
-
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
deleted file mode 100644
index 75b1a9bf119..00000000000
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package seedu.address.logic.parser;
-
-/**
- * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
- */
-public class CliSyntax {
-
- /* Prefix definitions */
- public static final Prefix PREFIX_NAME = new Prefix("n/");
- public static final Prefix PREFIX_PHONE = new Prefix("p/");
- public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
- public static final Prefix PREFIX_TAG = new Prefix("t/");
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
deleted file mode 100644
index 845644b7dea..00000000000
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new EditCommand object
- */
-public class EditCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the EditCommand
- * and returns an EditCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public EditCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- Index index;
-
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
- }
-
- EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
- if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
- editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
- }
- if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
- editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
- }
- if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
- editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
- }
- if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
- editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
- }
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
-
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
- }
-
- return new EditCommand(index, editPersonDescriptor);
- }
-
- /**
- * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
- * If {@code tags} contain only one element which is an empty string, it will be parsed into a
- * {@code Set} containing zero tags.
- */
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
- assert tags != null;
-
- if (tags.isEmpty()) {
- return Optional.empty();
- }
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
deleted file mode 100644
index 4fb71f23103..00000000000
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import java.util.Arrays;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Parses input arguments and creates a new FindCommand object
- */
-public class FindCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the FindCommand
- * and returns a FindCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
- }
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
deleted file mode 100644
index b117acb9c55..00000000000
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Contains utility methods used for parsing strings in the various *Parser classes.
- */
-public class ParserUtil {
-
- public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
-
- /**
- * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
- * trimmed.
- * @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
- */
- public static Index parseIndex(String oneBasedIndex) throws ParseException {
- String trimmedIndex = oneBasedIndex.trim();
- if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
- throw new ParseException(MESSAGE_INVALID_INDEX);
- }
- return Index.fromOneBased(Integer.parseInt(trimmedIndex));
- }
-
- /**
- * Parses a {@code String name} into a {@code Name}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code name} is invalid.
- */
- public static Name parseName(String name) throws ParseException {
- requireNonNull(name);
- String trimmedName = name.trim();
- if (!Name.isValidName(trimmedName)) {
- throw new ParseException(Name.MESSAGE_CONSTRAINTS);
- }
- return new Name(trimmedName);
- }
-
- /**
- * Parses a {@code String phone} into a {@code Phone}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code phone} is invalid.
- */
- public static Phone parsePhone(String phone) throws ParseException {
- requireNonNull(phone);
- String trimmedPhone = phone.trim();
- if (!Phone.isValidPhone(trimmedPhone)) {
- throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
- }
- return new Phone(trimmedPhone);
- }
-
- /**
- * Parses a {@code String address} into an {@code Address}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code address} is invalid.
- */
- public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
- String trimmedAddress = address.trim();
- if (!Address.isValidAddress(trimmedAddress)) {
- throw new ParseException(Address.MESSAGE_CONSTRAINTS);
- }
- return new Address(trimmedAddress);
- }
-
- /**
- * Parses a {@code String email} into an {@code Email}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code email} is invalid.
- */
- public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
- String trimmedEmail = email.trim();
- if (!Email.isValidEmail(trimmedEmail)) {
- throw new ParseException(Email.MESSAGE_CONSTRAINTS);
- }
- return new Email(trimmedEmail);
- }
-
- /**
- * Parses a {@code String tag} into a {@code Tag}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code tag} is invalid.
- */
- public static Tag parseTag(String tag) throws ParseException {
- requireNonNull(tag);
- String trimmedTag = tag.trim();
- if (!Tag.isValidTagName(trimmedTag)) {
- throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(trimmedTag);
- }
-
- /**
- * Parses {@code Collection tags} into a {@code Set}.
- */
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
- }
- return tagSet;
- }
-}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
deleted file mode 100644
index 1a943a0781a..00000000000
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
-
-/**
- * Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
- */
-public class AddressBook implements ReadOnlyAddressBook {
-
- private final UniquePersonList persons;
-
- /*
- * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
- * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
- *
- * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
- * among constructors.
- */
- {
- persons = new UniquePersonList();
- }
-
- public AddressBook() {}
-
- /**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
- */
- public AddressBook(ReadOnlyAddressBook toBeCopied) {
- this();
- resetData(toBeCopied);
- }
-
- //// list overwrite operations
-
- /**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
- }
-
- /**
- * Resets the existing data of this {@code AddressBook} with {@code newData}.
- */
- public void resetData(ReadOnlyAddressBook newData) {
- requireNonNull(newData);
-
- setPersons(newData.getPersonList());
- }
-
- //// person-level operations
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
- }
-
- /**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
- */
- public void addPerson(Person p) {
- persons.add(p);
- }
-
- /**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
-
- persons.setPerson(target, editedPerson);
- }
-
- /**
- * Removes {@code key} from this {@code AddressBook}.
- * {@code key} must exist in the address book.
- */
- public void removePerson(Person key) {
- persons.remove(key);
- }
-
- //// util methods
-
- @Override
- public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
- // TODO: refine later
- }
-
- @Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddressBook // instanceof handles nulls
- && persons.equals(((AddressBook) other).persons));
- }
-
- @Override
- public int hashCode() {
- return persons.hashCode();
- }
-}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
deleted file mode 100644
index d54df471c1f..00000000000
--- a/src/main/java/seedu/address/model/Model.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package seedu.address.model;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
-
-/**
- * The API of the Model component.
- */
-public interface Model {
- /** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
-
- /**
- * Replaces user prefs data with the data in {@code userPrefs}.
- */
- void setUserPrefs(ReadOnlyUserPrefs userPrefs);
-
- /**
- * Returns the user prefs.
- */
- ReadOnlyUserPrefs getUserPrefs();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Sets the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Sets the user prefs' address book file path.
- */
- void setAddressBookFilePath(Path addressBookFilePath);
-
- /**
- * Replaces address book data with the data in {@code addressBook}.
- */
- void setAddressBook(ReadOnlyAddressBook addressBook);
-
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- boolean hasPerson(Person person);
-
- /**
- * Deletes the given person.
- * The person must exist in the address book.
- */
- void deletePerson(Person target);
-
- /**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
- */
- void addPerson(Person person);
-
- /**
- * Replaces the given person {@code target} with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- void setPerson(Person target, Person editedPerson);
-
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
-
- /**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
- * @throws NullPointerException if {@code predicate} is null.
- */
- void updateFilteredPersonList(Predicate predicate);
-}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
deleted file mode 100644
index 86c1df298d7..00000000000
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import javafx.collections.transformation.FilteredList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
-
-/**
- * Represents the in-memory model of the address book data.
- */
-public class ModelManager implements Model {
- private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
- private final AddressBook addressBook;
- private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
-
- /**
- * Initializes a ModelManager with the given addressBook and userPrefs.
- */
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
-
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
-
- this.addressBook = new AddressBook(addressBook);
- this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
- }
-
- public ModelManager() {
- this(new AddressBook(), new UserPrefs());
- }
-
- //=========== UserPrefs ==================================================================================
-
- @Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- requireNonNull(userPrefs);
- this.userPrefs.resetData(userPrefs);
- }
-
- @Override
- public ReadOnlyUserPrefs getUserPrefs() {
- return userPrefs;
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return userPrefs.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- requireNonNull(guiSettings);
- userPrefs.setGuiSettings(guiSettings);
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return userPrefs.getAddressBookFilePath();
- }
-
- @Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- userPrefs.setAddressBookFilePath(addressBookFilePath);
- }
-
- //=========== AddressBook ================================================================================
-
- @Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
- }
-
- @Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
- }
-
- @Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
- }
-
- @Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- }
-
- @Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- addressBook.setPerson(target, editedPerson);
- }
-
- //=========== Filtered Person List Accessors =============================================================
-
- /**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
- */
- @Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
- }
-
- @Override
- public void updateFilteredPersonList(Predicate predicate) {
- requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
- }
-
- @Override
- public boolean equals(Object obj) {
- // short circuit if same object
- if (obj == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(obj instanceof ModelManager)) {
- return false;
- }
-
- // state check
- ModelManager other = (ModelManager) obj;
- return addressBook.equals(other.addressBook)
- && userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
- }
-
-}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
deleted file mode 100644
index 6ddc2cd9a29..00000000000
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package seedu.address.model;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-
-/**
- * Unmodifiable view of an address book
- */
-public interface ReadOnlyAddressBook {
-
- /**
- * Returns an unmodifiable view of the persons list.
- * This list will not contain any duplicate persons.
- */
- ObservableList getPersonList();
-
-}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
deleted file mode 100644
index 60472ca22a0..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's address in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
- */
-public class Address {
-
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
-
- /*
- * The first character of the address must not be a whitespace,
- * otherwise " " (a blank string) becomes a valid input.
- */
- public static final String VALIDATION_REGEX = "[^\\s].*";
-
- public final String value;
-
- /**
- * Constructs an {@code Address}.
- *
- * @param address A valid address.
- */
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
- }
-
- /**
- * Returns true if a given string is a valid email.
- */
- public static boolean isValidAddress(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Address // instanceof handles nulls
- && value.equals(((Address) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
deleted file mode 100644
index f866e7133de..00000000000
--- a/src/main/java/seedu/address/model/person/Email.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's email in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
- */
-public class Email {
-
- private static final String SPECIAL_CHARACTERS = "+_.-";
- public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
- + "and adhere to the following constraints:\n"
- + "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
- + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special "
- + "characters.\n"
- + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels "
- + "separated by periods.\n"
- + "The domain name must:\n"
- + " - end with a domain label at least 2 characters long\n"
- + " - have each domain label start and end with alphanumeric characters\n"
- + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
- // alphanumeric and special characters
- private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
- private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
- + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE
- + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
- private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
- private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
- public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
-
- public final String value;
-
- /**
- * Constructs an {@code Email}.
- *
- * @param email A valid email address.
- */
- public Email(String email) {
- requireNonNull(email);
- checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
- value = email;
- }
-
- /**
- * Returns if a given string is a valid email.
- */
- public static boolean isValidEmail(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Email // instanceof handles nulls
- && value.equals(((Email) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
deleted file mode 100644
index 8ff1d83fe89..00000000000
--- a/src/main/java/seedu/address/model/person/Person.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package seedu.address.model.person;
-
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-import seedu.address.model.tag.Tag;
-
-/**
- * Represents a Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
- */
-public class Person {
-
- // Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
-
- // Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
-
- /**
- * Every field must be present and not null.
- */
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- this.tags.addAll(tags);
- }
-
- public Name getName() {
- return name;
- }
-
- public Phone getPhone() {
- return phone;
- }
-
- public Email getEmail() {
- return email;
- }
-
- public Address getAddress() {
- return address;
- }
-
- /**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- */
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
- }
-
- /**
- * Returns true if both persons have the same name.
- * This defines a weaker notion of equality between two persons.
- */
- public boolean isSamePerson(Person otherPerson) {
- if (otherPerson == this) {
- return true;
- }
-
- return otherPerson != null
- && otherPerson.getName().equals(getName());
- }
-
- /**
- * Returns true if both persons have the same identity and data fields.
- * This defines a stronger notion of equality between two persons.
- */
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- if (!(other instanceof Person)) {
- return false;
- }
-
- Person otherPerson = (Person) other;
- return otherPerson.getName().equals(getName())
- && otherPerson.getPhone().equals(getPhone())
- && otherPerson.getEmail().equals(getEmail())
- && otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
- }
-
- @Override
- public int hashCode() {
- // use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append(getName())
- .append("; Phone: ")
- .append(getPhone())
- .append("; Email: ")
- .append(getEmail())
- .append("; Address: ")
- .append(getAddress());
-
- Set tags = getTags();
- if (!tags.isEmpty()) {
- builder.append("; Tags: ");
- tags.forEach(builder::append);
- }
- return builder.toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
deleted file mode 100644
index 872c76b382f..00000000000
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's phone number in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
- */
-public class Phone {
-
-
- public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
- public final String value;
-
- /**
- * Constructs a {@code Phone}.
- *
- * @param phone A valid phone number.
- */
- public Phone(String phone) {
- requireNonNull(phone);
- checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
- value = phone;
- }
-
- /**
- * Returns true if a given string is a valid phone number.
- */
- public static boolean isValidPhone(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Phone // instanceof handles nulls
- && value.equals(((Phone) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
deleted file mode 100644
index 0fee4fe57e6..00000000000
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Iterator;
-import java.util.List;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-import seedu.address.model.person.exceptions.PersonNotFoundException;
-
-/**
- * A list of persons that enforces uniqueness between its elements and does not allow nulls.
- * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of
- * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is
- * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so
- * as to ensure that the person with exactly the same fields will be removed.
- *
- * Supports a minimal set of list operations.
- *
- * @see Person#isSamePerson(Person)
- */
-public class UniquePersonList implements Iterable {
-
- private final ObservableList internalList = FXCollections.observableArrayList();
- private final ObservableList internalUnmodifiableList =
- FXCollections.unmodifiableObservableList(internalList);
-
- /**
- * Returns true if the list contains an equivalent person as the given argument.
- */
- public boolean contains(Person toCheck) {
- requireNonNull(toCheck);
- return internalList.stream().anyMatch(toCheck::isSamePerson);
- }
-
- /**
- * Adds a person to the list.
- * The person must not already exist in the list.
- */
- public void add(Person toAdd) {
- requireNonNull(toAdd);
- if (contains(toAdd)) {
- throw new DuplicatePersonException();
- }
- internalList.add(toAdd);
- }
-
- /**
- * Replaces the person {@code target} in the list with {@code editedPerson}.
- * {@code target} must exist in the list.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the list.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- int index = internalList.indexOf(target);
- if (index == -1) {
- throw new PersonNotFoundException();
- }
-
- if (!target.isSamePerson(editedPerson) && contains(editedPerson)) {
- throw new DuplicatePersonException();
- }
-
- internalList.set(index, editedPerson);
- }
-
- /**
- * Removes the equivalent person from the list.
- * The person must exist in the list.
- */
- public void remove(Person toRemove) {
- requireNonNull(toRemove);
- if (!internalList.remove(toRemove)) {
- throw new PersonNotFoundException();
- }
- }
-
- public void setPersons(UniquePersonList replacement) {
- requireNonNull(replacement);
- internalList.setAll(replacement.internalList);
- }
-
- /**
- * Replaces the contents of this list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- requireAllNonNull(persons);
- if (!personsAreUnique(persons)) {
- throw new DuplicatePersonException();
- }
-
- internalList.setAll(persons);
- }
-
- /**
- * Returns the backing list as an unmodifiable {@code ObservableList}.
- */
- public ObservableList asUnmodifiableObservableList() {
- return internalUnmodifiableList;
- }
-
- @Override
- public Iterator iterator() {
- return internalList.iterator();
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof UniquePersonList // instanceof handles nulls
- && internalList.equals(((UniquePersonList) other).internalList));
- }
-
- @Override
- public int hashCode() {
- return internalList.hashCode();
- }
-
- /**
- * Returns true if {@code persons} contains only unique persons.
- */
- private boolean personsAreUnique(List persons) {
- for (int i = 0; i < persons.size() - 1; i++) {
- for (int j = i + 1; j < persons.size(); j++) {
- if (persons.get(i).isSamePerson(persons.get(j))) {
- return false;
- }
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
deleted file mode 100644
index d7290f59442..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
- * identity).
- */
-public class DuplicatePersonException extends RuntimeException {
- public DuplicatePersonException() {
- super("Operation would result in duplicate persons");
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
deleted file mode 100644
index fa764426ca7..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation is unable to find the specified person.
- */
-public class PersonNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
deleted file mode 100644
index b0ea7e7dad7..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package seedu.address.model.tag;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Tag in the address book.
- * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
- */
-public class Tag {
-
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
-
- /**
- * Constructs a {@code Tag}.
- *
- * @param tagName A valid tag name.
- */
- public Tag(String tagName) {
- requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
- this.tagName = tagName;
- }
-
- /**
- * Returns true if a given string is a valid tag name.
- */
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Tag // instanceof handles nulls
- && tagName.equals(((Tag) other).tagName)); // state check
- }
-
- @Override
- public int hashCode() {
- return tagName.hashCode();
- }
-
- /**
- * Format state as text for viewing.
- */
- public String toString() {
- return '[' + tagName + ']';
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
deleted file mode 100644
index 1806da4facf..00000000000
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.model.util;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Contains utility methods for populating {@code AddressBook} with sample data.
- */
-public class SampleDataUtil {
- public static Person[] getSamplePersons() {
- return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
- };
- }
-
- public static ReadOnlyAddressBook getSampleAddressBook() {
- AddressBook sampleAb = new AddressBook();
- for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
- }
- return sampleAb;
- }
-
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
deleted file mode 100644
index 4599182b3f9..00000000000
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * Represents a storage for {@link seedu.address.model.AddressBook}.
- */
-public interface AddressBookStorage {
-
- /**
- * Returns the file path of the data file.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns AddressBook data as a {@link ReadOnlyAddressBook}.
- * Returns {@code Optional.empty()} if storage file is not found.
- * @throws DataConversionException if the data in storage is not in the expected format.
- * @throws IOException if there was any problem when reading from the storage.
- */
- Optional readAddressBook() throws DataConversionException, IOException;
-
- /**
- * @see #getAddressBookFilePath()
- */
- Optional readAddressBook(Path filePath) throws DataConversionException, IOException;
-
- /**
- * Saves the given {@link ReadOnlyAddressBook} to the storage.
- * @param addressBook cannot be null.
- * @throws IOException if there was any problem writing to the file.
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
- /**
- * @see #saveAddressBook(ReadOnlyAddressBook)
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
deleted file mode 100644
index a6321cec2ea..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package seedu.address.storage;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Person}.
- */
-class JsonAdaptedPerson {
-
- public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
-
- private final String name;
- private final String phone;
- private final String email;
- private final String address;
- private final List tagged = new ArrayList<>();
-
- /**
- * Constructs a {@code JsonAdaptedPerson} with the given person details.
- */
- @JsonCreator
- public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tagged") List tagged) {
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- if (tagged != null) {
- this.tagged.addAll(tagged);
- }
- }
-
- /**
- * Converts a given {@code Person} into this class for Jackson use.
- */
- public JsonAdaptedPerson(Person source) {
- name = source.getName().fullName;
- phone = source.getPhone().value;
- email = source.getEmail().value;
- address = source.getAddress().value;
- tagged.addAll(source.getTags().stream()
- .map(JsonAdaptedTag::new)
- .collect(Collectors.toList()));
- }
-
- /**
- * Converts this Jackson-friendly adapted person object into the model's {@code Person} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted person.
- */
- public Person toModelType() throws IllegalValueException {
- final List personTags = new ArrayList<>();
- for (JsonAdaptedTag tag : tagged) {
- personTags.add(tag.toModelType());
- }
-
- if (name == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
- }
- if (!Name.isValidName(name)) {
- throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
- }
- final Name modelName = new Name(name);
-
- if (phone == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
- }
- if (!Phone.isValidPhone(phone)) {
- throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
- }
- final Phone modelPhone = new Phone(phone);
-
- if (email == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
- }
- if (!Email.isValidEmail(email)) {
- throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
- }
- final Email modelEmail = new Email(email);
-
- if (address == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
- }
- if (!Address.isValidAddress(address)) {
- throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
- }
- final Address modelAddress = new Address(address);
-
- final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
deleted file mode 100644
index 0df22bdb754..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package seedu.address.storage;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Tag}.
- */
-class JsonAdaptedTag {
-
- private final String tagName;
-
- /**
- * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
- */
- @JsonCreator
- public JsonAdaptedTag(String tagName) {
- this.tagName = tagName;
- }
-
- /**
- * Converts a given {@code Tag} into this class for Jackson use.
- */
- public JsonAdaptedTag(Tag source) {
- tagName = source.tagName;
- }
-
- @JsonValue
- public String getTagName() {
- return tagName;
- }
-
- /**
- * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
- */
- public Tag toModelType() throws IllegalValueException {
- if (!Tag.isValidTagName(tagName)) {
- throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(tagName);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
deleted file mode 100644
index dfab9daaa0d..00000000000
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package seedu.address.storage;
-
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * A class to access AddressBook data stored as a json file on the hard disk.
- */
-public class JsonAddressBookStorage implements AddressBookStorage {
-
- private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class);
-
- private Path filePath;
-
- public JsonAddressBookStorage(Path filePath) {
- this.filePath = filePath;
- }
-
- public Path getAddressBookFilePath() {
- return filePath;
- }
-
- @Override
- public Optional readAddressBook() throws DataConversionException {
- return readAddressBook(filePath);
- }
-
- /**
- * Similar to {@link #readAddressBook()}.
- *
- * @param filePath location of the data. Cannot be null.
- * @throws DataConversionException if the file is not in the correct format.
- */
- public Optional readAddressBook(Path filePath) throws DataConversionException {
- requireNonNull(filePath);
-
- Optional jsonAddressBook = JsonUtil.readJsonFile(
- filePath, JsonSerializableAddressBook.class);
- if (!jsonAddressBook.isPresent()) {
- return Optional.empty();
- }
-
- try {
- return Optional.of(jsonAddressBook.get().toModelType());
- } catch (IllegalValueException ive) {
- logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
- throw new DataConversionException(ive);
- }
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, filePath);
- }
-
- /**
- * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}.
- *
- * @param filePath location of the data. Cannot be null.
- */
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- requireNonNull(addressBook);
- requireNonNull(filePath);
-
- FileUtil.createIfMissing(filePath);
- JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
deleted file mode 100644
index 5efd834091d..00000000000
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.storage;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonRootName;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-
-/**
- * An Immutable AddressBook that is serializable to JSON format.
- */
-@JsonRootName(value = "addressbook")
-class JsonSerializableAddressBook {
-
- public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
-
- private final List persons = new ArrayList<>();
-
- /**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
- */
- @JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
- this.persons.addAll(persons);
- }
-
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}.
- */
- public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
- persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList()));
- }
-
- /**
- * Converts this address book into the model's {@code AddressBook} object.
- *
- * @throws IllegalValueException if there were any data constraints violated.
- */
- public AddressBook toModelType() throws IllegalValueException {
- AddressBook addressBook = new AddressBook();
- for (JsonAdaptedPerson jsonAdaptedPerson : persons) {
- Person person = jsonAdaptedPerson.toModelType();
- if (addressBook.hasPerson(person)) {
- throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
- }
- addressBook.addPerson(person);
- }
- return addressBook;
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
deleted file mode 100644
index beda8bd9f11..00000000000
--- a/src/main/java/seedu/address/storage/Storage.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * API of the Storage component
- */
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
-
- @Override
- Optional readUserPrefs() throws DataConversionException, IOException;
-
- @Override
- void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
-
- @Override
- Path getAddressBookFilePath();
-
- @Override
- Optional readAddressBook() throws DataConversionException, IOException;
-
- @Override
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
deleted file mode 100644
index 6cfa0162164..00000000000
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * Manages storage of AddressBook data in local storage.
- */
-public class StorageManager implements Storage {
-
- private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
- private AddressBookStorage addressBookStorage;
- private UserPrefsStorage userPrefsStorage;
-
- /**
- * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
- */
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
- this.addressBookStorage = addressBookStorage;
- this.userPrefsStorage = userPrefsStorage;
- }
-
- // ================ UserPrefs methods ==============================
-
- @Override
- public Path getUserPrefsFilePath() {
- return userPrefsStorage.getUserPrefsFilePath();
- }
-
- @Override
- public Optional readUserPrefs() throws DataConversionException, IOException {
- return userPrefsStorage.readUserPrefs();
- }
-
- @Override
- public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
- userPrefsStorage.saveUserPrefs(userPrefs);
- }
-
-
- // ================ AddressBook methods ==============================
-
- @Override
- public Path getAddressBookFilePath() {
- return addressBookStorage.getAddressBookFilePath();
- }
-
- @Override
- public Optional readAddressBook() throws DataConversionException, IOException {
- return readAddressBook(addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public Optional readAddressBook(Path filePath) throws DataConversionException, IOException {
- logger.fine("Attempting to read data from file: " + filePath);
- return addressBookStorage.readAddressBook(filePath);
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- logger.fine("Attempting to write to data file: " + filePath);
- addressBookStorage.saveAddressBook(addressBook, filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
deleted file mode 100644
index 7fc927bc5d9..00000000000
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package seedu.address.ui;
-
-import java.util.Comparator;
-
-import javafx.fxml.FXML;
-import javafx.scene.control.Label;
-import javafx.scene.layout.FlowPane;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Region;
-import seedu.address.model.person.Person;
-
-/**
- * An UI component that displays information of a {@code Person}.
- */
-public class PersonCard extends UiPart {
-
- private static final String FXML = "PersonListCard.fxml";
-
- /**
- * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
- * As a consequence, UI elements' variable names cannot be set to such keywords
- * or an exception will be thrown by JavaFX during runtime.
- *
- * @see The issue on AddressBook level 4
- */
-
- public final Person person;
-
- @FXML
- private HBox cardPane;
- @FXML
- private Label name;
- @FXML
- private Label id;
- @FXML
- private Label phone;
- @FXML
- private Label address;
- @FXML
- private Label email;
- @FXML
- private FlowPane tags;
-
- /**
- * Creates a {@code PersonCode} with the given {@code Person} and index to display.
- */
- public PersonCard(Person person, int displayedIndex) {
- super(FXML);
- this.person = person;
- id.setText(displayedIndex + ". ");
- name.setText(person.getName().fullName);
- phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
- person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof PersonCard)) {
- return false;
- }
-
- // state check
- PersonCard card = (PersonCard) other;
- return id.getText().equals(card.id.getText())
- && person.equals(card.person);
- }
-}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
deleted file mode 100644
index f4c501a897b..00000000000
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package seedu.address.ui;
-
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.layout.Region;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
-
-/**
- * Panel containing the list of persons.
- */
-public class PersonListPanel extends UiPart {
- private static final String FXML = "PersonListPanel.fxml";
- private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
-
- @FXML
- private ListView personListView;
-
- /**
- * Creates a {@code PersonListPanel} with the given {@code ObservableList}.
- */
- public PersonListPanel(ObservableList personList) {
- super(FXML);
- personListView.setItems(personList);
- personListView.setCellFactory(listView -> new PersonListViewCell());
- }
-
- /**
- * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
- */
- class PersonListViewCell extends ListCell {
- @Override
- protected void updateItem(Person person, boolean empty) {
- super.updateItem(person, empty);
-
- if (empty || person == null) {
- setGraphic(null);
- setText(null);
- } else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
- }
- }
- }
-
-}
diff --git a/src/main/resources/images/clean.png b/src/main/resources/images/clean.png
new file mode 100644
index 00000000000..935ed58bf72
Binary files /dev/null and b/src/main/resources/images/clean.png differ
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/exercise_tracker_32.png
similarity index 100%
rename from src/main/resources/images/address_book_32.png
rename to src/main/resources/images/exercise_tracker_32.png
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..921a5c80113 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -90,7 +90,7 @@
.list-view {
-fx-background-insets: 0;
-fx-padding: 0;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #262e42;
}
.list-cell {
@@ -100,19 +100,19 @@
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: #181b24;
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
+ -fx-background-color: #272d3b;
}
.list-cell:filled:selected {
- -fx-background-color: #424d5f;
+ -fx-background-color: #738FA7;
}
.list-cell:filled:selected #cardPane {
- -fx-border-color: #3e7b91;
+ -fx-border-color: #0C4160;
-fx-border-width: 1;
}
@@ -123,6 +123,7 @@
.cell_big_label {
-fx-font-family: "Segoe UI Semibold";
-fx-font-size: 16px;
+ -fx-font-weight: bold;
-fx-text-fill: #010504;
}
@@ -133,21 +134,21 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #262e42;
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-background-color: #071330;
+ -fx-border-color: #071330;
-fx-border-top-width: 1px;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #071330;
}
.result-display {
- -fx-background-color: transparent;
+ -fx-background-color:transparent;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
@@ -157,6 +158,14 @@
-fx-text-fill: black !important;
}
+.saved-exercise {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
+}
+
+
.status-bar .label {
-fx-font-family: "Segoe UI Light";
-fx-text-fill: white;
@@ -165,8 +174,8 @@
}
.status-bar-with-border {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-background-color: #071330;
+ -fx-border-color: #071330;
-fx-border-width: 1px;
}
@@ -175,17 +184,17 @@
}
.grid-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #071330;
+ -fx-border-color: #071330;
-fx-border-width: 1px;
}
.grid-pane .stack-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #071330;
}
.context-menu {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: #071330;
}
.context-menu .label {
@@ -193,7 +202,7 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #071330;
}
.menu-bar .label {
@@ -217,7 +226,7 @@
-fx-border-color: #e2e2e2;
-fx-border-width: 2;
-fx-background-radius: 0;
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #071330;
-fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
-fx-font-size: 11pt;
-fx-text-fill: #d8d8d8;
@@ -225,7 +234,7 @@
}
.button:hover {
- -fx-background-color: #3a3a3a;
+ -fx-background-color: #071330;
}
.button:pressed, .button:default:hover:pressed {
@@ -282,11 +291,11 @@
}
.scroll-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #262e42;
}
.scroll-bar .thumb {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: derive(#262e42, 50%);
-fx-background-insets: 3;
}
@@ -320,7 +329,7 @@
#commandTextField {
-fx-background-color: transparent #383838 transparent #383838;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-color: #0C4160 #0C4160 #ffffff #0C4160;
-fx-border-insets: 0;
-fx-border-width: 1;
-fx-font-family: "Segoe UI Light";
@@ -328,12 +337,17 @@
-fx-text-fill: white;
}
-#filterField, #personListPanel, #personWebpage {
+#filterField, #exerciseListPanel, #exerciseWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-color: transparent, #262e42, transparent, #262e42;
+ -fx-background-radius: 0;
+}
+
+#savedExerciseList .content {
+ -fx-background-color: transparent, #262e42, transparent, #262e42;
-fx-background-radius: 0;
}
@@ -343,10 +357,26 @@
}
#tags .label {
- -fx-text-fill: white;
+ -fx-text-fill: black;
-fx-background-color: #3e7b91;
-fx-padding: 1 3 1 3;
-fx-border-radius: 2;
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+.date_label {
+ -fx-background-color: dimgray;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-border-color: black;
+ -fx-background-radius: 2;
+ -fx-alignment: center;
+}
+
+#dates .label {
+ -fx-text-fill: white;
+ -fx-font-size: 15;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-weight: bold;
+}
diff --git a/src/main/resources/view/ExerciseListCard.fxml b/src/main/resources/view/ExerciseListCard.fxml
new file mode 100644
index 00000000000..828546131d0
--- /dev/null
+++ b/src/main/resources/view/ExerciseListCard.fxml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/ExerciseListPanel.fxml
similarity index 77%
rename from src/main/resources/view/PersonListPanel.fxml
rename to src/main/resources/view/ExerciseListPanel.fxml
index 8836d323cc5..6a05d03eb18 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/ExerciseListPanel.fxml
@@ -4,5 +4,5 @@
-
+
diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css
index bfe82a85964..9f40e5e48c2 100644
--- a/src/main/resources/view/Extensions.css
+++ b/src/main/resources/view/Extensions.css
@@ -5,7 +5,7 @@
.list-cell:empty {
/* Empty cells will not have alternating colours */
- -fx-background: #383838;
+ -fx-background: #262e42;
}
.tag-selector {
diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css
index 17e8a8722cd..a05e3198514 100644
--- a/src/main/resources/view/HelpWindow.css
+++ b/src/main/resources/view/HelpWindow.css
@@ -15,5 +15,22 @@
}
#helpMessageContainer {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #071330;
+}
+
+#helpMessage {
+ -fx-background-color: #181b24;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 13px;
+}
+
+.helpPara {
+ -fx-background-color: dimgray;
+ -fx-text-fill: white;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+#helpPara .content {
+ -fx-background-color: transparent, #262e42, transparent, #262e42;
+ -fx-background-radius: 0;
}
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index 5dea0adef70..3848490858a 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -5,11 +5,14 @@
+
+
+
-
+
@@ -19,18 +22,21 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index a431648f6c0..12aa14bbe1c 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -6,15 +6,15 @@
-
+
+
-
+
-
+
@@ -33,26 +33,30 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
deleted file mode 100644
index f08ea32ad55..00000000000
--- a/src/main/resources/view/PersonListCard.fxml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 58d5ad3dc56..edf9382ba0b 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -3,7 +3,6 @@
-
-
+
+
diff --git a/src/main/resources/view/SavedExerciseListWindow.fxml b/src/main/resources/view/SavedExerciseListWindow.fxml
new file mode 100644
index 00000000000..e7db9dbc884
--- /dev/null
+++ b/src/main/resources/view/SavedExerciseListWindow.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
deleted file mode 100644
index 6a4d2b7181c..00000000000
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "persons": [ {
- "name": "Valid Person",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- }, {
- "name": "Person With Invalid Phone Field",
- "phone": "948asdf2424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
-}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
deleted file mode 100644
index ccd21f7d1a9..00000000000
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
-}
diff --git a/src/test/data/JsonExerciseTrackerStorageTest/invalidAndValidExerciseExerciseTracker.json b/src/test/data/JsonExerciseTrackerStorageTest/invalidAndValidExerciseExerciseTracker.json
new file mode 100644
index 00000000000..e3e21880f4f
--- /dev/null
+++ b/src/test/data/JsonExerciseTrackerStorageTest/invalidAndValidExerciseExerciseTracker.json
@@ -0,0 +1,15 @@
+{
+ "exercises": [ {
+ "name": "Valid Exercise",
+ "weight": "9482424",
+ "sets": "hans@example.com",
+ "reps": "1",
+ "date": "01/01/2022"
+ }, {
+ "name": "Exercise With Invalid Weight Field",
+ "weight": "948asdf2424",
+ "sets": "hans@example.com",
+ "reps": "1",
+ "date": "01/01/2022"
+ } ]
+}
diff --git a/src/test/data/JsonExerciseTrackerStorageTest/invalidExerciseExerciseTracker.json b/src/test/data/JsonExerciseTrackerStorageTest/invalidExerciseExerciseTracker.json
new file mode 100644
index 00000000000..36797a0fac4
--- /dev/null
+++ b/src/test/data/JsonExerciseTrackerStorageTest/invalidExerciseExerciseTracker.json
@@ -0,0 +1,9 @@
+{
+ "exercises": [ {
+ "name": "Exercise with invalid name field: Ha!ns Mu@ster",
+ "weight": "20",
+ "sets": "1",
+ "reps": "1",
+ "date": "01/01/2022"
+ } ]
+}
diff --git a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json b/src/test/data/JsonExerciseTrackerStorageTest/notJsonFormatExerciseTracker.json
similarity index 100%
rename from src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json
rename to src/test/data/JsonExerciseTrackerStorageTest/notJsonFormatExerciseTracker.json
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
deleted file mode 100644
index 48831cc7674..00000000000
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "persons": [ {
- "name": "Alice Pauline",
- "phone": "94351253",
- "email": "alice@example.com",
- "address": "123, Jurong West Ave 6, #08-111",
- "tagged": [ "friends" ]
- }, {
- "name": "Alice Pauline",
- "phone": "94351253",
- "email": "pauline@example.com",
- "address": "4th street"
- } ]
-}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
deleted file mode 100644
index ad3f135ae42..00000000000
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "persons": [ {
- "name": "Hans Muster",
- "phone": "9482424",
- "email": "invalid@email!3e",
- "address": "4th street"
- } ]
-}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
deleted file mode 100644
index f10eddee12e..00000000000
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()",
- "persons" : [ {
- "name" : "Alice Pauline",
- "phone" : "94351253",
- "email" : "alice@example.com",
- "address" : "123, Jurong West Ave 6, #08-111",
- "tagged" : [ "friends" ]
- }, {
- "name" : "Benson Meier",
- "phone" : "98765432",
- "email" : "johnd@example.com",
- "address" : "311, Clementi Ave 2, #02-25",
- "tagged" : [ "owesMoney", "friends" ]
- }, {
- "name" : "Carl Kurz",
- "phone" : "95352563",
- "email" : "heinz@example.com",
- "address" : "wall street",
- "tagged" : [ ]
- }, {
- "name" : "Daniel Meier",
- "phone" : "87652533",
- "email" : "cornelia@example.com",
- "address" : "10th street",
- "tagged" : [ "friends" ]
- }, {
- "name" : "Elle Meyer",
- "phone" : "9482224",
- "email" : "werner@example.com",
- "address" : "michegan ave",
- "tagged" : [ ]
- }, {
- "name" : "Fiona Kunz",
- "phone" : "9482427",
- "email" : "lydia@example.com",
- "address" : "little tokyo",
- "tagged" : [ ]
- }, {
- "name" : "George Best",
- "phone" : "9482442",
- "email" : "anna@example.com",
- "address" : "4th street",
- "tagged" : [ ]
- } ]
-}
diff --git a/src/test/data/JsonSerializableExerciseTrackerTest/duplicateExerciseExerciseTracker.json b/src/test/data/JsonSerializableExerciseTrackerTest/duplicateExerciseExerciseTracker.json
new file mode 100644
index 00000000000..84186cfa5a3
--- /dev/null
+++ b/src/test/data/JsonSerializableExerciseTrackerTest/duplicateExerciseExerciseTracker.json
@@ -0,0 +1,15 @@
+{
+ "exercises": [ {
+ "name": "ABDUCTION",
+ "weight": "10",
+ "sets": "1",
+ "reps": "1",
+ "date": "11/01/2010"
+ }, {
+ "name": "ABDUCTION",
+ "weight": "10",
+ "sets": "1",
+ "reps": "1",
+ "date": "11/01/2010"
+ } ]
+}
diff --git a/src/test/data/JsonSerializableExerciseTrackerTest/invalidExerciseExerciseTracker.json b/src/test/data/JsonSerializableExerciseTrackerTest/invalidExerciseExerciseTracker.json
new file mode 100644
index 00000000000..960f30a5a63
--- /dev/null
+++ b/src/test/data/JsonSerializableExerciseTrackerTest/invalidExerciseExerciseTracker.json
@@ -0,0 +1,9 @@
+{
+ "exercises": [ {
+ "name": "Hans Muster",
+ "weight": "9482424",
+ "sets": "invalid@sets!3e",
+ "reps": "1",
+ "date": "01/01/2022"
+ } ]
+}
diff --git a/src/test/data/JsonSerializableExerciseTrackerTest/typicalExercisesExerciseTracker.json b/src/test/data/JsonSerializableExerciseTrackerTest/typicalExercisesExerciseTracker.json
new file mode 100644
index 00000000000..7fd60945e96
--- /dev/null
+++ b/src/test/data/JsonSerializableExerciseTrackerTest/typicalExercisesExerciseTracker.json
@@ -0,0 +1,46 @@
+{
+ "_comment": "ExerciseTracker save file which contains the same Exercise values as in TypicalExercises#getTypicalExerciseTracker()",
+ "exercises" : [ {
+ "name" : "ABDUCTION",
+ "weight" : "10",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "BICEP CURLS",
+ "weight" : "20",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "CALF RAISES",
+ "weight" : "30",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "DEADLIFT",
+ "weight" : "40",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "ELEVATED SQUATS",
+ "weight" : "50",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "FRONT SQUATS",
+ "weight" : "60",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ }, {
+ "name" : "GLUTE RAISES",
+ "weight" : "70",
+ "sets" : "1",
+ "reps" : "1",
+ "date" : "02/05/2022"
+ } ]
+}
diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json
index 1037548a9cd..99b855ce5f0 100644
--- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json
+++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json
@@ -9,5 +9,5 @@
"z" : 99
}
},
- "addressBookFilePath" : "addressbook.json"
+ "exerciseTrackerFilePath" : "exercisetracker.json"
}
diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json
index b819bed900a..6e5af82d5b6 100644
--- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json
+++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json
@@ -7,5 +7,5 @@
"y" : 100
}
},
- "addressBookFilePath" : "addressbook.json"
+ "exerciseTrackerFilePath" : "exercisetracker.json"
}
diff --git a/src/test/java/seedu/address/AppParametersTest.java b/src/test/java/gim/AppParametersTest.java
similarity index 98%
rename from src/test/java/seedu/address/AppParametersTest.java
rename to src/test/java/gim/AppParametersTest.java
index 61326b2d31a..acdde7dcde8 100644
--- a/src/test/java/seedu/address/AppParametersTest.java
+++ b/src/test/java/gim/AppParametersTest.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package gim;
import static org.junit.jupiter.api.Assertions.assertEquals;
diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/gim/commons/core/ConfigTest.java
similarity index 95%
rename from src/test/java/seedu/address/commons/core/ConfigTest.java
rename to src/test/java/gim/commons/core/ConfigTest.java
index 07cd7f73d53..1223feb5746 100644
--- a/src/test/java/seedu/address/commons/core/ConfigTest.java
+++ b/src/test/java/gim/commons/core/ConfigTest.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package gim.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/gim/commons/core/VersionTest.java
similarity index 98%
rename from src/test/java/seedu/address/commons/core/VersionTest.java
rename to src/test/java/gim/commons/core/VersionTest.java
index 495cd231554..afde57f6511 100644
--- a/src/test/java/seedu/address/commons/core/VersionTest.java
+++ b/src/test/java/gim/commons/core/VersionTest.java
@@ -1,8 +1,8 @@
-package seedu.address.commons.core;
+package gim.commons.core;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/seedu/address/commons/core/index/IndexTest.java b/src/test/java/gim/commons/core/index/IndexTest.java
similarity index 74%
rename from src/test/java/seedu/address/commons/core/index/IndexTest.java
rename to src/test/java/gim/commons/core/index/IndexTest.java
index a3ec6f8e747..e416f5bc8f1 100644
--- a/src/test/java/seedu/address/commons/core/index/IndexTest.java
+++ b/src/test/java/gim/commons/core/index/IndexTest.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.core.index;
+package gim.commons.core.index;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
@@ -39,22 +39,22 @@ public void createZeroBasedIndex() {
@Test
public void equals() {
- final Index fifthPersonIndex = Index.fromOneBased(5);
+ final Index fifthExerciseIndex = Index.fromOneBased(5);
// same values -> returns true
- assertTrue(fifthPersonIndex.equals(Index.fromOneBased(5)));
- assertTrue(fifthPersonIndex.equals(Index.fromZeroBased(4)));
+ assertTrue(fifthExerciseIndex.equals(Index.fromOneBased(5)));
+ assertTrue(fifthExerciseIndex.equals(Index.fromZeroBased(4)));
// same object -> returns true
- assertTrue(fifthPersonIndex.equals(fifthPersonIndex));
+ assertTrue(fifthExerciseIndex.equals(fifthExerciseIndex));
// null -> returns false
- assertFalse(fifthPersonIndex.equals(null));
+ assertFalse(fifthExerciseIndex.equals(null));
// different types -> returns false
- assertFalse(fifthPersonIndex.equals(5.0f));
+ assertFalse(fifthExerciseIndex.equals(5.0f));
// different index -> returns false
- assertFalse(fifthPersonIndex.equals(Index.fromOneBased(1)));
+ assertFalse(fifthExerciseIndex.equals(Index.fromOneBased(1)));
}
}
diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/gim/commons/util/AppUtilTest.java
similarity index 85%
rename from src/test/java/seedu/address/commons/util/AppUtilTest.java
rename to src/test/java/gim/commons/util/AppUtilTest.java
index 594de1e6365..01572d7294f 100644
--- a/src/test/java/seedu/address/commons/util/AppUtilTest.java
+++ b/src/test/java/gim/commons/util/AppUtilTest.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
@@ -9,7 +9,7 @@ public class AppUtilTest {
@Test
public void getImage_exitingImage() {
- assertNotNull(AppUtil.getImage("/images/address_book_32.png"));
+ assertNotNull(AppUtil.getImage("/images/exercise_tracker_32.png"));
}
@Test
diff --git a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java b/src/test/java/gim/commons/util/CollectionUtilTest.java
similarity index 96%
rename from src/test/java/seedu/address/commons/util/CollectionUtilTest.java
rename to src/test/java/gim/commons/util/CollectionUtilTest.java
index b467a3dc025..266ab51383d 100644
--- a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java
+++ b/src/test/java/gim/commons/util/CollectionUtilTest.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.commons.util.CollectionUtil.requireAllNonNull;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-import static seedu.address.testutil.Assert.assertThrows;
import java.util.Arrays;
import java.util.Collection;
diff --git a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java b/src/test/java/gim/commons/util/ConfigUtilTest.java
similarity index 94%
rename from src/test/java/seedu/address/commons/util/ConfigUtilTest.java
rename to src/test/java/gim/commons/util/ConfigUtilTest.java
index d2ab2839a52..e574994b04d 100644
--- a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java
+++ b/src/test/java/gim/commons/util/ConfigUtilTest.java
@@ -1,8 +1,8 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static seedu.address.testutil.Assert.assertThrows;
import java.io.IOException;
import java.nio.file.Path;
@@ -13,8 +13,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataConversionException;
+import gim.commons.core.Config;
+import gim.commons.exceptions.DataConversionException;
public class ConfigUtilTest {
diff --git a/src/test/java/seedu/address/commons/util/FileUtilTest.java b/src/test/java/gim/commons/util/FileUtilTest.java
similarity index 84%
rename from src/test/java/seedu/address/commons/util/FileUtilTest.java
rename to src/test/java/gim/commons/util/FileUtilTest.java
index 1fe5478c756..2946e191534 100644
--- a/src/test/java/seedu/address/commons/util/FileUtilTest.java
+++ b/src/test/java/gim/commons/util/FileUtilTest.java
@@ -1,8 +1,8 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/gim/commons/util/JsonUtilTest.java
similarity index 92%
rename from src/test/java/seedu/address/commons/util/JsonUtilTest.java
rename to src/test/java/gim/commons/util/JsonUtilTest.java
index d4907539dee..204b947eb7f 100644
--- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java
+++ b/src/test/java/gim/commons/util/JsonUtilTest.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package gim.commons.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -7,8 +7,8 @@
import org.junit.jupiter.api.Test;
-import seedu.address.testutil.SerializableTestClass;
-import seedu.address.testutil.TestUtil;
+import gim.testutil.SerializableTestClass;
+import gim.testutil.TestUtil;
/**
* Tests JSON Read and Write
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/gim/commons/util/StringUtilTest.java
similarity index 98%
rename from src/test/java/seedu/address/commons/util/StringUtilTest.java
rename to src/test/java/gim/commons/util/StringUtilTest.java
index c56d407bf3f..a04ef6c58d9 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/gim/commons/util/StringUtilTest.java
@@ -1,8 +1,8 @@
-package seedu.address.commons.util;
+package gim.commons.util;
+import static gim.testutil.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.testutil.Assert.assertThrows;
import java.io.FileNotFoundException;
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/gim/logic/LogicManagerTest.java
similarity index 62%
rename from src/test/java/seedu/address/logic/LogicManagerTest.java
rename to src/test/java/gim/logic/LogicManagerTest.java
index ad923ac249a..962f1f8b288 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/gim/logic/LogicManagerTest.java
@@ -1,14 +1,16 @@
-package seedu.address.logic;
-
+package gim.logic;
+
+import static gim.commons.core.Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX;
+import static gim.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static gim.logic.commands.CommandTestUtil.DATE_DESC;
+import static gim.logic.commands.CommandTestUtil.NAME_DESC_ARM_CURLS;
+import static gim.logic.commands.CommandTestUtil.REPS_DESC_ARM_CURLS;
+import static gim.logic.commands.CommandTestUtil.SETS_DESC_ARM_CURLS;
+import static gim.logic.commands.CommandTestUtil.VALID_DATE;
+import static gim.logic.commands.CommandTestUtil.WEIGHT_DESC_ARM_CURLS;
+import static gim.testutil.Assert.assertThrows;
+import static gim.testutil.TypicalExercises.ARM_CURLS;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
-import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
-import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
-import static seedu.address.testutil.Assert.assertThrows;
-import static seedu.address.testutil.TypicalPersons.AMY;
import java.io.IOException;
import java.nio.file.Path;
@@ -17,20 +19,22 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.StorageManager;
-import seedu.address.testutil.PersonBuilder;
+import gim.logic.commands.AddCommand;
+import gim.logic.commands.CommandResult;
+import gim.logic.commands.ListCommand;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.parser.exceptions.ParseException;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.UserPrefs;
+import gim.model.exercise.Exercise;
+import gim.storage.JsonExerciseTrackerStorage;
+import gim.storage.JsonUserPrefsStorage;
+import gim.storage.StorageManager;
+import gim.testutil.ExerciseBuilder;
+
+
public class LogicManagerTest {
private static final IOException DUMMY_IO_EXCEPTION = new IOException("dummy exception");
@@ -43,10 +47,10 @@ public class LogicManagerTest {
@BeforeEach
public void setUp() {
- JsonAddressBookStorage addressBookStorage =
- new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
+ JsonExerciseTrackerStorage exerciseTrackerStorage =
+ new JsonExerciseTrackerStorage(temporaryFolder.resolve("exerciseTracker.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ StorageManager storage = new StorageManager(exerciseTrackerStorage, userPrefsStorage);
logic = new LogicManager(model, storage);
}
@@ -58,8 +62,8 @@ public void execute_invalidCommandFormat_throwsParseException() {
@Test
public void execute_commandExecutionError_throwsCommandException() {
- String deleteCommand = "delete 9";
- assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ String deleteCommand = ":del 9";
+ assertCommandException(deleteCommand, MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
}
@Test
@@ -70,27 +74,28 @@ public void execute_validCommand_success() throws Exception {
@Test
public void execute_storageThrowsIoException_throwsCommandException() {
- // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub
- JsonAddressBookStorage addressBookStorage =
- new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json"));
+ // Setup LogicManager with JsonExerciseTrackerIoExceptionThrowingStub
+ JsonExerciseTrackerStorage exerciseTrackerStorage =
+ new JsonExerciseTrackerIoExceptionThrowingStub(
+ temporaryFolder.resolve("ioExceptionExerciseTracker.json"));
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ StorageManager storage = new StorageManager(exerciseTrackerStorage, userPrefsStorage);
logic = new LogicManager(model, storage);
// Execute add command
- String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY
- + ADDRESS_DESC_AMY;
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_ARM_CURLS + WEIGHT_DESC_ARM_CURLS + SETS_DESC_ARM_CURLS
+ + REPS_DESC_ARM_CURLS + DATE_DESC;
+ Exercise expectedExercise = new ExerciseBuilder(ARM_CURLS).withDate(VALID_DATE).build();
ModelManager expectedModel = new ModelManager();
- expectedModel.addPerson(expectedPerson);
+ expectedModel.addExercise(expectedExercise);
String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION;
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
}
@Test
- public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
- assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0));
+ public void getFilteredExerciseList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredExerciseList().remove(0));
}
/**
@@ -129,7 +134,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getExerciseTracker(), new UserPrefs());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
@@ -149,13 +154,13 @@ private void assertCommandFailure(String inputCommand, Class extends Throwable
/**
* A stub class to throw an {@code IOException} when the save method is called.
*/
- private static class JsonAddressBookIoExceptionThrowingStub extends JsonAddressBookStorage {
- private JsonAddressBookIoExceptionThrowingStub(Path filePath) {
+ private static class JsonExerciseTrackerIoExceptionThrowingStub extends JsonExerciseTrackerStorage {
+ private JsonExerciseTrackerIoExceptionThrowingStub(Path filePath) {
super(filePath);
}
@Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
+ public void saveExerciseTracker(ReadOnlyExerciseTracker exerciseTracker, Path filePath) throws IOException {
throw DUMMY_IO_EXCEPTION;
}
}
diff --git a/src/test/java/gim/logic/commands/AddCommandIntegrationTest.java b/src/test/java/gim/logic/commands/AddCommandIntegrationTest.java
new file mode 100644
index 00000000000..68c4654d2f7
--- /dev/null
+++ b/src/test/java/gim/logic/commands/AddCommandIntegrationTest.java
@@ -0,0 +1,38 @@
+package gim.logic.commands;
+
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+import gim.model.exercise.Exercise;
+import gim.testutil.ExerciseBuilder;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code AddCommand}.
+ */
+public class AddCommandIntegrationTest {
+
+ private Model model;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_newExercise_success() {
+ Exercise validExercise = new ExerciseBuilder().build();
+
+ Model expectedModel = new ModelManager(model.getExerciseTracker(), new UserPrefs());
+ Exercise added = expectedModel.addExercise(validExercise);
+
+ assertCommandSuccess(new AddCommand(validExercise), model,
+ String.format(AddCommand.MESSAGE_SUCCESS, validExercise.getName().toString(), added), expectedModel);
+ }
+
+}
diff --git a/src/test/java/gim/logic/commands/AddCommandTest.java b/src/test/java/gim/logic/commands/AddCommandTest.java
new file mode 100644
index 00000000000..20d69547d7c
--- /dev/null
+++ b/src/test/java/gim/logic/commands/AddCommandTest.java
@@ -0,0 +1,213 @@
+package gim.logic.commands;
+
+import static gim.testutil.Assert.assertThrows;
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import gim.commons.core.GuiSettings;
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.Name;
+import gim.testutil.ExerciseBuilder;
+import javafx.collections.ObservableList;
+
+public class AddCommandTest {
+
+ @Test
+ public void constructor_nullExercise_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new AddCommand(null));
+ }
+
+ @Test
+ public void execute_exerciseAcceptedByModel_addSuccessful() throws Exception {
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ Exercise validExercise = new ExerciseBuilder().build();
+
+ CommandResult commandResult = new AddCommand(validExercise).execute(modelStub);
+
+ assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validExercise.getName().toString(), validExercise),
+ commandResult.getFeedbackToUser());
+ assertEquals(Arrays.asList(validExercise), modelStub.exercisesAdded);
+ }
+
+ @Test
+ public void equals() {
+ Exercise alice = new ExerciseBuilder().withName("Alice").build();
+ Exercise benchPress = new ExerciseBuilder().withName("Bench Press").build();
+ AddCommand addAliceCommand = new AddCommand(alice);
+ AddCommand addBenchPressCommand = new AddCommand(benchPress);
+
+ // same object -> returns true
+ assertTrue(addAliceCommand.equals(addAliceCommand));
+
+ // same values -> returns true
+ AddCommand addAliceCommandCopy = new AddCommand(alice);
+ assertTrue(addAliceCommand.equals(addAliceCommandCopy));
+
+ // different types -> returns false
+ assertFalse(addAliceCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(addAliceCommand.equals(null));
+
+ // different exercise -> returns false
+ assertFalse(addAliceCommand.equals(addBenchPressCommand));
+ }
+
+ /**
+ * A default model stub that have all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getExerciseTrackerFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setExerciseTrackerFilePath(Path exerciseTrackerFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Exercise getExercisePR(Name exerciseName) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ArrayList getAllExercisePRs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Exercise addExercise(Exercise exercise) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setExerciseTracker(ReadOnlyExerciseTracker newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyExerciseTracker getExerciseTracker() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasExercise(Exercise exercise) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteExercise(Exercise target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredExerciseList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void sortFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void filterFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void resetDisplayedList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ExerciseHashMap getExerciseHashMap() {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that contains a single exercise.
+ */
+ private class ModelStubWithExercise extends ModelStub {
+ private final Exercise exercise;
+
+ ModelStubWithExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ this.exercise = exercise;
+ }
+
+ @Override
+ public boolean hasExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ return this.exercise.isSameExercise(exercise);
+ }
+ }
+
+ /**
+ * A Model stub that always accept the exercise being added.
+ */
+ private class ModelStubAcceptingExerciseAdded extends ModelStub {
+ final ArrayList exercisesAdded = new ArrayList<>();
+
+ @Override
+ public boolean hasExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ return exercisesAdded.stream().anyMatch(exercise::isSameExercise);
+ }
+
+ @Override
+ public Exercise addExercise(Exercise exercise) {
+ requireNonNull(exercise);
+ exercisesAdded.add(exercise);
+ return exercise;
+ }
+
+ @Override
+ public ReadOnlyExerciseTracker getExerciseTracker() {
+ return new ExerciseTracker();
+ }
+ }
+
+}
diff --git a/src/test/java/gim/logic/commands/ClearCommandTest.java b/src/test/java/gim/logic/commands/ClearCommandTest.java
new file mode 100644
index 00000000000..5a60ab6010a
--- /dev/null
+++ b/src/test/java/gim/logic/commands/ClearCommandTest.java
@@ -0,0 +1,32 @@
+package gim.logic.commands;
+
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+
+import org.junit.jupiter.api.Test;
+
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+
+public class ClearCommandTest {
+
+ @Test
+ public void execute_emptyExerciseTracker_success() {
+ Model model = new ModelManager();
+ Model expectedModel = new ModelManager();
+
+ assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
+ }
+
+ @Test
+ public void execute_nonEmptyExerciseTracker_success() {
+ Model model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ Model expectedModel = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ expectedModel.setExerciseTracker(new ExerciseTracker());
+
+ assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/gim/logic/commands/CommandResultTest.java
similarity index 98%
rename from src/test/java/seedu/address/logic/commands/CommandResultTest.java
rename to src/test/java/gim/logic/commands/CommandResultTest.java
index 4f3eb46e9ef..d1884137ee3 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/gim/logic/commands/CommandResultTest.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
diff --git a/src/test/java/gim/logic/commands/CommandTestUtil.java b/src/test/java/gim/logic/commands/CommandTestUtil.java
new file mode 100644
index 00000000000..32c67a2522a
--- /dev/null
+++ b/src/test/java/gim/logic/commands/CommandTestUtil.java
@@ -0,0 +1,134 @@
+package gim.logic.commands;
+
+import static gim.logic.parser.CliSyntax.PREFIX_ALL;
+import static gim.logic.parser.CliSyntax.PREFIX_CONFIRM;
+import static gim.logic.parser.CliSyntax.PREFIX_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_END_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_NAME;
+import static gim.logic.parser.CliSyntax.PREFIX_REPS;
+import static gim.logic.parser.CliSyntax.PREFIX_SETS;
+import static gim.logic.parser.CliSyntax.PREFIX_START_DATE;
+import static gim.logic.parser.CliSyntax.PREFIX_WEIGHT;
+import static gim.testutil.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import gim.commons.core.index.Index;
+import gim.logic.commands.exceptions.CommandException;
+import gim.logic.generators.ValidLevel;
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.NameContainsKeywordsPredicate;
+
+
+/**
+ * Contains helper methods for testing commands.
+ */
+public class CommandTestUtil {
+
+ public static final String VALID_NAME_ARM_CURLS = "Arm Curls";
+ public static final String VALID_NAME_BENCH_PRESS = "Bench Press";
+ public static final String VALID_WEIGHT_ARM_CURLS = "100";
+ public static final String VALID_WEIGHT_BENCH_PRESS = "200";
+ public static final String VALID_SETS_ARM_CURLS = "3";
+ public static final String VALID_SETS_BENCH_PRESS = "5";
+ public static final String VALID_REPS_ARM_CURLS = "1";
+ public static final String VALID_REPS_BENCH_PRESS = "2";
+ public static final ValidLevel VALID_LEVEL_EASY = ValidLevel.EASY;
+ public static final ValidLevel VALID_LEVEL_MEDIUM = ValidLevel.MEDIUM;
+ public static final ValidLevel VALID_LEVEL_HARD = ValidLevel.HARD;
+ public static final String VALID_DATE = "02/05/2022";
+ public static final String VALID_DATE_2 = "12/01/2007";
+ public static final String VALID_START_DATE = "01/10/2022";
+ public static final String VALID_END_DATE = "31/10/2022";
+
+ public static final String NAME_DESC_ARM_CURLS = " " + PREFIX_NAME + VALID_NAME_ARM_CURLS;
+ public static final String NAME_DESC_BENCH_PRESS = " " + PREFIX_NAME + VALID_NAME_BENCH_PRESS;
+ public static final String WEIGHT_DESC_ARM_CURLS = " " + PREFIX_WEIGHT + VALID_WEIGHT_ARM_CURLS;
+ public static final String WEIGHT_DESC_BENCH_PRESS = " " + PREFIX_WEIGHT + VALID_WEIGHT_BENCH_PRESS;
+ public static final String SETS_DESC_ARM_CURLS = " " + PREFIX_SETS + VALID_SETS_ARM_CURLS;
+ public static final String SETS_DESC_BENCH_PRESS = " " + PREFIX_SETS + VALID_SETS_BENCH_PRESS;
+ public static final String REPS_DESC_ARM_CURLS = " " + PREFIX_REPS + VALID_REPS_ARM_CURLS;
+ public static final String REPS_DESC_BENCH_PRESS = " " + PREFIX_REPS + VALID_REPS_BENCH_PRESS;
+ public static final String DATE_DESC = " " + PREFIX_DATE + VALID_DATE;
+
+ public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
+ public static final String INVALID_WEIGHT_DESC = " " + PREFIX_WEIGHT + "911a"; // 'a' not allowed in weights
+ public static final String INVALID_SETS_DESC = " " + PREFIX_SETS; // empty string not allowed for sets
+ public static final String INVALID_REPS_DESC = " " + PREFIX_REPS; // empty string not allowed for reps
+ public static final String INVALID_DATE_DESC = " " + PREFIX_DATE + "02/05/2022z"; // 'z' not allowed in date
+ public static final String VALID_START_DATE_DESC = " " + PREFIX_START_DATE + "01/10/2022";
+ public static final String INVALID_START_DATE_DESC = " " + PREFIX_START_DATE + "001/10/2022";
+ public static final String VALID_END_DATE_DESC = " " + PREFIX_END_DATE + "31/10/2022";
+ public static final String INVALID_END_DATE_DESC = " " + PREFIX_END_DATE + "031/10/2022";
+ // date can be empty, it will default to today's date
+
+ public static final String DESC_PREFIX_ALL = " " + PREFIX_ALL;
+ public static final String DESC_PREFIX_CONFIRM = " " + PREFIX_CONFIRM;
+
+ public static final String PREAMBLE_WHITESPACE = "\t \r \n";
+ public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
+
+
+ /**
+ * Executes the given {@code command}, confirms that
+ * - the returned {@link CommandResult} matches {@code expectedCommandResult}
+ * - the {@code actualModel} matches {@code expectedModel}
+ */
+ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult,
+ Model expectedModel) {
+ try {
+ CommandResult result = command.execute(actualModel);
+ assertEquals(expectedCommandResult, result);
+ assertEquals(expectedModel, actualModel);
+ } catch (CommandException ce) {
+ throw new AssertionError("Execution of command should not fail.", ce);
+ }
+ }
+
+ /**
+ * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)}
+ * that takes a string {@code expectedMessage}.
+ */
+ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage,
+ Model expectedModel) {
+ CommandResult expectedCommandResult = new CommandResult(expectedMessage);
+ assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel);
+ }
+
+ /**
+ * Executes the given {@code command}, confirms that
+ * - a {@code CommandException} is thrown
+ * - the CommandException message matches {@code expectedMessage}
+ * - the exercise tracker, filtered exercise list and selected exercise in {@code actualModel} remain unchanged
+ */
+ public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) {
+ // we are unable to defensively copy the model for comparison later, so we can
+ // only do so by copying its components.
+ ExerciseTracker expectedExerciseTracker = new ExerciseTracker(actualModel.getExerciseTracker());
+ List expectedFilteredList = new ArrayList<>(actualModel.getFilteredExerciseList());
+
+ assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel));
+ assertEquals(expectedExerciseTracker, actualModel.getExerciseTracker());
+ assertEquals(expectedFilteredList, actualModel.getFilteredExerciseList());
+ }
+ /**
+ * Updates {@code model}'s filtered list to show only the exercise at the given {@code targetIndex} in the
+ * {@code model}'s exercise tracker.
+ */
+ public static void showExerciseAtIndex(Model model, Index targetIndex) {
+ assertTrue(targetIndex.getZeroBased() < model.getFilteredExerciseList().size());
+
+ Exercise exercise = model.getFilteredExerciseList().get(targetIndex.getZeroBased());
+ final String[] splitName = exercise.getName().fullName.split("\\s+");
+ model.updateFilteredExerciseList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0])));
+
+ assertEquals(1, model.getFilteredExerciseList().size());
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/gim/logic/commands/DeleteCommandTest.java
similarity index 50%
rename from src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
rename to src/test/java/gim/logic/commands/DeleteCommandTest.java
index 45a8c910ba1..226c12615a0 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/gim/logic/commands/DeleteCommandTest.java
@@ -1,22 +1,22 @@
-package seedu.address.logic.commands;
-
+package gim.logic.commands;
+
+import static gim.logic.commands.CommandTestUtil.assertCommandFailure;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.logic.commands.CommandTestUtil.showExerciseAtIndex;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+import static gim.testutil.TypicalIndexes.INDEX_FIRST_EXERCISE;
+import static gim.testutil.TypicalIndexes.INDEX_SECOND_EXERCISE;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
-import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
import org.junit.jupiter.api.Test;
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
+import gim.commons.core.Messages;
+import gim.commons.core.index.Index;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+import gim.model.exercise.Exercise;
/**
* Contains integration tests (interaction with the Model) and unit tests for
@@ -24,68 +24,68 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
@Test
public void execute_validIndexUnfilteredList_success() {
- Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ Exercise exerciseToDelete = model.getFilteredExerciseList().get(INDEX_FIRST_EXERCISE.getZeroBased());
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_EXERCISE);
- String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
+ String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_EXERCISE_SUCCESS, exerciseToDelete);
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- expectedModel.deletePerson(personToDelete);
+ ModelManager expectedModel = new ModelManager(model.getExerciseTracker(), new UserPrefs());
+ expectedModel.deleteExercise(exerciseToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
}
@Test
public void execute_invalidIndexUnfilteredList_throwsCommandException() {
- Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredExerciseList().size() + 1);
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
}
@Test
public void execute_validIndexFilteredList_success() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ showExerciseAtIndex(model, INDEX_FIRST_EXERCISE);
- Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ Exercise exerciseToDelete = model.getFilteredExerciseList().get(INDEX_FIRST_EXERCISE.getZeroBased());
+ DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_EXERCISE);
- String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
+ String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_EXERCISE_SUCCESS, exerciseToDelete);
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- expectedModel.deletePerson(personToDelete);
- showNoPerson(expectedModel);
+ Model expectedModel = new ModelManager(model.getExerciseTracker(), new UserPrefs());
+ expectedModel.deleteExercise(exerciseToDelete);
+ showNoExercise(expectedModel);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
}
@Test
public void execute_invalidIndexFilteredList_throwsCommandException() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ showExerciseAtIndex(model, INDEX_FIRST_EXERCISE);
- Index outOfBoundIndex = INDEX_SECOND_PERSON;
- // ensures that outOfBoundIndex is still in bounds of address book list
- assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+ Index outOfBoundIndex = INDEX_SECOND_EXERCISE;
+ // ensures that outOfBoundIndex is still in bounds of exercise tracker list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getExerciseTracker().getExerciseList().size());
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_EXERCISE_DISPLAYED_INDEX);
}
@Test
public void equals() {
- DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
- DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON);
+ DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_EXERCISE);
+ DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_EXERCISE);
// same object -> returns true
assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
// same values -> returns true
- DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_EXERCISE);
assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
// different types -> returns false
@@ -94,16 +94,16 @@ public void equals() {
// null -> returns false
assertFalse(deleteFirstCommand.equals(null));
- // different person -> returns false
+ // different exercise -> returns false
assertFalse(deleteFirstCommand.equals(deleteSecondCommand));
}
/**
* Updates {@code model}'s filtered list to show no one.
*/
- private void showNoPerson(Model model) {
- model.updateFilteredPersonList(p -> false);
+ private void showNoExercise(Model model) {
+ model.updateFilteredExerciseList(p -> false);
- assertTrue(model.getFilteredPersonList().isEmpty());
+ assertTrue(model.getFilteredExerciseList().isEmpty());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/gim/logic/commands/ExitCommandTest.java
similarity index 60%
rename from src/test/java/seedu/address/logic/commands/ExitCommandTest.java
rename to src/test/java/gim/logic/commands/ExitCommandTest.java
index 9533c473875..79cbbbeb408 100644
--- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
+++ b/src/test/java/gim/logic/commands/ExitCommandTest.java
@@ -1,12 +1,12 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT;
import org.junit.jupiter.api.Test;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
+import gim.model.Model;
+import gim.model.ModelManager;
public class ExitCommandTest {
private Model model = new ModelManager();
diff --git a/src/test/java/gim/logic/commands/FilterCommandTest.java b/src/test/java/gim/logic/commands/FilterCommandTest.java
new file mode 100644
index 00000000000..993888945ba
--- /dev/null
+++ b/src/test/java/gim/logic/commands/FilterCommandTest.java
@@ -0,0 +1,84 @@
+package gim.logic.commands;
+
+import static gim.commons.core.Messages.MESSAGE_EXERCISES_LISTED_OVERVIEW;
+import static gim.commons.core.Messages.MESSAGE_FIND_EMPTY_EXERCISES_LIST;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.testutil.TypicalExercises.CALF_RAISES;
+import static gim.testutil.TypicalExercises.ELEVATED_SQUATS;
+import static gim.testutil.TypicalExercises.FRONT_SQUATS;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+import gim.model.exercise.NameContainsKeywordsPredicate;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code FilterCommand}.
+ */
+public class FilterCommandTest {
+ private Model model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+
+ @Test
+ public void equals() {
+ NameContainsKeywordsPredicate firstPredicate =
+ new NameContainsKeywordsPredicate(Collections.singletonList("first"));
+ NameContainsKeywordsPredicate secondPredicate =
+ new NameContainsKeywordsPredicate(Collections.singletonList("second"));
+
+ FilterCommand filterFirstCommand = new FilterCommand(firstPredicate);
+ FilterCommand filterSecondCommand = new FilterCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(filterFirstCommand.equals(filterFirstCommand));
+
+ // same values -> returns true
+ FilterCommand filterFirstCommandCopy = new FilterCommand(firstPredicate);
+ assertTrue(filterFirstCommand.equals(filterFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(filterFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(filterFirstCommand.equals(null));
+
+ // different exercise -> returns false
+ assertFalse(filterFirstCommand.equals(filterSecondCommand));
+ }
+
+ @Test
+ public void execute_zeroKeywords_noExerciseFound() {
+ String expectedMessage = String.format(MESSAGE_FIND_EMPTY_EXERCISES_LIST, 0);
+ NameContainsKeywordsPredicate predicate = preparePredicate(" ");
+ FilterCommand command = new FilterCommand(predicate);
+ expectedModel.filterFilteredExerciseList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredExerciseList());
+ }
+
+ @Test
+ public void execute_multipleKeywords_multipleExercisesFound() {
+ String expectedMessage = String.format(MESSAGE_EXERCISES_LISTED_OVERVIEW, 3);
+ NameContainsKeywordsPredicate predicate = preparePredicate("CALF ELEVATED FRONT");
+ FilterCommand command = new FilterCommand(predicate);
+ expectedModel.filterFilteredExerciseList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(CALF_RAISES, ELEVATED_SQUATS, FRONT_SQUATS), model.getFilteredExerciseList());
+ }
+
+ /**
+ * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
+ */
+ private NameContainsKeywordsPredicate preparePredicate(String userInput) {
+ return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+}
diff --git a/src/test/java/gim/logic/commands/GenerateCommandTest.java b/src/test/java/gim/logic/commands/GenerateCommandTest.java
new file mode 100644
index 00000000000..9115944f366
--- /dev/null
+++ b/src/test/java/gim/logic/commands/GenerateCommandTest.java
@@ -0,0 +1,130 @@
+package gim.logic.commands;
+
+import static gim.logic.commands.CommandTestUtil.VALID_LEVEL_EASY;
+import static gim.logic.commands.CommandTestUtil.VALID_LEVEL_HARD;
+import static gim.logic.commands.CommandTestUtil.VALID_LEVEL_MEDIUM;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.logic.commands.GenerateCommand.MESSAGE_GENERATE_SUCCESS;
+import static gim.testutil.TypicalExercises.BICEP_CURLS;
+import static gim.testutil.TypicalExercises.DEADLIFT;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+import static gim.testutil.TypicalIndexes.INDEX_FIRST_EXERCISE;
+import static gim.testutil.TypicalIndexes.INDEX_LIST_FIRST_SECOND;
+import static gim.testutil.TypicalIndexes.INDEX_LIST_SECOND_THIRD;
+import static gim.testutil.TypicalIndexes.INDEX_SECOND_EXERCISE;
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import gim.logic.generators.Generator;
+import gim.logic.generators.GeneratorFactory;
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+
+
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for GenerateCommand.
+ */
+class GenerateCommandTest {
+
+ private Model model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ private final Exercise firstExercise = model.getFilteredExerciseList().get(INDEX_FIRST_EXERCISE.getZeroBased());
+ private final Exercise secondExercise = model.getFilteredExerciseList().get(INDEX_SECOND_EXERCISE.getZeroBased());
+ private final Name firstExerciseName = firstExercise.getName();
+ private final Name secondExerciseName = secondExercise.getName();
+ private final Exercise firstExercisePR = model.getExercisePR(firstExerciseName);
+ private final Exercise secondExercisePR = model.getExercisePR(secondExerciseName);
+
+ @Test
+ public void execute_generateUnfilteredList_success() {
+ Generator firstGenerator = GeneratorFactory.getGenerator(firstExercisePR, VALID_LEVEL_EASY);
+ String suggestion = requireNonNull(firstGenerator).suggest();
+ StringBuilder fullSuggestion = new StringBuilder().append(suggestion).append("\n");
+
+
+ if (!firstExercise.getName().equals(secondExercise.getName())) {
+ Generator secondGenerator = GeneratorFactory.getGenerator(secondExercisePR, VALID_LEVEL_EASY);
+ fullSuggestion.append(requireNonNull(secondGenerator).suggest()).append("\n");
+ }
+
+ GenerateCommand generateCommand = new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_EASY);
+ String expectedMessage = VALID_LEVEL_EASY + MESSAGE_GENERATE_SUCCESS + fullSuggestion;
+ Model expectedModel = new ModelManager(new ExerciseTracker(model.getExerciseTracker()), new UserPrefs());
+
+ assertCommandSuccess(generateCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_generateFromNames_success() {
+ Generator firstGenerator = GeneratorFactory.getGenerator(BICEP_CURLS, VALID_LEVEL_EASY);
+ String suggestion = requireNonNull(firstGenerator).suggest();
+ StringBuilder fullSuggestion = new StringBuilder().append(suggestion).append("\n");
+ Generator secondGenerator = GeneratorFactory.getGenerator(DEADLIFT, VALID_LEVEL_EASY);
+ fullSuggestion.append(requireNonNull(secondGenerator).suggest()).append("\n");
+
+ Set names = new HashSet<>();
+ names.add(BICEP_CURLS.getName());
+ names.add(DEADLIFT.getName());
+
+ GenerateCommand generateCommand = new GenerateCommand(names, VALID_LEVEL_EASY);
+ String expectedMessage = VALID_LEVEL_EASY + MESSAGE_GENERATE_SUCCESS + fullSuggestion;
+ Model expectedModel = new ModelManager(new ExerciseTracker(model.getExerciseTracker()), new UserPrefs());
+
+ assertCommandSuccess(generateCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void equals() {
+ final GenerateCommand standardEasyCommand = new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_EASY);
+ final GenerateCommand standardMediumCommand = new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_MEDIUM);
+ final GenerateCommand standardHardCommand = new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_HARD);
+
+ final Set nameSet = new HashSet<>();
+ nameSet.add(firstExerciseName);
+ nameSet.add(secondExerciseName);
+ final GenerateCommand easyCommandWithNames = new GenerateCommand(nameSet, VALID_LEVEL_EASY);
+
+ // same values -> returns true
+ GenerateCommand commandWithSameValues = new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_EASY);
+ assertTrue(standardEasyCommand.equals(commandWithSameValues));
+
+ // same object -> returns true
+ assertTrue(standardEasyCommand.equals(standardEasyCommand));
+
+ // same object -> returns true
+ assertTrue(standardMediumCommand.equals(standardMediumCommand));
+
+ // same object -> returns true
+ assertTrue(standardHardCommand.equals(standardHardCommand));
+
+ // same exercise names and level -> returns true
+ final Set names = new HashSet<>();
+ names.add(firstExerciseName);
+ names.add(secondExerciseName);
+ assertTrue(easyCommandWithNames.equals(new GenerateCommand(names, VALID_LEVEL_EASY)));
+
+ // null -> returns false
+ assertFalse(standardEasyCommand.equals(null));
+
+ // different types -> returns false
+ assertFalse(standardEasyCommand.equals(new ClearCommand()));
+
+ // different index -> returns false
+ assertFalse(standardEasyCommand.equals(new GenerateCommand(INDEX_LIST_SECOND_THIRD, VALID_LEVEL_EASY)));
+
+ // different level -> returns false
+ assertFalse(standardEasyCommand.equals(new GenerateCommand(INDEX_LIST_FIRST_SECOND, VALID_LEVEL_MEDIUM)));
+
+
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/gim/logic/commands/HelpCommandTest.java
similarity index 61%
rename from src/test/java/seedu/address/logic/commands/HelpCommandTest.java
rename to src/test/java/gim/logic/commands/HelpCommandTest.java
index 4904fc4352e..0a8e8ebd7ff 100644
--- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
+++ b/src/test/java/gim/logic/commands/HelpCommandTest.java
@@ -1,12 +1,12 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE;
import org.junit.jupiter.api.Test;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
+import gim.model.Model;
+import gim.model.ModelManager;
public class HelpCommandTest {
private Model model = new ModelManager();
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/gim/logic/commands/ListCommandTest.java
similarity index 50%
rename from src/test/java/seedu/address/logic/commands/ListCommandTest.java
rename to src/test/java/gim/logic/commands/ListCommandTest.java
index 435ff1f7275..35bd2182af3 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/gim/logic/commands/ListCommandTest.java
@@ -1,16 +1,16 @@
-package seedu.address.logic.commands;
+package gim.logic.commands;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
-import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static gim.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static gim.logic.commands.CommandTestUtil.showExerciseAtIndex;
+import static gim.testutil.TypicalExercises.getTypicalExerciseTracker;
+import static gim.testutil.TypicalIndexes.INDEX_FIRST_EXERCISE;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
/**
* Contains integration tests (interaction with the Model) and unit tests for ListCommand.
@@ -22,8 +22,8 @@ public class ListCommandTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalExerciseTracker(), new UserPrefs());
+ expectedModel = new ModelManager(model.getExerciseTracker(), new UserPrefs());
}
@Test
@@ -33,7 +33,7 @@ public void execute_listIsNotFiltered_showsSameList() {
@Test
public void execute_listIsFiltered_showsEverything() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ showExerciseAtIndex(model, INDEX_FIRST_EXERCISE);
assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel);
}
}
diff --git a/src/test/java/gim/logic/commands/PrCommandTest.java b/src/test/java/gim/logic/commands/PrCommandTest.java
new file mode 100644
index 00000000000..734e18c0389
--- /dev/null
+++ b/src/test/java/gim/logic/commands/PrCommandTest.java
@@ -0,0 +1,120 @@
+package gim.logic.commands;
+
+import static gim.testutil.Assert.assertThrows;
+import static gim.testutil.TypicalExercises.DEADLIFT_HEAVY;
+import static gim.testutil.TypicalExercises.DEADLIFT_LIGHT;
+import static gim.testutil.TypicalExercises.SQUAT_HEAVY;
+import static gim.testutil.TypicalExercises.SQUAT_LIGHT;
+import static gim.testutil.TypicalExercises.getNoExerciseExerciseTracker;
+import static gim.testutil.TypicalExercises.getOneExerciseExerciseTracker;
+import static gim.testutil.TypicalExercises.getSameExerciseNameDifferentWeightsExerciseTracker;
+import static gim.testutil.TypicalExercises.getTwoDifferentExercisesExerciseTracker;
+import static gim.testutil.TypicalExercises.twoDifferentExercisesTwoDifferentWeightsExerciseTracker;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import gim.model.Model;
+import gim.model.ModelManager;
+import gim.model.UserPrefs;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.Name;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for PRCommandTest.
+ */
+public class PrCommandTest {
+ private Model model;
+ private Set nameSet;
+ private ArrayList expectedList;
+
+ @BeforeEach
+ public void setUp() {
+ nameSet = new HashSet<>();
+ expectedList = new ArrayList<>();
+ }
+
+ @Test
+ public void constructor_nullExercise_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new PrCommand(null));
+ }
+
+ @Test
+ public void execute_noExercise_showsFailureMessage() {
+ model = new ModelManager(getNoExerciseExerciseTracker(), new UserPrefs());
+
+ CommandResult result = new PrCommand(nameSet).execute(model);
+
+ String expectedMessage = PrCommand.MESSAGE_FAILURE;
+
+ assertEquals(new CommandResult(expectedMessage), result);
+ }
+
+ @Test
+ public void execute_oneExercise_showsExercise() {
+ model = new ModelManager(getOneExerciseExerciseTracker(), new UserPrefs());
+
+ nameSet.add(SQUAT_LIGHT.getName());
+ CommandResult result = new PrCommand(nameSet).execute(model);
+
+ expectedList.add(SQUAT_LIGHT);
+ String expectedMessage = String.format(PrCommand.MESSAGE_SUCCESS,
+ PrCommand.prettyStringifyArrayList(expectedList));
+
+ assertEquals(new CommandResult(expectedMessage), result);
+ }
+
+ @Test
+ public void execute_sameExerciseDifferentWeights_showsHeavierExercise() {
+ model = new ModelManager(getSameExerciseNameDifferentWeightsExerciseTracker(), new UserPrefs());
+
+ nameSet.add(SQUAT_LIGHT.getName());
+ nameSet.add(SQUAT_HEAVY.getName());
+ CommandResult result = new PrCommand(nameSet).execute(model);
+
+ expectedList.add(SQUAT_HEAVY);
+ String expectedMessage = String.format(PrCommand.MESSAGE_SUCCESS,
+ PrCommand.prettyStringifyArrayList(expectedList));
+
+ assertEquals(new CommandResult(expectedMessage), result);
+ }
+
+ @Test
+ public void execute_twoDifferentExercises_showsBothExercises() {
+ model = new ModelManager(getTwoDifferentExercisesExerciseTracker(), new UserPrefs());
+
+ nameSet.add(DEADLIFT_LIGHT.getName());
+ nameSet.add(SQUAT_LIGHT.getName());
+ CommandResult result = new PrCommand(nameSet).execute(model);
+
+ expectedList.add(DEADLIFT_LIGHT);
+ expectedList.add(SQUAT_LIGHT);
+ String expectedMessage = String.format(PrCommand.MESSAGE_SUCCESS,
+ PrCommand.prettyStringifyArrayList(expectedList));
+
+ assertEquals(new CommandResult(expectedMessage), result);
+ }
+
+ @Test
+ public void execute_twoDifferentExercisesTwoDifferentWeights_showsTwoHeavierExercises() {
+ model = new ModelManager(twoDifferentExercisesTwoDifferentWeightsExerciseTracker(), new UserPrefs());
+
+ nameSet.add(DEADLIFT_LIGHT.getName());
+ nameSet.add(DEADLIFT_HEAVY.getName());
+ nameSet.add(SQUAT_LIGHT.getName());
+ nameSet.add(SQUAT_HEAVY.getName());
+ CommandResult result = new PrCommand(nameSet).execute(model);
+
+ expectedList.add(DEADLIFT_HEAVY);
+ expectedList.add(SQUAT_HEAVY);
+ String expectedMessage = String.format(PrCommand.MESSAGE_SUCCESS,
+ PrCommand.prettyStringifyArrayList(expectedList));
+
+ assertEquals(new CommandResult(expectedMessage), result);
+ }
+}
diff --git a/src/test/java/gim/logic/commands/RangeCommandTest.java b/src/test/java/gim/logic/commands/RangeCommandTest.java
new file mode 100644
index 00000000000..d27980cf62e
--- /dev/null
+++ b/src/test/java/gim/logic/commands/RangeCommandTest.java
@@ -0,0 +1,268 @@
+package gim.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+import gim.commons.core.GuiSettings;
+import gim.commons.core.Messages;
+import gim.model.ExerciseTracker;
+import gim.model.Model;
+import gim.model.ReadOnlyExerciseTracker;
+import gim.model.ReadOnlyUserPrefs;
+import gim.model.date.Date;
+import gim.model.exercise.DateWithinRangePredicate;
+import gim.model.exercise.Exercise;
+import gim.model.exercise.ExerciseHashMap;
+import gim.model.exercise.Name;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+
+class RangeCommandTest {
+
+ @Test
+ public void execute_exerciseAcceptedByModel_rangeVariationOneSuccessful() throws Exception {
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ DateWithinRangePredicate validPredicate = new DateWithinRangePredicate(new Date("10/10/2022"),
+ new Date("12/10/2022"));
+
+ CommandResult commandResult = new RangeCommand(validPredicate).execute(modelStub);
+
+ assertEquals(String.format(Messages.MESSAGE_EXERCISES_LISTED_OVERVIEW,
+ modelStub.getFilteredExerciseList().size()),
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_exerciseAcceptedByModel_rangeVariationTwoRangeZeroSuccessful() throws Exception {
+ // When rangeInDays is 0 (refer to RangeCommand::execute)
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ DateWithinRangePredicate validPredicate = new DateWithinRangePredicate(new Date("10/10/2022"),
+ new Date("10/10/2022"));
+
+ CommandResult commandResult = new RangeCommand(validPredicate, true).execute(modelStub);
+
+ assertEquals(Messages.MESSAGE_RANGE_COMMAND_TWO_TODAY,
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_exerciseAcceptedByModel_rangeVariationTwoRangeOneSuccessful() throws Exception {
+ // When rangeInDays is 1 (refer to RangeCommand::execute)
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ DateWithinRangePredicate validPredicate = new DateWithinRangePredicate(new Date("10/10/2022"),
+ new Date("11/10/2022"));
+
+ CommandResult commandResult = new RangeCommand(validPredicate, true).execute(modelStub);
+
+ assertEquals(Messages.MESSAGE_RANGE_COMMAND_TWO_YESTERDAY,
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_exerciseAcceptedByModel_rangeVariationTwoRangeSevenSuccessful() throws Exception {
+ // When rangeInDays is 7 (refer to RangeCommand::execute)
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ DateWithinRangePredicate validPredicate = new DateWithinRangePredicate(new Date("10/10/2022"),
+ new Date("17/10/2022"));
+
+ CommandResult commandResult = new RangeCommand(validPredicate, true).execute(modelStub);
+
+ assertEquals(Messages.MESSAGE_RANGE_COMMAND_TWO_WEEK,
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void execute_exerciseAcceptedByModel_rangeVariationTwoRangeOthersSuccessful() throws Exception {
+ // When rangeInDays is greater than 1, e.g. 2 (refer to RangeCommand::execute)
+ int rangeInDays = 2;
+ ModelStubAcceptingExerciseAdded modelStub = new ModelStubAcceptingExerciseAdded();
+ DateWithinRangePredicate validPredicate = new DateWithinRangePredicate(new Date("10/10/2022"),
+ new Date("12/10/2022"));
+
+ CommandResult commandResult = new RangeCommand(validPredicate, true).execute(modelStub);
+
+ assertEquals(String.format(Messages.MESSAGE_RANGE_COMMAND_TWO, rangeInDays),
+ commandResult.getFeedbackToUser());
+ }
+
+ @Test
+ public void equals() {
+ String startDateStringOne = "10/10/2022";
+ String endDateStringOne = "12/10/2022";
+ String startDateStringTwo = "21/10/2022";
+ String endDateStringTwo = "28/10/2022";
+
+ DateWithinRangePredicate predicateOne =
+ new DateWithinRangePredicate(new Date(startDateStringOne), new Date(endDateStringOne));
+ DateWithinRangePredicate predicateTwo =
+ new DateWithinRangePredicate(new Date(startDateStringTwo), new Date(endDateStringTwo));
+
+ RangeCommand rangeCommandOne = new RangeCommand(predicateOne);
+ RangeCommand rangeCommandTwo = new RangeCommand(predicateTwo);
+
+ // same object -> returns true
+ assertEquals(rangeCommandOne, rangeCommandOne);
+
+ // same values -> returns true
+ RangeCommand rangeCommandOneCopy = new RangeCommand(predicateOne);
+ assertEquals(rangeCommandOne, rangeCommandOneCopy);
+
+ // different types -> returns false
+ assertNotEquals("STRING", rangeCommandOne);
+
+ // null returns false
+ assertNotEquals(rangeCommandOne, null);
+
+ // different exercise returns false
+ assertNotEquals(rangeCommandOne, rangeCommandTwo);
+ }
+
+ /**
+ * A default model stub that have all of the methods failing.
+ */
+ private class ModelStub implements Model {
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getExerciseTrackerFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setExerciseTrackerFilePath(Path exerciseTrackerFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Exercise getExercisePR(Name exerciseName) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ArrayList getAllExercisePRs() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Exercise addExercise(Exercise exercise) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setExerciseTracker(ReadOnlyExerciseTracker newData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyExerciseTracker getExerciseTracker() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasExercise(Exercise exercise) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteExercise(Exercise target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredExerciseList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void sortFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void filterFilteredExerciseList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void resetDisplayedList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ExerciseHashMap getExerciseHashMap() {
+ throw new AssertionError("This method should not be called.");
+ }
+ }
+
+ /**
+ * A Model stub that always accept the exercise being added.
+ */
+ private class ModelStubAcceptingExerciseAdded extends ModelStub {
+ final ArrayList exercisesAdded = new ArrayList<>();
+ final ExerciseTracker exerciseTracker = new ExerciseTracker();
+ final FilteredList