diff --git a/README.md b/README.md
index 13f5c77403f..99bd03b45a7 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,27 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+## Lifebook
+[](https://github.com/AY2021S1-CS2103T-F12-4/tp/actions)
+[](https://codecov.io/gh/AY2021S1-CS2103T-F12-4/tp)

-* This is **a sample project for Software Engineering (SE) students**.
- 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)**.
-* 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.
+**Introduction**
+
+* Lifebook is a project developed for an introductory Software Engineering (SE) module (CS2103T) at the National University of Singapore.
+
+ * Lifebook is a desktop application intended for University students to manage contact details, assignments, projects, module details, etc.
+
+ * 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 is evolved from AddressBook Level 3 (AB3).
+
+ * It comes with a reasonable level of user and developer documentation.
+
+* For the detailed user documentation of this project, see [here](https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html).
+
+* For contributing to the ongoing development of the Lifebook, do check out the [Developer Guide](https://ay2021s1-cs2103t-f12-4.github.io/tp/DeveloperGuide.html).
+
+This project is developed from an se-education.org initiative. If you would like to contribute code to the initiative, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+
+**Acknowledgements**
+
+Libraries used: JavaFX, Jackson, JUnit5
diff --git a/build.gradle b/build.gradle
index be2d2905dde..54d1cc9db4c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,6 +40,10 @@ task coverage(type: JacocoReport) {
}
}
+run {
+ enableAssertions = true
+}
+
dependencies {
String jUnitVersion = '5.4.0'
String javaFxVersion = '11'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..d503d740218 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -5,55 +5,58 @@ title: About Us
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
-You can reach us at the email `seer[at]comp.nus.edu.sg`
+You may reach us through the following email addresses:
+
+* Uriel Tan: urieltan@comp.nus.edu.sg
+* Chua Chen Ler: chua.chenler@u.nus.edu
+* Kevin William: kevinwilliam@u.nus.edu
+* Lucia Tirta Gunawan: luciatirtag@u.nus.edu
+* Lin Yuan Xun, Caleb: caleblyx@u.nus.edu
## Project team
-### John Doe
+### Tan Hong Jie Uriel
+
+
-
+[[github](https://github.com/urieltan)]
+[[portfolio](team/urieltan.md)]
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+* Role: Developer Guide, Testing
-* Role: Project Advisor
+### Chua Chen Ler
-### Jane Doe
+
-
+[[github](http://github.com/lerxcl)]
+[[portfolio](team/lerxcl.md)]
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+* Role: Team Lead, Deliverables and deadlines, Scheduling and tracking
-* Role: Team Lead
-* Responsibilities: UI
-### Johnny Doe
+### Lin Yuan Xun, Caleb
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/caleblyx)]
+[[portfolio](team/caleblyx.md)]
-* Role: Developer
-* Responsibilities: Data
+* Role: User guide, Intellij expert, Merge conflict solver
-### Jean Doe
+### Lucia Tirta Gunawan
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/luciatirta)]
+[[portfolio](team/luciatirta.md)]
-* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Role: Testing, Ui Expert
-### James Doe
+### Kevin William
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/kevnw)]
+[[portfolio](team/kevnw.md)]
-* Role: Developer
-* Responsibilities: UI
+* Role: Integration, Code quality, Github Expert
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 4829fe43011..45ccdc59464 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -17,206 +17,370 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
### Architecture
-
+**How the architecture components interact with each other**
-The ***Architecture Diagram*** given above explains the high-level design of the App. Given below is a quick overview of each component.
+### UI component
+
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `TaskListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class.
-
+The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder.
+For example, the layout of the [`MainWindow`](https://github.com/AY2021S1-CS2103T-F12-4/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java)
+is specified in [`MainWindow.fxml`](https://github.com/AY2021S1-CS2103T-F12-4/tp/blob/master/src/main/resources/view/MainWindow.fxml)
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
+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.
-**`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,
-* 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.
+**API** :
+[`Ui.java`](https://github.com/AY2021S1-CS2103T-F12-4/tp/blob/master/src/main/java/seedu/address/ui/Ui.java)
-[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
+### Logic component
+
-The rest of the App consists of four components.
+**API** :
+[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
-* [**`UI`**](#ui-component): The UI of the App.
-* [**`Logic`**](#logic-component): The command executor.
-* [**`Model`**](#model-component): Holds the data of the App in memory.
-* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
+1. `Logic` uses the `AddressBookParser` class to parse the user command.
+2. This results in a `Command` object which is executed by the `LogicManager`.
+3. The command execution can affect the `Model` (e.g. adding a person).
+4. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`.
+5. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user.
-Each of the four components,
+Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete contact 1")` API call.
-* defines its *API* in an `interface` with the same name as the Component.
-* exposes its functionality using a concrete `{Component Name}Manager` class (which implements the corresponding API `interface` mentioned in the previous point.
+
-For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class which implements the `Logic` interface.
+Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("edit event i/1 ...")` API call.
-
+
-**How the architecture components interact with each other**
+
: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.
+
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
+### Model component
+
+**API** : [`Model.java`](https://github.com/AY2021S1-CS2103T-F12-4/tp/blob/master/src/main/java/seedu/address/model/Model.java)
-
+The Model,
+* stores a UserPref object that represents the user’s preferences.
+* stores the address book and TaskList data.
+* exposes an unmodifiable ObservableList and an unmodifiable ObservableList. Both of these lists can be ‘observed’ i.e. the UI can be bound to these lists so that the UI automatically updates when the data in their respective lists change.
+* does not depend on any of the other three components.
-The sections below give more details of each component.
+:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook` and `TaskList` , which both `Person` and `Task` references. This allows `AddressBook` amd `TaskList` to only require one `Tag` object per unique `Tag`, instead of each `Person` and `Task` needing their own `Tag` object.
-### UI component
+
+### Storage component
-
+
-**API** :
-[`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The `UserPrefsStorage` and `TaskListStorage` and `AddressBookStorage` defines the API for reading and saving the Model from and to the computer's memory.
+* `UserPrefsStorage` keeps track of `UserPrefs`
+* `TaskListStorage` keeps track of `Task` items in the `Model`
+* `AddressBookStorage` keeps track of `Person` items in the `Model`
+Storage is responsible for keeping the `UserPrefs`, `Task` and `Person` in JSON file format.
-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.
+The following class diagram shows how `TaskListStorage` makes use of OOP to handle additional data such as Tags and Recurrence, as well as to differentiate between `Task` and `Event`.
-The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](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,
+The `AddressBookStorage` class is much simpler and only makes use of `JsonAdaptedTag`, while `UserPrefsStorage` is even simpler and doesn't require it.
-* Executes user commands using the `Logic` component.
-* Listens for changes to `Model` data so that the UI can be updated with the modified data.
+These 2 sequence diagrams show a high level view of reading and saving the `Task` from the `LogicManager`.
-### Logic component
+
+
-
+
-**API** :
-[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+The activity diagram gives a slightly deeper view of how reading `Task` is done.
+If the file parsing has issues, an exception will be thrown.
-1. `Logic` uses the `AddressBookParser` class to parse the user command.
-1. This results in a `Command` object which is executed by the `LogicManager`.
-1. The command execution can affect the `Model` (e.g. adding a person).
-1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`.
-1. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user.
-Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call.
+**API** : [`Storage.java`](https://github.com/AY2021S1-CS2103T-F12-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java)
-
+### Common classes
-
: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.
-
+--------------------------------------------------------------------------------------------------------------------
-### Model component
+## **Implementation**
-
+This section describes some noteworthy details on how certain features are implemented.
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+### Add tasks (`todo` and `event`) feature
-The `Model`,
+##### Parser:
-* stores a `UserPref` object that represents the user’s preferences.
-* stores the address book data.
-* exposes 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.
-* does not depend on any of the other three components.
+
+* `AddCommandParser` implements `Parser`
-
: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` object.
-
+* It parses the user input to determine if the user intends to add a `todo`, `event`, or `person`.
+* It parses the input after the prefixes required to create the intended `todo`, `event`, or `person`.
+* If the user input has all all required prefixes and matches the required syntax and format, it creates the new intended Task or `person` and passes it to its respective AddCommand constructor.
-
+##### Command:
+
-### Storage component
+* The abstract class `AddCommand` extends `Command`.
+* The concrete classes `AddTodoCommand` and `AddEventCommand` extends `AddCommand`.
+* The command will be executed by the Model, which will update the FilteredTaskList based on the added task.
+* If it is successful, it will return a CommandResult with a successful message to the UI.
-
+---
+The following sequence diagrams displays a `Todo` being added to the TaskList after inputting the following command: `add todo desc/Complete homework date/12-12-2020 time/2359`. Adding an `Event` follows a similar sequence.
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+
+The following sequence diagram exhibits the behavior of logic.
-The `Storage` component,
-* can save `UserPref` objects in json format and read it back.
-* can save the address book data in json format and read it back.
+
+The following activity diagram shows what happens when the user enters an add task command:
-### Common classes
+
-Classes used by multiple components are in the `seedu.addressbook.commons` package.
+#### Design consideration
---------------------------------------------------------------------------------------------------------------------
+#### How command works:
-## **Implementation**
+* An alternative approach would be to have a single `AddTaskCommand` which extends `AddCommand`. The `AddCommandParser` could pass either `todo` or `event` to this class' constructor.
+* This could reduce the replication of code, since both `AddTodoCommand` and `AddEventCommand` are almost identical.
+* However, by having two distinct commands, different and more specific success or error messages can be produced by the execution of respective commands.
-This section describes some noteworthy details on how certain features are implemented.
+### Edit tasks (`todo` and `event`) feature
-### \[Proposed\] Undo/redo feature
+##### Parser:
-#### Proposed 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:
+* `EditCommandParser` implements `Parser`
-* `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.
+* It parses the user input to determine if the user intends to add a `todo` or `event`.
+* It parses the input after the prefixes required to create the intended `todo` or `event`.
+* If the user input has all all required prefixes and matches the required syntax and format, it edits the new intended `Todo` or `Event` and passes it to its respective EditCommand constructor.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+##### Command:
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+
-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.
+* The abstract class `EditCommand` extends `Command`.
+* The concrete classes `EditEventCommand`, `EditTodoCommand`, and `EditContactCommand` extends `EditCommand`.
+* The command will be executed by the Model, which will update the FilteredTaskList based on the edited task.
+* If it is successful, it will return a CommandResult with a successful message to the UI.
-
+---
+The following sequence diagrams displays an `Event` being edited to the TaskList. Editing a `Todo` follows a similar sequence.
-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 following sequence diagram exhibits the behavior of logic.
-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`.
+
-
+The following activity diagram shows what happens when the user enters an edit task command:
-
: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 consideration
-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.
+#### How command works:
-
+* An alternative approach would be to have a single `EditTaskCommand` which extends `EditCommand`. The `EditCommandParser` could pass either `todo` or `event` to this class' constructor.
+* This could reduce the replication of code, since both `AddEventCommand` and `AddTodoCommand` are almost identical.
+* However, by having two distinct commands, different and more specific success or error messages can be produced by the execution of respective commands.
-
: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.
+### Filter tasks (`dueAt` and `dueBefore`) feature
-
+##### Parser:
-The following sequence diagram shows how the undo operation works:
+
-
+* `DueBeforeCommandParser` implements `Parser`
-
: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.
+ * It checks for the phrase `itemsDueBefore` and parses the input after the prefixes: date `date/` and time `time/`.
+ * If the input are in the correct date and time format, a new DueBeforePredicate object is created and passed
+ to a new DueBeforeCommand constructor.
-
+* `DueAtCommandParser` implements `Parser`
-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.
+ * It checks for the phrase `itemsDueAt` and parses the content after the prefixes: date `date/` and time `time/`.
+ * If the input are in the correct date and time format, a new DueAtPredicate object is created and passed to a new DueAtCommand constructor.
-
: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.
+##### Predicate:
+
+
+
+The way dueAt and dueBefore works is very similar, the difference only being the dueBefore and dueAt predicate.
+
+`DueBeforePredicate` and `DueAtPredicate` extends `DuePredicate`.
-
-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.
+* `DueBeforePredicate` compares the LocalDateTime input and every task's LocalDateTime, and returns true if the task's LocalDateTime *is before* the input's LocalDateTime.
+* `DueAtPredicate` compares the LocalDateTime input and every task's LocalDateTime, and returns true if the task's LocalDateTime *equals* the input's LocalDateTime.
-
+##### Command:
+The class diagram
-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.
+
-
+* `DueBeforeCommand` and `DueAtCommand` extends `Command`.
+* The command will be executed with the `Model`, which will update the `FilteredTaskList` based on the `DueAtPredicate`/`DueBeforePredicate`
+* If it is successful, it will return a `CommandResult` with a successful message to the UI.
-The following activity diagram summarizes what happens when a user executes a new command:
+The following sequence diagram shows how the dueAt filtering works:
-
+
+
+The following activity diagram shows what happens when the user enters the filter command:
+
+
#### Design consideration:
-##### Aspect: How undo & redo executes
+##### Aspect: How dueAt and dueBefore executes
+
+After implementing the task operations, there is `FilteredTaskList` which we can utilise to filter tasks.
+
+By using the same function, we can prevent duplication of code.
+
+Furthermore, we have adhered a similar design to the task's operations (Using of Command, Parser classes) to maintain code consistency.
+
+### Add link to tasks (`link meeting` and `link doc`) feature
+
+##### Parser:
+
+
+
+* `LinkCommandParser` implements `Parser`
+
+ * It checks for the phrase `link meeting` for LinkMeetingCommand and parses the input
+ after the prefixes: desc `desc/`, url `url/`, index `i/`, date `date/DD-MM-YYYY`, and time `time/HHmm`.
+ * It checks for the phrase `link doc` for LinkCollaborativeCommand and parses the input
+ after the prefixes: desc `desc/`, url `url/`, and index `i/`.
+ * If the inputs are all in the correct format, a new Link object is created and added to an existing task.
+
+##### Command:
+ The class diagram
+
+
+
+-----
+The following sequence diagram shows how the LinkCommand works:
+* `LinkCollaborativeCommand` and `LinkMeetingCommand` extends `Command`.
+* The command will be parsed by `AddressBookParser` and further parsed by `LinkCommandParser`.
+* The `LinkCommandParser` will determine whether the command is a `LinkMeetingCommand` or a `LinkCollaborativeCommand`.
+* After returning the suitable Link Command, the command will be executed, calling the `setTask()` method of `Model`,
+which will update the `TaskList`.
+* After updating the task, the `LogicManager` will call `saveLifeBook()` method of `Storage` class to store the update.
+* If all are successful, `LinkCommand` will return a `CommandResult` with a successful message to the UI.
+
+
+
+The following activity diagram shows what happens when the user enters the link command:
+
+
+
+### Find (`find contact`, `find todo`, and `find event`) feature
+
+#### Parser:
+
+
+
+* `FindCommandParser` implements `Parser`
+
+ * It checks for the phrase `find contact` for FindContactCommand and parses the input
+ after the prefixes: `n/` and `t/`.
+ * It checks for the phrase `find event` for FindEventCommand and parses the input
+ after the prefixes: `desc/` and `t/`.
+ * It checks for the phrase `find todo` for FindTodoCommand and parses the input
+ after the prefixes: `desc/` and `t/`.
+ * If the input is correct, a new Predicate object is created and passed to a new FindCommand constructor.
+
+##### Predicate:
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+
+
-* **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.
+The way these predicate works is very similar, where the `ContactMatchesFindKeywordPredicate` handles the Person object
+and the `TaskMatchesFindKeywordPredicate` handles the Task object.
-_{more aspects and alternatives to be added}_
+`ContactMatchesFindKeywordPredicate` implements `Predicate`.
+`TaskMatchesFindKeywordPredicate` implements `Predicate`.
-### \[Proposed\] Data archiving
+* `ContactMatchesFindKeywordPredicate` returns true if the person's name contains one of the name keyword given AND one of the tag matches the given tag keyword.
+* `TaskMatchesFindKeywordPredicate` returns true if the task's(event or todo) description contains one of the description keyword given AND one of the tag matches the given tag keyword.
+* When only name or description prefix and keyword are given, the predicates return true if the person's name or task's description contain one of the keyword given.
+* When only tag prefix and keyword are given, the predicates return true if one of the person's or task's tag(s) matches the keyword given.
-_{Explain here how the data archiving feature will be implemented}_
+##### Command:
+ The class diagram
+
+
+-----
+The sequence diagram:
+* `FindContactCommand`, `FindEventCommand` and `FindTodoCommand` extends `FindCommand`.
+* The command will be parsed by `AddressBookParser` and further parsed by `FindCommandParser`.
+* The `FindCommandParser` will determine whether the command is a `FindContactCommand`, `FindEventCommand` or a `FindTodoCommand`.
+* After returning the suitable FindCommand, the command will be executed,
+calling the `updateFiltertedPersonList()` method of `Model` and update the `AddressBook` if it is a `FindContactCommand`, or
+the `updateFiltertedTaskList()` method of `Model` and update the `TaskList` if it is a `FindEventCommand` or `FindTodoCommand`.
+* After updating the model, the `LogicManager` will call the storage to save the file.
+* If all are successful, `FindCommand` will return a `CommandResult` with a successful message to the UI.
+
+The following sequence diagram shows how the `FindContactCommand` works.
+The sequence diagrams for `FindEventCommand` and `FindTodoCommand` are very similar to the diagram below
+with minor differences in the type of FindCommand returned and function called to update the model.
+
+
+
+
+
+The following activity diagram shows what happens when the user enters the find contact command:
+
+
+
+The activity diagram when user enters the find event or find todo command is similar to the diagram above.
+
+### Common Tag feature `contactTaskTag`
+
+##### Parser:
+
+
+
+* `ContactTaskTagParser` implements `Parser`
+
+Upon calling `contactTaskTagParser`, the static classes from `contactTaskTagCommand`: `editEditPersonTags` and `EditTaskTags` will be invoked.
+
+If the person and task index are valid, and there is at least 1 tag given, it returns a new `ContactTaskTagCommand`.
+
+##### Command:
+
+
+
+* `ContactTaskTagCommand` extends `Command`.
+
+When the `ContactTaskTagCommand` is being executed, it will retrieve the respective `Person` and `Task`, and update the `Tag` field for both
+with the common tag(s) input.
+
+Then, it will update the `FilteredPersonList` and `FilteredTaskList` to reflect the new changes for the `Person` and `Task` in the GUI.
+
+The following sequence diagram shows how the `contactTaskTag` works:
+
+
+
+**Full command : "contactTaskTag t/CS2103T contactIndex/1 taskIndex/1"** (Due to space constraints in the sequence diagram)
+
+_Note: The details to of the `Storage` model is being omitted, as it is the same procedure as adding a new contact/task._
+
+Here is an activity diagram which shows the possible scenerios when the user inputs the command:
+
+
+
+#### Design consideration:
+
+Making use of the `Tag` class is useful in this case, as this will prevent duplication of code, and reduce the chance of bugs.
--------------------------------------------------------------------------------------------------------------------
@@ -242,7 +406,11 @@ _{Explain here how the data archiving feature will be implemented}_
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**:
+* Students can keep track of tasks from all modules they take efficiently.
+* Manage contacts faster than a typical mouse/GUI driven app.
+* Increase school productivity.
+* An all in one app that makes student's life easier.
### User stories
@@ -253,28 +421,43 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
| `* * *` | user | add a new person | |
+| `* * *` | forgetful student | add todos and events | remember to complete important tasks for projects and attend important events|
| `* * *` | 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 |
+| `* * *` | user | edit a task | easily change wrong or outdated information |
+| `* * *` | forgetful student | remove todos and events | remove tasks that I no longer need |
+| `* * *` | user | find a person by name or tag | locate details of persons without having to go through the entire list |
+| `* * *` | user | find todos by description or tag | locate details of todos without having to go through the entire list |
+| `* * *` | user | find events by description or tag | locate details of events without having to go through the entire list |
+| `* * *` | student | mark todos and events as done | remember the tasks or assignments that I have completed |
+| `* *` | disorganised student | add and remove collaborative links (Google Drive, and many more) to a todo | find the collaborative link for the project easily |
+| `* *` | disorganised student | add, remove, and view zoom links for meetings to an event | remember my Zoom Links |
+| `* *` | forgetful/disorganised student | search what tasks/meetings are due soon or by a specific date/time (filter) | remember to finish before the deadline|
+| `* *` | forgetful/disorganised student | see what tasks are due soon | finish up the most urgent tasks first |
+| `* *` | disorganised student |sort contacts alphabetically | have a more organised contact list to locate contact details more easily|
+| `* *` | disorganised student |sort tasks according to order of imminence | have a more organised task list to select tasks that are most imminent|
+| `* *` | disorganised student |filter the task list to display all tasks, todos, or events | have a complete or more focused view of my Task List |
+| `*` | user with many contacts in the Lifebook | sort persons by name | locate a person easily |
+| `*` | student with weekly lectures and tutorials | add recurring tasks | save time by not adding the same task every week, which is time-consuming|
+| `*` | student | have a common tag for my contact and task | easily find the person I am working with in a project |
-*{More to be added}*
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `Lifebook` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+#### ContactList use cases
+**Use case: UC1 Delete a person**
**MSS**
1. User requests to list persons
-2. AddressBook shows a list of persons
+2. Lifebook shows a list of persons
3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+4. Lifebook deletes the person
Use case ends.
+
**Extensions**
* 2a. The list is empty.
@@ -283,24 +466,337 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+ * 3a1. Lifebook shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+**Use case: UC2 Adjust sorting of tasks**
+
+1. User requests to apply/remove sorting on a displayed lists of contacts.
+2. Lifebook acknowledges by adjusting the sorting on the displayed list of contacts according to the option selected by the user.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The displayed list of contacts is empty.
+ * 2a1. Lifebook removes all filtering from the list and returns a list sorted according to the option selected by the user.
+
+ Use case ends.
+
+* 2b. The list of contacts is empty (i.e no items were added to the list to be sorted).
+ * 2b1. Lifebook displays an error message to prompt the user to add contacts to the list.
+
+ Use case ends.
+
+#### TaskList use cases
+**Use case: UC3 Add a Task to the TaskList**
+
+**MSS**
+1. User requests to add a Task and its details (i.e. description, dates, and times) to the TaskList.
+2. Lifebook acknowledges the request by adding the To Do to the To Do list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. User inputs the dates or times of the Task in the incorrect format.
+
+ * 1a1. Lifebook shows an error message
+
+ Use case restarts at step 1.
+
+* 1b. User chooses to input the task as a recurring one
+
+ * 1b1. Lifebook will add the task as a recurring one instead.
+
+**Use case: UC4 Perform an action (delete or mark as done) on a Task from the Tasklist**
+
+**MSS**
+1. User requests for list of all Tasks.
+2. Lifebook shows the requested list of Tasks.
+3. User requests to perform an action on a specific Task from the list
+4. Lifebook performs action on the Task.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The given action does not exist.
+
+ * 3b1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+
+* 3c. The user marks a recurring task as done.
+
+ * 3c1. Lifebook will automatically add a new task with the same details, with a new deadline given by the _recurrence_.
+
+
+**Use case: UC5 Find contacts by name and/or tag**
+
+**MSS**
+
+1. User requests to list persons
+2. Lifebook shows a list of persons
+3. User requests to find all persons by name and/or tag
+4. Lifebook displays all the persons who match the searched keywords
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. Both the given name and tag is empty.
+
+ * 3a1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+* 3b. The given name or tag is invalid.
+
+ * 3b1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC6 Find todos by description and/or tag**
+
+**MSS**
+
+1. User requests to list todos
+2. Lifebook shows a list of todos
+3. User requests to find all todos by description and/or tag
+4. Lifebook displays all the todos that match the searched keywords
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. Both the given description and tag is empty.
+
+ * 3a1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+* 3b. The given description or tag is invalid.
+
+ * 3b1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC7 Find events by description and/or tag**
+
+**MSS**
+
+1. User requests to list events
+2. Lifebook shows a list of events
+3. User requests to find all events by description and/or tag
+4. Lifebook displays all the events that match the searched keywords
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+Use case ends.
+
+* 3a. Both the given description and tag is empty.
+
+ * 3a1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+* 3b. The given description or tag is invalid.
+
+ * 3b1. Lifebook shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC8 Filter items due on a specific date/time**
+
+**MSS**
+
+1. User requests to filter items due by/before a specified date/time
+2. Lifebook shows a list of items that fulfil the requirement.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+
+ * 1a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+* 1b. The given date/time format is invalid.
+
+ * 1b1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+**Use case: UC9 Adjust sorting of tasks**
+
+1. User requests to apply/remove sorting on a displayed lists of tasks.
+2. LifeBook acknowledges by adjusting the sorting on the displayed list of tasks according to the option selected by the user.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The displayed list of tasks is empty.
+ * 2a1. Lifebook removes all filtering from the list and returns a list sorted according to the option selected by the user.
+
+ Use case ends.
+
+* 2b. The list of tasks is empty (i.e no items were added to the list to be sorted).
+ * 2b1. Lifebook displays an error message to prompt the user to add tasks to the list.
+
+ Use case ends.
+
+**Use case: UC10 Add or remove a collaborative link**
+
+**MSS**
+
+1. User requests a task to be given a link (GitHub Repo, Google Drive, etc).
+2. Lifebook acknowledges the request by showing the requested task.
+3. User requests to bind the link with the task.
+4. Lifebook performs action on the task.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+
+ * 1a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+* 2a. The list is empty.
+
+* 3a. The task already has a link.
+
+ * 3a1. Lifebook requests permission to override the existing link.
+
+ Use case ends.
+
+**Use case: UC11 Store and retrieve a meeting link**
+
+**MSS**
+
+1. User requests a task to be given a meeting link (Teams, Zoom, etc).
+2. Lifebook acknowledges the request by attaching a link to the task.
+3. User later requests to view meeting links associated with the task.
+4. Lifebook shows the links associated with the task.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+
+ * 1a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+* 2a. The list is empty.
+
+* 3a. The task already has a link.
+
+ * 3a1. Lifebook requests permission to override the existing link.
+
+ Use case ends.
+
+**Use case: UC12 Add + search a common tag to a contact and task**
+
+**MSS**
+
+1. User requests to add tag(s) to a particular contact and task.
+2. Lifebook acknowledges the request by attaching the tag(s) to the respective contact and task.
+3. User then requests to search for the tag in the contact list.
+4. Lifebook shows the contact that is associated with the tag.
+5. User also requests to search for the tag in the TaskList.
+6. Lifebook shows the task that is associated with the task.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given person/task index is invalid.
+
+ * 1a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+**Use case: UC13 Edit a task**
+
+**MSS**
+
+1. User requests to edit some fields of a task(s).
+2. Lifebook acknowledges the request by replacing the field(s) to the respective information.
+3. User then requests to search for the tag in the contact list.
+4. Lifebook shows the contact that is associated with the tag.
+5. User also requests to search for the tag in the TaskList.
+6. Lifebook shows the task that is associated with the task.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given person/task index is invalid.
+
+ * 1a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
+* 2a. The list is empty.
+
+* 3a. The user does not give any additional field to be edited.
+
+ * 3a1. Lifebook shows an error message.
+
+ Use case restarts at step 1.
+
### 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 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.
+3. Should be able to hold up to 1000 tasks without a noticeable sluggishness in performance for typical usage.
+4. 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.
+5. Commands should be intuitive so that users can quickly remember the commands.
+6. Should work without an Internet connection.
+7. Should not require more than 100 MB of storage space.
+8. Should be able to backup and restore data by simply copying the whole Lifebook folder.
+9. A user should be able to switch contact/TaskList with command or by clicking on the GUI.
+10. The data saved should be in a human-readable format.
-*{More to be added}*
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Recurrence**: A task that is done on a fixed interval (day/week/month/year).
--------------------------------------------------------------------------------------------------------------------
@@ -319,7 +815,7 @@ testers are expected to do more *exploratory* testing.
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 contacts and tasks. The window size may not be optimum.
1. Saving window preferences
@@ -328,29 +824,215 @@ testers are expected to do more *exploratory* testing.
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 … }_
### Deleting a person
1. Deleting a person while all persons are being shown
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Prerequisites: List all persons using the `list contact` command. Multiple persons in the list.
- 1. Test case: `delete 1`
+ 1. Test case: `delete contact 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`
+ 1. Test case: `delete contact 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Other incorrect delete commands to try: `delete`, `delete contact x` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
-
-### Saving data
-
-1. Dealing with missing/corrupted data files
-
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
-
-1. _{ more test cases … }_
+### Adding a task (e.g. Todo)
+
+1. Adding on to the TaskList while all tasks are being shown
+
+ 1. Prerequisites: List all tasks using the `list task` command.
+
+ 1. Test case: `add todo desc/test date/12-12-2020 time/2359`
+ Expected: A todo with the description "test" and deadline "12-12-2020, 2359" is added to the TaskList.
+
+ 1. Test case: `add todo desc/test date/12-1-2020 time/259`
+ Expected: The todo is not created as the date and time format is wrong. TaskList should remain the same.
+ A "Parse Exception" will be thrown.
+
+ 1. Other incorrect add commands to try: `add`, `add todo`, missing description and/or date/time
+ Expected: Similar to previous.
+
+### Editing a Task
+
+1. Editing a task while the TaskList is being shown.
+ 1. Prerequisites: Have the tasks in the displayed TaskList. The list may be filtered or unfiltered.
+ 1. Test case: `edit todo i/VALID INDEX ...` e.g. if there is a todo with an index of 5, input the command `edit todo i/5 ...`.
+ Expected: The todo at the index of 5 should be edited according to the input given.
+ 1. Test case: `edit todo i/2 date/17-12-2020 time/2359`.
+ Expected: The todo at the index of 2 should have the date edited to be `17-12-2020` and the time to be `2359`.
+ 1. Test case: `edit event i/VALID INDEX ...` e.g. if there is an event with an index of 5, input the command `edit event i/5 ...`.
+ Expected: The event at the index of 5 should be edited according to the input given.
+ 1. Test case: `edit event i/1 desc/new description startdate/12-12-2020`.
+ Expected: The event at the index of 1 should have the description edited to be `new description` and the starting date to be `12-12-2020`.
+ 1. Test case: `edit todo i/INVALID INDEX ...` e.g. if the TaskList has 10 items, input the command `edit todo i/12`.
+ Expected: An error message should be provided indicating that the provided index is invalid.
+ 1. Test case: `edit event i/INVALID INDEX ...` e.g. if the TaskList has 10 items, input the command `edit event i/12`.
+ Expected: An error message should be provided indicating that the provided index is invalid.
+
+### Marking a Task as done and Task deletion
+
+1. Marking a task as done or deleting a task while the TaskList is being shown.
+ 1. Prerequisites: Have tasks in the displayed TaskList. The list may be filtered or unfiltered.
+ 1. Test case: `done VALID INDEX` e.g. if there is a task with an index of 5, input the command `done 5`.
+ Expected: The task at the index of 5 should be marked as done.
+ 1. Test case: `delete task VALID INDEX` e.g. if there is a task with an index of 5, input the command `delete task 5`.
+ Expected: The task at the index of 5 should be deleted.
+ 1. Test case: `done INVALID INDEX` e.g. if the TaskList has 10 items, input the command `done 12`.
+ Expected: An error message should be provided indicating that the provided index is invalid.
+ 1. Test case: `delete task INVALID INDEX` e.g. if the TaskList has 10 items, input the command `delete task 12`.
+ Expected: An error message should be provided indicating that the provided index is invalid.
+
+### Listing
+1. List all contacts, tasks, events, or todos.
+ 1. Prerequisites: Have tasks and contacts added to LifeBook.
+ 1. Test case: `list task`
+ Expected: GUI should switch to the task tab (if previously on the contact tab) that displays a complete list of all added tasks.
+ 1. Test case: `list contact`
+ Expected: GUI should switch to the contact tab (if previously on the the task tab) that displays a complete list of all added contacts.
+ 1. Test case: `list todo`
+ Expected: GUI should switch to the task tab (if previously on the contact tab) that displays a complete list of only all added todos.
+ 1. Test case: `list event`
+ Expected: GUI should switch to the task tab (if previously on the contact tab) that displays a complete list of only all added events.
+
+### Finding contacts or tasks
+1. Find contacts by name and/or tag
+ 1. Prerequisites: Have contacts added to Lifebook
+ 1. Test case: `find contact n/James`
+ Expected: All contacts whose name contains the word 'James'(case-insensitive) will be displayed in the list.
+ 1. Test case: `find contact t/friend`
+ Expected: All contacts whose one of the tags is 'friend'(case-insensitive) will be displayed in the list.
+ 1. Test case: `find contact n/James t/friend`
+ Expected: All contacts whose name contains the word 'James'(case-insensitive) AND one of the tags is 'friend'(case-insensitive) will be displayed in the list.
+ 1. Incorrect find commands to try: `find contact`, `find contact john`
+ Expected: Error message of invalid command format will be returned.
+2. Find events or todos by description and/or tag
+ 1. Prerequisites: Have events or todos added to Lifebook
+ 1. Test case: `find event desc/meeting`
+ Expected: All events whose description contains the word 'meeting'(case-insensitive) will be displayed in the list.
+ 1. Test case: `find event t/important`
+ Expected: All events whose one of the tags is 'important'(case-insensitive) will be displayed in the list.
+ 1. Test case: `find event desc/meeting t/important`
+ Expected: All events whose description contains the word 'meeting'(case-insensitive) AND one of the tags is 'important'(case-insensitive) will be displayed in the list.
+ 1. All three test cases above can be applied to todos by changing the `find event` to `find todo`.
+ 1. Incorrect find commands to try: `find event`, `find todo assignment`
+ Expected: Error message of invalid command format will be returned.
+
+### Adding a link to a task
+Adding a CollaborativeLink to a `Todo` or a MeetingLink to an `Event`.
+
+1. Command: `link meeting`
+ 1. Prerequisites: Have the tasks in the displayed TaskList. The list may be filtered or unfiltered.
+ 1. Test case: `link meeting i/VALID INDEX ...` e.g. if there is an event with an index of 5, input the command `link meeting i/5 ...`.
+ Expected: The event at index 5 will now have a link and the GUI will show it.
+ 1. Test case: `link meeting i/VALID INDEX desc/DESC url/VALID URL date/DATE time/TIME` e.g. if there is an event with an index of 2, input the command `link meeting i/2 desc/Link to Zoom Meeting url/https://www.zoom.com date/20-12-2020 time/1400`.
+ Expected: The event at index 2 will now have a [link](https://www.zoom.com) that has a description, date, and time.
+ 1. Test case: `link meeting i/VALID INDEX url/INVALID URL FORMAT ...` e.g. if there is an event with an index of 3, input the command `link meeting i/3 url/thisisnotaurl`.
+ Expected: An error message should be provided indicating that the provided url is invalid.
+ 1. Test case: `link meeting i/VALID INDEX desc/DESC url/VALID URL date/DATE` e.g. if there is an event with an index of 1, input the command `link meeting i/1 desc/Link to Zoom Meeting url/https://www.zoom.com date/20-12-2020`.
+ Expected: An error message should be provided indicating that one of the field is not provided (`time`).
+
+1. Command: `link doc`
+ 1. Prerequisites: Have the tasks in the displayed TaskList. The list may be filtered or unfiltered.
+ 1. Test case: `link doc i/VALID INDEX ...` e.g. if there is a todo with an index of 5, input the command `link doc i/5 ...`.
+ Expected: The todo at index 5 will now have a link and the GUI will show it.
+ 1. Test case: `link doc i/VALID INDEX desc/DESC url/VALID` e.g. if there is a todo with an index of 2, input the command `link doc i/2 desc/Link to User Guide url/https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html`.
+ Expected: The todo at index 2 will now have a [link](https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html) that has a description.
+ 1. Test case: `link doc i/VALID INDEX url/INVALID URL FORMAT ...` e.g. if there is a todo with an index of 3, input the command `link doc i/3 url/thisisnotaurl`.
+ Expected: An error message should be provided indicating that the provided url is invalid.
+ 1. Test case: `link doc i/VALID INDEX url/VALID URL` e.g. if there is a todo with an index of 1, input the command `link doc i/1 url/https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html`.
+ Expected: An error message should be provided indicating that one of the field is not provided (`desc`).
+
+### Sorting
+Sorting the contact list and TaskList with different states. There are different inputs for each sorting command.
+
+1. Command: `sort task`
+ 1. Input: Have an unsorted displayed list of tasks.
+ Expected: The list of task should be sorted according to date and time in ascending order.
+ 1. Input: Have an empty displayed list of tasks due to filtering.
+ Expected: An unfiltered list of tasks sorted according to date and time in ascending order should be displayed.
+ 1. Input:: Have a TaskList without added Tasks.
+ Expected: An error prompting the user to add tasks should be displayed.
+
+1. Command: `sort contact`
+ 1. Input: Have an unsorted displayed list of contacts.
+ Expected: The list of contacts should be sorted according to name in alphabetical order.
+ 1. Input: Have an empty displayed list of contacts due to filtering.
+ Expected: An unfiltered list of contacts sorted according to name in alphabetical order should be displayed.
+ 1. Input: Have a contact list without added contacts.
+ Expected: An error prompting the user to add contacts should be displayed.
+
+1. Command: `sort clear`
+ 1. Input: Sorted displayed lists of tasks and contacts.
+ Expected: Both lists should be restored to their natural orders.
+ 1. Input: Empty displayed list or lists due to filtering (i.e. both or one of the displayed lists can be empty).
+ Expected: The empty displayed list or lists should now be unfiltered and restored to natural orders.
+ 1. Input: List or lists without added tasks or contacts (i.e. both or one of the lists can have no added tasks or contacts).
+ Expected: An error message prompting the user to add tasks or contacts to the list or lists without added items should be displayed. If one of the list had items, that list will be restored to its natural order.
+
+
+### Filter (itemsDueAt/itemsDueBefore)
+
+1. Filter tasks based on a specific deadline.
+
+ 1. Prerequisites: List all tasks using the `list task` command. Multiple tasks in the list.
+
+ 1. Test case: `itemsDueAt date/12-12-2020 time/2359`
+ Expected: A list of tasks that are due **exactly** at `12-12-2020, 2359` will be shown.
+ 1. Test case: `itemsDueBefore date/02-01-2021 time/2359`
+ Expected: A list of tasks that are due **before** `02-01-2021, 2359` will be shown.
+ 1. Other variations to try: Missing date/time, where an exception will be thrown.
+
+### Due soon tasks
+
+1. At the bottom right-hand corner of Lifebook, a list of due soon tasks are shown (latest of 1 week from the current date/time)
+
+ 1. Prerequisites: A todo/event with a deadline (latest 1 week from the current date/time) should be added.
+ Expected: Upon adding the task, the task should appear immediately in the "due soon" panel.
+
+ 1. Test case: Marking a recurring task as done
+ Expected: Upon marking the recurring task as done, if the new task generated is due soon, it should appear immediately in the "due soon" panel.
+ Also, the done task should no longer appear in the "due soon" panel.
+
+### Common tag
+
+1. Adds a common tag to a specified contact and task.
+
+ 1. Prerequisites: There should be at least 1 contact and 1 task.
+
+ 1. Test case: `contactTaskTag t/CS2100 contactIndex/1 taskIndex/1`
+ Expected: The contact and task at index 1 should have the tag "CS2100".
+
+ 1. Test case: `contactTaskTag t/CS2103T t/project contactIndex/2 taskIndex/2`
+ Expected: The contact and task at index 2 should have the tags "CS2103T" and "project".
+
+ 1. Other variations to try: Incorrect index for contact/task, not giving a tag, tags with a spacing.
+
+## **Effort**
+
+When we first started coding for Lifebook, we had to figure out how to integrate all the task operations into the current
+AB3. Although we had experience with creating "Duke" for our iP, which shares many similar features, we still had to figure out how to merge both the
+contact and task aspects together. Thus, many hours were spent inspecting AB3's codebase.
+
+For the first milestone, we managed to implement the basic task operations, like add, view and delete. This required an implementation
+of multiple new model components, such as TaskList and Tasks.
+We also modified AB3's GUI to include another panel to view the list of tasks.
+Inspecting AB3's JavaFX files proved to be a challenge, as the GUI is significantly more complicated than one in "Duke".
+Much trial and error was required to properly implement it. For instance, the initial implementation of TaskList had to be scrapped, because
+it was not observable to the GUI. To remedy this, a new TaskList implementation, which follow's AB3's implementation of the AddressBook closely had to be created.
+This challenge was encountered primarily due to a lack of familiarity. Likewise, challenges of a similar nature were faced when implementing Storage components for TaskList.
+The implementation of Storage for TaskList had to be done using JSON to ensure it remained consistent with AB3's implementation of storage. Again due to a lack of experience, there were multiple bugs while developing it.
+It took multiple attempts of troubleshooting to finally realise that additional annotations had to be made to serialize/deserialize polymorphic objects.
+
+While creating test cases for task operations, we had to study the way AB3 created its stubs.
+We followed after AB3 and created "TodoBuilder", "EventBuilder", "TypicalTodos" and "TypicalEvents" to abstract out
+the process of creating tasks to do testing.
+
+While implementing recurring task, we had to ensure that Lifebook automatically creates a new task after the recurring task
+is marked as done. Initially, we followed a brute force approach, wherein the "done" method in Todo/Event returns an "AddCommand"
+to generate a new task when executed. This did not follow recommended design principles, as the Todo/Event class (Model components) should not be return
+the Command type class (Logic component). Eventually, we managed to solve this issue, by making the "DoneCommand" responsible for checking
+if the task is a recurring type, and if it is, to create a new recurring task and add it directly to the TaskList.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index b89b691fed9..f34751d4081 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -50,6 +50,8 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Do the tutorials**
These tutorials will help you get acquainted with the codebase.
- * [Tracing code](tutorials/TracingCode.md)
- * [Removing fields](tutorials/RemovingFields.md)
- * [Adding a new command](tutorials/AddRemark.md)
+ We recommend you do these tutorials on the [parent codebase](https://github.com/nus-cs2103-AY2021S1/tp).
+
+ * [Tracing code](https://se-education.org/addressbook-level3/tutorials/TracingCode.html)
+ * [Removing fields](https://se-education.org/addressbook-level3/tutorials/RemovingFields.html)
+ * [Adding a new command](https://se-education.org/addressbook-level3/tutorials/AddRemark.html)
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index b91c3bab04d..7a4a23dda9d 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,34 +3,59 @@ 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.
+
+
+
+
+
+
+Welcome to Lifebook User Guide! Choose a section from the table of contents below to find answers, step-by-step guides and know about Lifebook better.
+
+
+
+Are you a developer? Make sure to check our Developer Guide also! Go back to our Lifebook Developer Guide Page to find out more resources.
+
+
+
+
+
+Lifebook is a **desktop application intended for university students to manage contact details, assignments, projects,
+and module details.** Lifebook supports Command Line Interface (CLI) for efficient contacts and tasks management while still having the benefits of a Graphical User Interface (GUI).
+
+This user guide is targeted at university students who are interested in using Lifebook.
+
+
Table of Contents
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## 1. Quick start
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).
+1. Download the latest `Lifebook.jar` from [here](https://github.com/AY2021S1-CS2103T-F12-4/tp/releases/tag/v1.4).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Copy the file to the folder you want to use as the _home folder_ for your Lifebook.
-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.
- 
+1. Double-click the file to start the app. You will see that the app initially contains some sample data.
+ 
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:
- * **`list`** : Lists all contacts.
+ * **`list contact`** : Lists all contacts.
+
+ * **`add contact`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the contact list.
- * **`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.
+ * **`add todo`**`desc/homework date/12-12-2020 time/2359`: Adds a todo with a description `homework` that is due by `12 December 2020, 2359`.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ * **`link meeting`**`desc/Annual meeting url/https://nus-sg.zoom.us/j/98401234359?pwd=eG9HU1FJRDdsVHRaYkFUTC95L0abcedf i/2 date/20/1/2020 time/2359` : Add a zoom meeting titled 'annual meeting' at 2359, 20 Jan 2020 to the app.
- * **`clear`** : Deletes all contacts.
+ * **`delete contact`**`3` : Deletes the 3rd contact shown in the current list.
+
+ * **`clear contact`** : Deletes all contacts.
* **`exit`** : Exits the app.
@@ -38,7 +63,7 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
--------------------------------------------------------------------------------------------------------------------
-## Features
+## 2. Features
@@ -53,126 +78,521 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* 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.
-* Parameters can be in any order.
+* Parameters for contacts and task operations can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
-### Viewing help : `help`
-
-Shows a message explaning how to access the help page.
+### 2.1 Contact
-
+##### 2.1.1 Adding a person: `add contact`
-Format: `help`
+Adds a person to the contact list.
+Format: `add contact n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
-### Adding a person: `add`
+
:bulb: **Tip:**
-Adds a person to the address book.
+A person can have any number of tags (including 0).
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+Each tag should not contain a spacing.
-
: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`
+* `add contact n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
+* `add contact n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
-### Listing all persons : `list`
+##### 2.1.2 Listing all persons : `list contact`
-Shows a list of all persons in the address book.
+Shows a list of all persons in the contact list.
-Format: `list`
+Format: `list contact`
-### Editing a person : `edit`
+##### 2.1.3 Editing a person : `edit contact`
-Edits an existing person in the address book.
+Edits an existing person in the contact list.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `edit contact i/INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
* 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.
+* Emails should be of the format `local-part@domain` and adhere to the following constraints:
+ 1. The local-part should only contain alphanumeric characters and these special characters: `` `!#$%&'*+/=?`{|}~^.- ``
+ 2. This is followed by a '@' and then a domain name. The domain name must:
+ - be at least 2 characters long
+ - start and end with alphanumeric characters
+ - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any.
* 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.
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.
+* `edit contact i/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 contact i/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`
+##### 2.1.4 Locating person by name or tag: `find contact`
-Finds persons whose names contain any of the given keywords.
+Finds people whose names contain any of the given name keywords and tag matches the given tag keyword.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Format: `find contact [n/NAME_KEYWORD [MORE_NAME_KEYWORDS]] [t/TAG_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`
+* At least one of name keyword or tag keyword is given in the command.
+* The search is case-insensitive for both name and tag. e.g `friends` will match `Friends`
+* The order of the name keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
+* Only full words will be matched for both name and tag e.g. `Han` will not match `Hans`
+* When only name keyword is given, persons matching at least one keyword will be returned (i.e. `OR` search).
+ e.g. name keyword `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* When only tag keyword is given, persons whose one of the tag(s) matches the tag searched will be returned.
+e.g. Hans with tag `friends` and `colleagues` will be returned when tag `friends` is searched.
+* When both name keyword and tag keyword are given, persons matching both keywords are returned.
+e.g. `Hans Gruber` with tag `friends` and `colleagues` will be returned on command `find contact n/Hans Bo t/friends`.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
+* `find contact n/John` returns `john` and `John Doe`
+* `find contact n/alex david` returns `Alex Yeoh`, `David Li`
+* `find contact t/friends`
+* `find contact n/John Doe t/colleagues`
+
+

-### Deleting a person : `delete`
+##### 2.1.5 Deleting a person : `delete contact`
-Deletes the specified person from the address book.
+Deletes the specified person from the contact list.
-Format: `delete INDEX`
+Format: `delete contact INDEX`
* 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, …
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.
+* `list contact` followed by `delete contact 2` deletes the 2nd person in the contact list.
+* `find contact Betsy` followed by `delete contact 1` deletes the 1st person in the results of the `find` command.
+
+##### 2.1.6 Clearing all entries : `clear contact`
+
+Clears all entries from the contact list.
+
+Format: `clear contact`
+
+### 2.2 Task
+
+##### 2.2.1 Due soon Tasks
+
+
+
+At the bottom right hand corner of Lifebook, a list of tasks is shown that are due **1 week from the current date/time**.
+
+
+
+:warning: **Warning:**
+
+For all task operations, input the task's index based from the **whole Tasklist**, NOT from "Due soon".
+
+
+
+
+##### 2.2.2 Removing Tasks : `delete task`
+Removes a To Do or an Event from the TaskList of LifeBook.
+
+Format: `delete task INDEX`
+
+* Removes the To Do at the specified `INDEX`.
+* The index refers to the index number shown in the displayed TodoList.
+* The index **must be a positive integer** 1, 2, 3, …
+
+Example:
+* `list todo` followed by `delete task 3` removes the 3rd To Do from the To Do list.
+* `list event` followed by `delete task 3` removes the 3rd Event from the Event list.
+
+##### 2.2.3 Mark Task as complete: `done`
+Marks a Task on the TaskList as complete.
+
+Format: `done INDEX`
+
+* Marks the Task at the specified `INDEX` as done.
+* The index refers to the index number shown in the displayed TaskList.
+* The index **must be a positive integer** 1, 2, 3, …
+* If it is a recurring todo or event, it will proceed to add a new Todo or a new Event based on the new deadline given by the recurrence.
+
+Example:
+* `list todo` followed by `done 3` marks the 3rd todo on the Todo List as complete.
+* `list event` followed by `done 3` marks the 3rd Event on the Event list as complete.
+
+##### 2.2.4 Listing all tasks : `list task`
+
+Shows a list of all events and to-dos in the Lifebook.
+
+Format: `list task`
+
+##### 2.2.5 Filter deadlines : `itemsDue`
+
+User can search todos/meetings due at/before a certain date/time.
+
+Outputs a list of results.
+
+* Due at
+ `itemsDueAt date/DD-MM-YYYY time/HHmm`
+
+ `DD-MM-YYYY`: States the Day, Month, Year.
+
+ `HHmm`: States the time in 24 hours.
-### Clearing all entries : `clear`
+ Example: `itemsDueAt date/12-12-2020 time/2359`
+ It will output a list of todos/meetings that are due specifically **at** 12th December 2020, 2359.
-Clears all entries from the address book.
+* Due before
+ `itemsDueBefore date/DD-MM-YYYY time/HHmm`
-Format: `clear`
+ `DD-MM-YYYY`: States the Day, Month, Year.
-### Exiting the program : `exit`
+ `HHmm`: States the time in 24 hours.
+
+ Example: `itemsDueBefore date/12-12-2020 time/2359`
+ It will output a list of todos/meetings that are due specifically **before** 12th December 2020, 2359.
+
+
+
+:information_source: **Note:**
+
+* It is designed to show your completed tasks as well.
+
+
+
+##### 2.2.6 Recurrence
+
+A recurring task is created when a user includes the optional `recurring/` field in adding a new todo/event.
+
+When a recurring task is marked as done, it will automatically generate another recurring task with the new deadline based on the recurrence field. (while the remaining details of the task remains the same)
+
+Example: `add todo desc/CS2100 Tutorial date/04-11-2020 time/1100 recurring/1 week`
+
+When this task is marked as done, it will generate another todo with the deadline: `Date: 11-11-2020, Time: 1100`
+
+If a task is recurring, it will be displayed in the GUI.
+
+
+
+##### 2.2.7 Add common tag to contact + task: `contactTaskTag`
+
+Adds a common tag to a contact and task (Todo or Event) based on the given indexes of the contact and task.
+The user can choose to add as many common tags as they want, where they simply just need to input the `t/` prefix.
+
+Format:
+* `contactTaskTag t/TAG... contactIndex/INDEX taskIndex/INDEX`
+
+
+
+Example:
+
+Upon executing the command, the contact at index 7- “James Ho” and task at index 1- “homework” will have both the tags “CS2100” and “buddy”, which helps in the ease of searching contacts that are associated with a task.
+
+
+
+:warning: **Warning:**
+
+It is a coincidence in the example that the index of the "homework" task is 1 for "Due soon" and the whole Tasklist.
+Input the task's index based from the **whole Tasklist** instead.
+
+Also, each tag should not have any spacings.
+
+
+
+* `contactTaskTag t/CS2100 t/buddy contactIndex/7 taskIndex/1`
+
+### 2.3 Todo
+
+##### 2.3.1 Adding To Dos : `add todo`
+Adds a To Do to the TodoList of LifeBook.
+
+Format: `add todo desc/DESCRIPTION date/DATE time/TIME [recurring/VALUE UNIT] [t/TAG]…`
+
+* `DESCRIPTION` must be <= 30 characters
+* `DATE` must be specified in the format of DD-MM-YYYY
+* `TIME` must be specified in the format of HHmm using 24 hour time
+* `VALUE` must be > 0
+* `TAG` must be alphanumeric (no spaces)
+* `UNIT` must be "day", "week", "month" or "year"
+
+Examples:
+
+* `add todo desc/cs2101 Oral Presentation reflection date/08-08-2020 time/2359`
+* `add todo desc/user guide draft date/09-08-2020 time/2300 t/MUSTFINISHSOON`
+* `add todo desc/tutorial date/10-10-2020 time/1130 recurring/1 week`
+
+##### 2.3.2 Editing a todo : `edit todo`
+
+Edits an existing todo in the task list.
+
+Format: `edit todo i/INDEX [desc/DESCRIPTION] [date/DATE] [time/TIME]`
+
+* Edits the todo at the specified `INDEX`. The index refers to the index number shown in the displayed task 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.
+* `DATE` must be specified in the format of DD-MM-YYYY
+* `TIME` must be specified in the format of HHmm using 24 hour time
+
+
+
+:warning: **Warning:**
+
+To change a normal Todo to a recurring one (or vice-versa), you would have to delete the task and add it with the updated fields, instead of using the `edit` command.
+
+
+
+
+Examples:
+* `edit todo i/1 desc/CS2101 Slides date/24-01-2020` Edits the description and date of the 1st todo to be `CS2101 Slides` and `24-01-2020` respectively.
+* `edit todo i/2 date/25-01-2020 time/2350` Edits the date and time of the 2nd todo to be `25-01-2020` and `2350` respectively.
+
+##### 2.3.3 View all To Dos : `list todo`
+
+Shows a list of all To Do's in the TodoList.
+
+Format: `list todo`
+
+##### 2.3.4 Adding a collaborative folder link: `link doc`
+
+
+
+:information_source: **Note:**
+
+* Collaborative links can only be added to a todo.
+* Only **one** collaborative link can be assigned to a todo.
+
+
+
+Adds a collaborative link (Google Drive, GitHub, Trello, and others) for a todo.
+
+Format:
+* `link doc desc/DESCRIPTION url/LINK i/INDEX_OF_TODO`
+
+* `LINK` must be specified in the format of URL.
+ * Valid URL: https://www.google.com
+ * Invalid URL: www.google.com, google.com, google
+
+Examples:
+* `link doc desc/CS2103T Team Project url/https://drive.google.com/drive/folders/1zoUz1JpAgynIkfacr0asqV9A4kh i/2`
+
+
+##### 2.3.5 Locating todos by description or tag: `find todo`
+Finds todos whose description contain any of the given description keywords and tag matches the given tag keyword.
+
+Format: `find todo [desc/DESC_KEYWORD [MORE_DESC_KEYWORDS]] [t/TAG_KEYWORD]`
+
+* At least one of description keyword or tag keyword is given in the command.
+* The search is case-insensitive for both description and tag. e.g `assignment` will match `Assignment`
+* The order of the description keywords does not matter. e.g. `Finish assignment` will match `assignment Finish`
+* Only full words will be matched for both description and tag e.g. `Quiz` will not match `Quizzes`
+* When only description keyword is given, todos matching at least one keyword will be returned (i.e. `OR` search).
+ e.g. `Do assignment` will return `Do chores`, `Finish assignment`
+* When only tag keyword is given, todos whose one of the tag(s) matches the tag searched will be returned.
+e.g. Finish assignment with tag `CS2100` and `Graded` will be returned when tag `CS2100` is searched.
+* When both description keyword and tag keyword are given, todos matching both keywords are returned.
+e.g. `Finish assignment` with tag `CS2100` and `Graded` will be returned
+on command `find todo desc/Do assignment t/CS2100`.
+
+Examples:
+* `find todo assignment` returns `Essay assignment` and `Quiz assignment`
+* `find todo Do assignment` returns `Do chores`, `Finish assignment`
+* `find todo t/CS3243`
+* `find todo n/Finish assignment t/CS2100`
+
+### 2.4 Event
+
+##### 2.4.1 Adding Events : `add event`
+Adds an Event o to the EventList of LifeBook.
+
+Format: `add event desc/DESCRIPTION startdate/DATE starttime/TIME enddate/DATE endtime/TIME [recurring/VALUE UNIT] [t/TAG]..`
+
+* `DESCRIPTION` must be <= 30 characters
+* `DATE` must be specified in the format of DD-MM-YYYY
+* `TIME` must be specified in the format of HHmm using 24 hour time
+* `VALUE` must be > 0
+* `UNIT` must be "day", "week", "month" or "year"
+* `TAG` must be alphanumeric (no spaces)
+
+Examples:
+
+* `add event desc/Party with friends startdate/08-08-2020 starttime/2359 enddate/10-08-2020 endtime/2359 recurring/1 year`
+* `add event desc/Singapore Fintech Festival startdate/09-08-2020 starttime/1000 enddate/12-08-2020 endtime/2359 t/important`
+
+##### 2.4.2 Editing an event : `edit event`
+
+Edits an existing event in the task list.
+
+Format: `edit event i/INDEX [desc/DESCRIPTION] [startdate/DATE] [starttime/TIME] [enddate/DATE] [endtime/TIME]`
+
+* Edits the event at the specified `INDEX`. The index refers to the index number shown in the displayed task 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.
+* `STARTDATE` and `ENDDATE` must be specified in the format of DD-MM-YYYY
+* `STARTTIME` and `ENDTIME` must be specified in the format of HHmm using 24 hour time
+
+
+
+:warning: **Warning:**
+
+To change a normal Event to a recurring one (or vice-versa), you would have to delete the task and add it with the updated fields, instead of using the `edit` command.
+
+
+
+Examples:
+* `edit event i/1 desc/CS2101 Lecture startdate/24-01-2020 endtime/2359` Edits the description, start date, and end time of the 1st event to be `CS2101 Lecture`, `24-01-2020`, and `2359` respectively.
+* `edit event i/2 starttime/1400 enddate/30-01-2020` Edits the start time and end date of the 2nd event to be `1400` and `30-01-2020` respectively.
+
+##### 2.4.3 View all Events : `list event`
+Shows a list of all Events in the EventList.
+
+Format: `list event`
+
+##### 2.4.4 Adding a zoom meeting: `link meeting`
+
+
+
+:information_source: **Note:**
+
+* Meeting links can only be added to an event.
+* Only **one** meeting link can be assigned to an event.
+
+
+
+Adds a meeting link for an event.
+
+Format:
+* `link meeting desc/DESCRIPTION url/MEETING LINK i/INDEX_OF_EVENT date/DD-MM-YYYY time/HHmm`
+
+* `DATE` must be specified in the format of DD-MM-YYYY
+* `TIME` must be specified in the format of HHmm using 24 hour time
+* `MEETING LINK` must be specified in the format of URL.
+ * Valid URL: https://www.google.com
+ * Invalid URL: www.google.com, google.com, google
+
+Examples:
+* `link meeting desc/Job interview url/https://nus-sg.zoom.us/j/98221234359?pwd=eG9HU1FJRDdsVHRaYk2UTC95L0abcedf i/2 date/22-09-2020 time/1400`
+
+##### 2.4.5 Locating events by description or tag: `find event`
+Finds events whose description contain any of the given description keywords and tag matches the given tag keyword.
+
+Format: `find event [desc/DESC_KEYWORD [MORE_DESC_KEYWORDS]] [t/TAG_KEYWORD]`
+
+* At least one of description keyword or tag keyword is given in the command.
+* The search is case-insensitive for both description and tag. e.g `meeting` will match `Meeting`
+* The order of the description keywords does not matter. e.g. `Attend meeting` will match `meeting Attend`
+* Only full words will be matched for both description and tag e.g. `Meet` will not match `Meeting`
+* When only description keyword is given, events matching at least one keyword will be returned (i.e. `OR` search).
+e.g. `Attend meeting` will return `Attend workshop`, `Arrange meeting`
+* When only tag keyword is given, events whose one of the tag(s) matches the tag searched will be returned.
+e.g. Attend meeting with tag `CS2100` and `TeamProject` will be returned when tag `CS2100` is searched.
+* When both description keyword and tag keyword are given, todos matching both keywords are returned.
+e.g. `Attend meeting` with tag `CS2100` and `TeamProject` will be returned
+on command `find event desc/meeting t/TeamProject`.
+
+Examples:
+* `find event meeting` returns `CCA meeting` and `Team meeting`
+* `find event Attend meeting` returns `Attend workshop`, `Arrange meeting`
+* `find event t/CS3243`
+* `find event n/Attend meeting t/TeamProject`
+
+### 2.5 Others
+
+##### 2.5.1 Sorting contacts and tasks: `sort`
+
+Sorts the currently displayed tasks or contacts according to date or name, respectively. Sorting may also be cleared, such that the natural order of both lists are restored.
+If the currently displayed list is empty, a sorted, unfiltered list will be displayed instead.
+
+Format: `sort OPTION`
+
+* The `OPTION` field refers to `contact`, `task`, or `clear`
+
+
+
+* Contact, task, and clear are the available options for sort and should be used individually in the `OPTION` field.
+* Using the clear option restores all lists (i.e. both contact list and task list) to their natural order.
+
+Examples:
+* `sort task` sorts the task list.
+* `sort contact` sorts the contact list.
+
+
+##### 2.5.2 Exiting the program : `exit`
Exits the program.
Format: `exit`
-### Saving the data
+##### 2.5.3 Viewing help : `help`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Shows a message explaining how to access the help page.
-### Archiving data files `[coming in v2.0]`
+
-_{explain the feature here}_
+Format: `help`
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## 3. FAQ
+**Q**: How do I save any update I made on the Lifebook?
+**A**: Lifebook will automatically save any update you make upon closing the app, and the data is saved in the hard disk. There is no need to save manually.
**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.
+**A**: Simply copy over the whole directory of Lifebook to the other computer and overwrite the files.
--------------------------------------------------------------------------------------------------------------------
-## Command summary
+## 4. Command summary
+
+### 4.1 General
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`
+**Sort** | `sort OPTION` `OPTION` refers to `contact`, `task`, or `clear`
**Help** | `help`
+**Add common tag to Contact + Task** | `contactTaskTag t/TAG... contactIndex/INDEX taskIndex/INDEX` e.g., `contactTaskTag t/CS2103T t/ProjMate contactIndex/1 taskIndex/1`
+
+
+### 4.2 Contact Operations
+
+Action | Format, Examples
+--------|------------------
+**Add Contact** | `add contact n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add contact n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
+**Clear Contacts** | `clear contact`
+**Delete Contact** | `delete contact INDEX` e.g., `delete contact 3`
+**Edit Contact** | `edit contact i/INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]` e.g.,`edit contact i/2 n/James Lee e/jameslee@example.com`
+**Find Contact** | `find contact [n/NAME_KEYWORD [MORE_NAME_KEYWORDS]] [t/TAG_KEYWORD]` e.g., `find contact n/James Jake t/colleagues`
+**List Contacts** | `list contact`
+
+
+### 4.3 Task Operations
+
+Action | Format, Examples
+--------|------------------
+**List Tasks** | `list task`
+**Remove Task (Todo and Event)** | `delete task INDEX` e.g., `delete task 3`
+**Mark To Do/Event as Complete** | `done INDEX` e.g., `done 5`
+**Find tasks due at** | `itemsDueAt date/DD-MM-YYYY time/HHmm` e.g. `itemsDueAt date/12-12-2020 time/2359`
+**Find tasks due before** | `itemsDueBefore date/DD-MM-YYYY time/HHmm` e.g. `itemsDueBefore date/12-12-2020 time/2359`
+
+
+##### 4.3.1 Todo-specific Operations
+
+Action | Format, Examples
+--------|------------------
+**List To Dos** | `list todo`
+**Add To Do** | `add todo desc/DESCRIPTION date/DATE time/TIME [recurring/VALUE UNIT] [t/TAG]…` e.g., `add todo desc/update user guide date/09-08-2020 time/2300 recurring/1 week t/MUSTFINISHSOON`
+**Find To Do** | `find todo [desc/DESC_KEYWORD [MORE_DESC_KEYWORDS]] [t/TAG_KEYWORD]` e.g., `find todo desc/Finish assignment t/CS2100`
+**Link doc** | `link doc desc/DESCRIPTION url/LINK i/INDEX_OF_TODO` e.g.,`link doc desc/CS2103T Team Project url/https://drive.google.com/drive/folders/1zoIkfacr0asqV9A4kh i/2`
+
+
+##### 4.3.2 Event-specific Operations
+
+Action | Format, Examples
+--------|------------------
+**List Events** | `list event`
+**Add Event** | `add event desc/DESCRIPTION startdate/DATE starttime/TIME enddate/DATE endtime/TIME [recurring/VALUE UNIT] [t/TAG]` e.g., `add event desc/meeting startdate/12-12-2020 starttime/1000 enddate/12-12-2020 endtime/1130 recurring/1 week t/IMPORTANT`
+**Find Event** | `find event [desc/DESC_KEYWORD [MORE_DESC_KEYWORDS]] [t/TAG_KEYWORD]` e.g., `find event desc/Attend meeting t/CS2103T`
+**Link meeting** | `link meeting desc/DESCRIPTION url/LINK i/INDEX_OF_EVENT date/DATE time/TIME` e.g.,`link meeting desc/Job interview url/https://nus-sg.zoom.us/j/98221234359?pwd=eG9HU1FJRDdsVHRaYk2UTC95L0abcedf i/2 date/22-09-2020 time/1400`
+
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..70ed050a21e 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,5 +1,5 @@
-title: "AB-3"
-theme: minima
+title: "LB-1"
+theme: jekyll-theme-slate
header_pages:
- UserGuide.md
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2021S1-CS2103T-F12-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss
index b5ec6976efa..9aca64cc1eb 100644
--- a/docs/assets/css/style.scss
+++ b/docs/assets/css/style.scss
@@ -10,3 +10,9 @@
height: 21px;
width: 21px
}
+
+.welcome-page {
+ background-color: #f2f2f2;
+ padding-top: 5px;
+ padding-bottom: 20px;
+}
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 29076104af3..33358b1ef0e 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -9,6 +9,7 @@ AddressBook *-right-> "1" UniqueTagList
UniqueTagList -[hidden]down- UniquePersonList
UniqueTagList -[hidden]down- UniquePersonList
+
UniqueTagList *-right-> "*" Tag
UniquePersonList o-right-> Person
@@ -18,4 +19,5 @@ Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
+
@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteContactSequenceDiagram.puml
similarity index 59%
rename from docs/diagrams/DeleteSequenceDiagram.puml
rename to docs/diagrams/DeleteContactSequenceDiagram.puml
index 1dc2311b245..75822b78bdc 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteContactSequenceDiagram.puml
@@ -5,7 +5,7 @@ box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
-participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
+participant "d:DeleteContactCommand" as DeleteContactCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
end box
@@ -13,10 +13,10 @@ box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute("delete contact 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
+LogicManager -> AddressBookParser : parseCommand("delete contact 1")
activate AddressBookParser
create DeleteCommandParser
@@ -26,15 +26,15 @@ activate DeleteCommandParser
DeleteCommandParser --> AddressBookParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+AddressBookParser -> DeleteCommandParser : parse("contact 1")
activate DeleteCommandParser
-create DeleteCommand
-DeleteCommandParser -> DeleteCommand
-activate DeleteCommand
+create DeleteContactCommand
+DeleteCommandParser -> DeleteContactCommand
+activate DeleteContactCommand
-DeleteCommand --> DeleteCommandParser : d
-deactivate DeleteCommand
+DeleteContactCommand --> DeleteCommandParser : d
+deactivate DeleteContactCommand
DeleteCommandParser --> AddressBookParser : d
deactivate DeleteCommandParser
@@ -45,24 +45,24 @@ destroy DeleteCommandParser
AddressBookParser --> LogicManager : d
deactivate AddressBookParser
-LogicManager -> DeleteCommand : execute()
-activate DeleteCommand
+LogicManager -> DeleteContactCommand : execute()
+activate DeleteContactCommand
-DeleteCommand -> Model : deletePerson(1)
+DeleteContactCommand -> Model : deletePerson(1)
activate Model
-Model --> DeleteCommand
+Model --> DeleteContactCommand
deactivate Model
create CommandResult
-DeleteCommand -> CommandResult
+DeleteContactCommand -> CommandResult
activate CommandResult
-CommandResult --> DeleteCommand
+CommandResult --> DeleteContactCommand
deactivate CommandResult
-DeleteCommand --> LogicManager : result
-deactivate DeleteCommand
+DeleteContactCommand --> LogicManager : result
+deactivate DeleteContactCommand
[<--LogicManager
deactivate LogicManager
diff --git a/docs/diagrams/EditEventSequenceDiagram.puml b/docs/diagrams/EditEventSequenceDiagram.puml
new file mode 100644
index 00000000000..fec69a0f31d
--- /dev/null
+++ b/docs/diagrams/EditEventSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "e:EditCommandParser" as EditCommandParser LOGIC_COLOR
+participant "e:EditEventCommand" as EditEventCommand 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("edit event i/1 ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("edit event i/1...")
+activate AddressBookParser
+
+create EditCommandParser
+AddressBookParser -> EditCommandParser
+activate EditCommandParser
+
+EditCommandParser --> AddressBookParser
+deactivate EditCommandParser
+
+AddressBookParser -> EditCommandParser : parse("event i/1...")
+activate EditCommandParser
+
+create EditEventCommand
+EditCommandParser -> EditEventCommand
+activate EditEventCommand
+
+EditEventCommand --> EditCommandParser : e
+deactivate EditEventCommand
+
+EditCommandParser --> AddressBookParser : e
+deactivate EditCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+EditCommandParser -[hidden]-> AddressBookParser
+destroy EditCommandParser
+
+AddressBookParser --> LogicManager : e
+deactivate AddressBookParser
+
+LogicManager -> EditEventCommand : execute()
+activate EditEventCommand
+
+EditEventCommand -> Model : setTask(eventToEdit, editedEvent)
+activate Model
+
+Model --> EditEventCommand
+deactivate Model
+
+create CommandResult
+EditEventCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditEventCommand
+deactivate CommandResult
+
+EditEventCommand --> LogicManager : result
+deactivate EditEventCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/UpdatedBetterModelClassDiagram.puml b/docs/diagrams/UpdatedBetterModelClassDiagram.puml
new file mode 100644
index 00000000000..09f3b6e7dee
--- /dev/null
+++ b/docs/diagrams/UpdatedBetterModelClassDiagram.puml
@@ -0,0 +1,36 @@
+@startuml
+!include style.puml
+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
+
+
+UniqueTagList *-right-> "*" Tag
+UniquePersonList o-right-> "*" Person
+
+Person -up-> "*" Tag
+
+Person *--> "1" Name
+Person *--> "1" Phone
+Person *--> "1" Email
+Person *--> "1" Address
+
+TaskList *-right-> "1" UniqueTaskList
+TaskList *--> "1" UniqueTagList
+UniqueTaskList o-right-> "*" "{abstract} Task"
+"{abstract} Task" -down-> "*" Tag
+Todo -left-|> "{abstract} Task"
+Event -down-|> "{abstract} Task"
+"{abstract} Task" *-down-> "1" Recurrence
+"{abstract} Task" *--> "1" "{abstract} Link"
+CollaborativeLink --|> "{abstract} Link"
+MeetingLink --|> "{abstract} Link"
+Event *--> "1" MeetingLink
+Todo *--> "1" CollaborativeLink
+
+@enduml
diff --git a/docs/diagrams/UpdatedModelClassDiagram.puml b/docs/diagrams/UpdatedModelClassDiagram.puml
new file mode 100644
index 00000000000..9e9c942eece
--- /dev/null
+++ b/docs/diagrams/UpdatedModelClassDiagram.puml
@@ -0,0 +1,94 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model <>{
+Interface ReadOnlyAddressBook <>
+Interface Model <>
+Interface ObservableList <>
+Class AddressBook
+Class ReadOnlyAddressBook
+Class TaskList
+Class ReadOnlyTaskList <>
+Class ModelManager
+Class UserPrefs
+Class ReadOnlyUserPrefs
+
+Package Person {
+Class Person
+Class Address
+Class Email
+Class Name
+Class Phone
+Class UniquePersonList
+}
+
+Package Tag {
+Class Tag
+}
+
+Package Task {
+Class "{abstract}\nTask"
+Class Todo
+Class Event
+Class Recurrence
+Class UniqueTaskList
+Class Recurrence
+Class "{abstract}\nLink"
+Class MeetingLink
+Class CollaborativeLink
+
+}
+}
+
+Class HiddenOutside #FFFFFF
+HiddenOutside ..> Model
+
+AddressBook .up.|> ReadOnlyAddressBook
+TaskList .up.|> ReadOnlyTaskList
+
+ModelManager .up.|> Model
+ModelManager -left->"1" UniquePersonList : sorted filtered list
+ModelManager -right->"1" UniqueTaskList : sorted filtered list
+Model .right.> ObservableList
+ModelManager --> "1" AddressBook
+ModelManager --> "1" TaskList
+ModelManager -left-> "1" UserPrefs
+UserPrefs .up.|> ReadOnlyUserPrefs
+
+
+AddressBook *--> "1" UniquePersonList
+UniquePersonList --> "*" Person
+Person *--> "1"Name
+Person *--> "1"Phone
+Person *--> "1"Email
+Person *--> "1"Address
+
+
+
+Tag "*"<--* "{abstract}\nTask"
+Tag "*"<--* Person
+
+TaskList *--> "1" UniqueTaskList
+Event *--> "1" MeetingLink
+Todo *--> "1" CollaborativeLink
+"{abstract}\nTask" <|-- Todo
+"{abstract}\nTask" <|-- Event
+"{abstract}\nLink" <|-- CollaborativeLink
+"{abstract}\nLink" <|-- MeetingLink
+UniqueTaskList --> "*" "{abstract}\nTask"
+"{abstract}\nTask" *--> "1" "{abstract}\nLink"
+"{abstract}\nTask" *--> "1" Recurrence
+
+
+
+Name -[hidden]right-> Phone
+Phone -[hidden]right-> Address
+Address -[hidden]right-> Email
+
+
+
+
+@enduml
diff --git a/docs/diagrams/UpdatedUiClassDiagram.puml b/docs/diagrams/UpdatedUiClassDiagram.puml
new file mode 100644
index 00000000000..b9461082b43
--- /dev/null
+++ b/docs/diagrams/UpdatedUiClassDiagram.puml
@@ -0,0 +1,68 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor UI_COLOR_T4
+skinparam classBackgroundColor UI_COLOR
+
+package UI <>{
+Interface Ui <>
+Class "{abstract}\nUiPart" as UiPart
+Class UiManager
+Class MainWindow
+Class HelpWindow
+Class ResultDisplay
+Class TaskListPanel
+Class TaskCard
+Class PersonListPanel
+Class PersonCard
+Class StatusBarFooter
+Class CommandBox
+}
+
+package Model <> {
+Class HiddenModel #FFFFFF
+}
+
+package Logic <> {
+Class HiddenLogic #FFFFFF
+}
+
+Class HiddenOutside #FFFFFF
+HiddenOutside ..> Ui
+
+UiManager .left.|> Ui
+UiManager -down-> MainWindow
+MainWindow *-left->"taskListPanel" TaskListPanel
+MainWindow *-left->"dueSoonTaskListPanel" TaskListPanel
+MainWindow --> HelpWindow
+MainWindow *--> CommandBox
+MainWindow *--> ResultDisplay
+MainWindow *--> PersonListPanel
+MainWindow *--> StatusBarFooter
+
+PersonListPanel --> PersonCard
+TaskListPanel --> TaskCard
+
+MainWindow --|> UiPart
+
+TaskListPanel --|> UiPart
+TaskCard --|> UiPart
+ResultDisplay --|> UiPart
+CommandBox --|> UiPart
+PersonListPanel --|> UiPart
+PersonCard --|> UiPart
+StatusBarFooter --|> UiPart
+HelpWindow --|> UiPart
+
+PersonCard .left.> Model
+TaskCard .left.> Model
+UiManager -right-> Logic
+MainWindow -right-> Logic
+
+PersonListPanel -[hidden]right- ResultDisplay
+TaskListPanel -[hidden]right- PersonListPanel
+CommandBox -[hidden]right- ResultDisplay
+ResultDisplay -[hidden]right- StatusBarFooter
+StatusBarFooter -[hidden]right- HelpWindow
+
+@enduml
diff --git a/docs/diagrams/addTask/AddSequenceDiagram.puml b/docs/diagrams/addTask/AddSequenceDiagram.puml
new file mode 100644
index 00000000000..f9ccbd1f0db
--- /dev/null
+++ b/docs/diagrams/addTask/AddSequenceDiagram.puml
@@ -0,0 +1,33 @@
+@startuml
+!include style.puml
+
+Actor User as user USER_COLOR
+Participant ":UI" as ui UI_COLOR
+Participant ":Logic" as logic LOGIC_COLOR
+Participant ":Model" as model MODEL_COLOR
+
+user -[USER_COLOR]> ui : "add todo ...
+activate ui UI_COLOR
+
+ui -[UI_COLOR]> logic : execute("add todo ...")
+activate logic LOGIC_COLOR
+
+logic -[LOGIC_COLOR]>model : new Todo("Complete homework date", "12-12-2020 2359" )
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]-> logic: todo
+deactivate model
+
+logic -[LOGIC_COLOR]> model : addTodo(todo)
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]-> logic
+deactivate model
+
+logic --[LOGIC_COLOR]> ui
+deactivate logic
+
+ui--[UI_COLOR]> user
+deactivate ui
+
+@enduml
diff --git a/docs/diagrams/addTask/AddTaskActivityDiagram.puml b/docs/diagrams/addTask/AddTaskActivityDiagram.puml
new file mode 100644
index 00000000000..9575263e700
--- /dev/null
+++ b/docs/diagrams/addTask/AddTaskActivityDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+start
+:User executes an add task command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([task is in the Task List])
+ :Task will not be added;
+else ([else])
+ : Task will be added to Task List;
+endif
+ : Task List is updated;
+stop
+@enduml
diff --git a/docs/diagrams/addTask/AddTaskParserClassDiagram.puml b/docs/diagrams/addTask/AddTaskParserClassDiagram.puml
new file mode 100644
index 00000000000..26fcf4785a4
--- /dev/null
+++ b/docs/diagrams/addTask/AddTaskParserClassDiagram.puml
@@ -0,0 +1,13 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+!define ABSTRACT {abstract}
+
+interface Parser <>
+class AddCommandParser
+
+Parser <|.. AddCommandParser
+
+@enduml
diff --git a/docs/diagrams/addTask/AddTaskSequenceDiagram.puml b/docs/diagrams/addTask/AddTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..4f7d9a9267a
--- /dev/null
+++ b/docs/diagrams/addTask/AddTaskSequenceDiagram.puml
@@ -0,0 +1,72 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant "d:AddTodoCommand" as AddTodoCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "t:Todo" as Todo MODEL_COLOR
+end box
+
+[-> LogicManager : execute("add todo ...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("add todo ...")
+activate AddressBookParser
+
+create AddCommandParser
+AddressBookParser -> AddCommandParser
+activate AddCommandParser
+
+create Todo
+AddCommandParser -> Todo : new Todo("Complete homework date", "12-12-2020 2359" )
+activate Todo
+
+Todo --> AddCommandParser : t
+deactivate Todo
+
+create AddTodoCommand
+AddCommandParser -> AddTodoCommand : new AddTodoCommand(t)
+activate AddTodoCommand
+
+AddTodoCommand --> AddCommandParser : d
+deactivate AddTodoCommand
+
+AddCommandParser --> AddressBookParser : d
+deactivate AddCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddCommandParser -[hidden]-> AddressBookParser
+destroy AddCommandParser
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> AddTodoCommand : execute()
+activate AddTodoCommand
+
+
+AddTodoCommand -> Model : addTodo(t)
+activate Model
+
+Model --> AddTodoCommand
+deactivate Model
+
+create CommandResult
+AddTodoCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddTodoCommand
+deactivate CommandResult
+
+AddTodoCommand --> LogicManager : result
+deactivate AddTodoCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/addTask/CommandClassDiagram.puml b/docs/diagrams/addTask/CommandClassDiagram.puml
new file mode 100644
index 00000000000..a70ca375586
--- /dev/null
+++ b/docs/diagrams/addTask/CommandClassDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+!define ABSTRACT {abstract}
+
+abstract class "{abstract}\nCommand"
+abstract class "{abstract}\nAddCommand"
+class AddTodoCommand
+class AddEventCommand
+
+
+"{abstract}\nCommand" <|-- "{abstract}\nAddCommand"
+
+
+"{abstract}\nAddCommand" <|-- AddTodoCommand
+"{abstract}\nAddCommand" <|-- AddEventCommand
+
+@enduml
diff --git a/docs/diagrams/addTask/style.puml b/docs/diagrams/addTask/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/addTask/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/contactTaskTag/CommandClassDiagram.puml b/docs/diagrams/contactTaskTag/CommandClassDiagram.puml
new file mode 100644
index 00000000000..2c85af6509b
--- /dev/null
+++ b/docs/diagrams/contactTaskTag/CommandClassDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+abstract class "{abstract}\nCommand" as Command
+class ContactTaskTagCommand
+
+Command <|-- ContactTaskTagCommand
+
+@enduml
diff --git a/docs/diagrams/contactTaskTag/ParserClassDiagram.puml b/docs/diagrams/contactTaskTag/ParserClassDiagram.puml
new file mode 100644
index 00000000000..012aa37d0db
--- /dev/null
+++ b/docs/diagrams/contactTaskTag/ParserClassDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+interface "Parser " <>
+class ContactTaskTagParser
+
+"Parser " <|.. ContactTaskTagParser
+
+@enduml
diff --git a/docs/diagrams/contactTaskTag/contactTaskTagActivityDiagram.puml b/docs/diagrams/contactTaskTag/contactTaskTagActivityDiagram.puml
new file mode 100644
index 00000000000..da8edd4c2c7
--- /dev/null
+++ b/docs/diagrams/contactTaskTag/contactTaskTagActivityDiagram.puml
@@ -0,0 +1,17 @@
+@startuml
+start
+:User executes contactTaskTag command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([Task and Contact index are valid,
+ and >= 1 tag is given])
+ : The selected contact and task will be updated
+ with the new common tag(s);
+else ([else])
+ : Parse Exception will be thrown;
+endif
+ : Results shown;
+stop
+@enduml
diff --git a/docs/diagrams/contactTaskTag/contactTaskTagSequenceDiagram.puml b/docs/diagrams/contactTaskTag/contactTaskTagSequenceDiagram.puml
new file mode 100644
index 00000000000..3b826e82408
--- /dev/null
+++ b/docs/diagrams/contactTaskTag/contactTaskTagSequenceDiagram.puml
@@ -0,0 +1,87 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ContactTaskTagParser" as ContactTaskTagParser LOGIC_COLOR
+participant "d:ContactTaskTagCommand" as ContactTaskTagCommand 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("contactTaskTag...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("contactTaskTag...")
+activate AddressBookParser
+
+create ContactTaskTagParser
+AddressBookParser -> ContactTaskTagParser
+activate ContactTaskTagParser
+
+ContactTaskTagParser --> AddressBookParser
+deactivate ContactTaskTagParser
+
+AddressBookParser -> ContactTaskTagParser : parse("t/CS2103T contactIndex/1 taskIndex/1")
+activate ContactTaskTagParser
+
+create ContactTaskTagCommand
+ContactTaskTagParser -> ContactTaskTagCommand
+activate ContactTaskTagCommand
+
+ContactTaskTagCommand --> ContactTaskTagParser : c
+deactivate ContactTaskTagCommand
+
+ContactTaskTagParser --> AddressBookParser : c
+deactivate ContactTaskTagParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ContactTaskTagParser -[hidden]-> AddressBookParser
+destroy ContactTaskTagParser
+
+AddressBookParser --> LogicManager : c
+deactivate AddressBookParser
+
+LogicManager -> ContactTaskTagCommand : execute()
+activate ContactTaskTagCommand
+
+ContactTaskTagCommand -> Model : setPerson(personToEdit, editedPerson)
+activate Model
+
+Model --> ContactTaskTagCommand
+deactivate Model
+
+ContactTaskTagCommand -> Model : updateFilteredPersonList()
+activate Model
+
+Model --> ContactTaskTagCommand
+deactivate Model
+
+ContactTaskTagCommand -> Model : setTask(taskToEdit, editedTask)
+activate Model
+
+Model --> ContactTaskTagCommand
+deactivate Model
+
+ContactTaskTagCommand -> Model : updateFilteredTaskList()
+activate Model
+
+Model --> ContactTaskTagCommand
+deactivate Model
+
+create CommandResult
+ContactTaskTagCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ContactTaskTagCommand
+deactivate CommandResult
+
+ContactTaskTagCommand --> LogicManager : result
+deactivate ContactTaskTagCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contactTaskTag/style.puml b/docs/diagrams/contactTaskTag/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/contactTaskTag/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/deleteTask/CommandClassDiagram.puml b/docs/diagrams/deleteTask/CommandClassDiagram.puml
new file mode 100644
index 00000000000..81faf93658b
--- /dev/null
+++ b/docs/diagrams/deleteTask/CommandClassDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+!define ABSTRACT {abstract}
+
+abstract class "{abstract}\nCommand"
+
+abstract class "{abstract}\nDeleteCommand"
+class DeleteTodoCommand
+class DeleteEventCommand
+
+"{abstract}\nCommand" <|-- "{abstract}\nDeleteCommand"
+
+"{abstract}\nDeleteCommand" <|-- DeleteTodoCommand
+"{abstract}\nDeleteCommand" <|-- DeleteEventCommand
+
+@enduml
diff --git a/docs/diagrams/deleteTask/style.puml b/docs/diagrams/deleteTask/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/deleteTask/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/editTask/CommandClassDiagram.puml b/docs/diagrams/editTask/CommandClassDiagram.puml
new file mode 100644
index 00000000000..1c278db19fa
--- /dev/null
+++ b/docs/diagrams/editTask/CommandClassDiagram.puml
@@ -0,0 +1,22 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+!define ABSTRACT {abstract}
+
+abstract class "{abstract}\nCommand"
+abstract class "{abstract}\nEditCommand"
+class EditContactCommand
+class EditTodoCommand
+class EditEventCommand
+
+
+"{abstract}\nCommand" <|-- "{abstract}\nEditCommand"
+
+
+"{abstract}\nEditCommand" <|-- EditContactCommand
+"{abstract}\nEditCommand" <|-- EditTodoCommand
+"{abstract}\nEditCommand" <|-- EditEventCommand
+
+@enduml
diff --git a/docs/diagrams/editTask/EditTaskActivityDiagram.puml b/docs/diagrams/editTask/EditTaskActivityDiagram.puml
new file mode 100644
index 00000000000..8e328e56113
--- /dev/null
+++ b/docs/diagrams/editTask/EditTaskActivityDiagram.puml
@@ -0,0 +1,16 @@
+@startuml
+start
+:User executes an edit task command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([The command is valid (valid index
+and minimal one field is edited)])
+ :Task will be edited;
+else ([else])
+ : The command will not be executed;
+endif
+ : Task List is updated;
+stop
+@enduml
diff --git a/docs/diagrams/editTask/EditTaskParserClassDiagram.puml b/docs/diagrams/editTask/EditTaskParserClassDiagram.puml
new file mode 100644
index 00000000000..61a9c0813ce
--- /dev/null
+++ b/docs/diagrams/editTask/EditTaskParserClassDiagram.puml
@@ -0,0 +1,13 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+!define ABSTRACT {abstract}
+
+interface Parser <>
+class EditCommand
+
+Parser <|.. EditCommandParser
+
+@enduml
diff --git a/docs/diagrams/editTask/EditTaskSequence.puml b/docs/diagrams/editTask/EditTaskSequence.puml
new file mode 100644
index 00000000000..79463de72ed
--- /dev/null
+++ b/docs/diagrams/editTask/EditTaskSequence.puml
@@ -0,0 +1,72 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EditCommandParser" as EditCommandParser LOGIC_COLOR
+participant "d:EditEventCommand" as EditEventCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "e:Event" as Event MODEL_COLOR
+end box
+
+[-> LogicManager : execute("edit event desc/Complete homework starttime/2359")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("edit event desc/Complete homework starttime/2359")
+activate AddressBookParser
+
+create EditCommandParser
+AddressBookParser -> EditCommandParser
+activate EditCommandParser
+
+create Event
+EditCommandParser -> Event : new Event("Complete homework date", "12-12-2020 2359" )
+activate Event
+
+Event --> EditCommandParser : e
+deactivate Event
+
+create EditEventCommand
+EditCommandParser -> EditEventCommand : new EditEventCommand(e)
+activate EditEventCommand
+
+EditEventCommand --> EditCommandParser : d
+deactivate EditEventCommand
+
+EditCommandParser --> AddressBookParser : d
+deactivate EditCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+EditCommandParser -[hidden]-> AddressBookParser
+destroy EditCommandParser
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> EditEventCommand : execute()
+activate EditEventCommand
+
+
+EditEventCommand -> Model : setTask(eventToEdit, editedEvent)
+activate Model
+
+Model --> EditEventCommand
+deactivate Model
+
+create CommandResult
+EditEventCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditEventCommand
+deactivate CommandResult
+
+EditEventCommand --> LogicManager : result
+deactivate EditEventCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/editTask/EditTaskSequenceDiagram.puml b/docs/diagrams/editTask/EditTaskSequenceDiagram.puml
new file mode 100644
index 00000000000..e7ebb93bf91
--- /dev/null
+++ b/docs/diagrams/editTask/EditTaskSequenceDiagram.puml
@@ -0,0 +1,33 @@
+@startuml
+!include style.puml
+
+Actor User as user USER_COLOR
+Participant ":UI" as ui UI_COLOR
+Participant ":Logic" as logic LOGIC_COLOR
+Participant ":Model" as model MODEL_COLOR
+
+user -[USER_COLOR]> ui : "edit event desc/Complete homework starttime/2359"
+activate ui UI_COLOR
+
+ui -[UI_COLOR]> logic : execute("edit event desc/Complete homework starttime/2359")
+activate logic LOGIC_COLOR
+
+logic -[LOGIC_COLOR]>model : new Event("Complete homework date", "12-12-2020 2359" )
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]-> logic: event
+deactivate model
+
+logic -[LOGIC_COLOR]> model : setTask(eventToEdit, editedEvent)
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]-> logic
+deactivate model
+
+logic --[LOGIC_COLOR]> ui
+deactivate logic
+
+ui--[UI_COLOR]> user
+deactivate ui
+
+@enduml
diff --git a/docs/diagrams/editTask/style.puml b/docs/diagrams/editTask/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/editTask/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/filterFunction/CommandClassDiagram.puml b/docs/diagrams/filterFunction/CommandClassDiagram.puml
new file mode 100644
index 00000000000..030370b06d5
--- /dev/null
+++ b/docs/diagrams/filterFunction/CommandClassDiagram.puml
@@ -0,0 +1,14 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+abstract class "{abstract}\nCommand" as Command
+class DueBeforeCommand
+class DueAtCommand
+
+Command <|-- DueBeforeCommand
+Command <|-- DueAtCommand
+
+@enduml
diff --git a/docs/diagrams/filterFunction/FilterActivityDiagram.puml b/docs/diagrams/filterFunction/FilterActivityDiagram.puml
new file mode 100644
index 00000000000..839bcc408f7
--- /dev/null
+++ b/docs/diagrams/filterFunction/FilterActivityDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+start
+:User executes itemsDueAt / itemsDueBefore command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([task matches predicate])
+ :Task will be displayed;
+else ([else])
+ : Task will not be displayed;
+endif
+ : Results shown;
+stop
+@enduml
diff --git a/docs/diagrams/filterFunction/FilterSequenceDiagram.puml b/docs/diagrams/filterFunction/FilterSequenceDiagram.puml
new file mode 100644
index 00000000000..191363fdcf8
--- /dev/null
+++ b/docs/diagrams/filterFunction/FilterSequenceDiagram.puml
@@ -0,0 +1,27 @@
+@startuml
+!include style.puml
+
+Actor User as user USER_COLOR
+Participant ":UI" as ui UI_COLOR
+Participant ":Logic" as logic LOGIC_COLOR
+Participant ":Model" as model MODEL_COLOR
+
+user -[USER_COLOR]> ui : "itemsDueBefore date/12-12-2020 time/2359"
+activate ui UI_COLOR
+
+ui -[UI_COLOR]> logic : execute("itemsDueBefore date/12-12-2020 time/2359")
+activate logic LOGIC_COLOR
+
+logic -[LOGIC_COLOR]> model : updateFilteredTaskList(predicate)
+activate model MODEL_COLOR
+
+model -[MODEL_COLOR]-> logic
+deactivate model
+
+logic --[LOGIC_COLOR]> ui
+deactivate logic
+
+ui--[UI_COLOR]> user
+deactivate ui
+
+@enduml
diff --git a/docs/diagrams/filterFunction/ParserClassDiagram.puml b/docs/diagrams/filterFunction/ParserClassDiagram.puml
new file mode 100644
index 00000000000..2a88e7fff4f
--- /dev/null
+++ b/docs/diagrams/filterFunction/ParserClassDiagram.puml
@@ -0,0 +1,14 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+interface Parser <>
+class DueBeforeCommandParser
+class DueAtCommandParser
+
+Parser <|.. DueBeforeCommandParser
+Parser <|.. DueAtCommandParser
+
+@enduml
diff --git a/docs/diagrams/filterFunction/PredicateClassDiagram.puml b/docs/diagrams/filterFunction/PredicateClassDiagram.puml
new file mode 100644
index 00000000000..64fea3ebf6b
--- /dev/null
+++ b/docs/diagrams/filterFunction/PredicateClassDiagram.puml
@@ -0,0 +1,14 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+abstract class "{abstract}\nDuePredicate" as DuePredicate
+class DueBeforePredicate
+class DueAtPredicate
+
+DuePredicate <|-- DueBeforePredicate
+DuePredicate <|-- DueAtPredicate
+
+@enduml
diff --git a/docs/diagrams/filterFunction/style.puml b/docs/diagrams/filterFunction/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/filterFunction/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/findFunction/ContactMatchesFindKeywordPredicate.puml b/docs/diagrams/findFunction/ContactMatchesFindKeywordPredicate.puml
new file mode 100644
index 00000000000..278cdc430c7
--- /dev/null
+++ b/docs/diagrams/findFunction/ContactMatchesFindKeywordPredicate.puml
@@ -0,0 +1,12 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+interface "Predicate" <>
+class ContactMatchesFindKeywordPredicate
+
+"Predicate" <|.. ContactMatchesFindKeywordPredicate
+
+@enduml
diff --git a/docs/diagrams/findFunction/FindCommandActivityDiagram.puml b/docs/diagrams/findFunction/FindCommandActivityDiagram.puml
new file mode 100644
index 00000000000..5027fdbe923
--- /dev/null
+++ b/docs/diagrams/findFunction/FindCommandActivityDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+start
+:User executes find contact command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([contact matches predicate])
+ :Contact will be displayed;
+else ([else])
+ : Contact will not be displayed;
+endif
+ : Results shown;
+stop
+@enduml
diff --git a/docs/diagrams/findFunction/FindCommandClassDiagram.puml b/docs/diagrams/findFunction/FindCommandClassDiagram.puml
new file mode 100644
index 00000000000..01c870dfbe6
--- /dev/null
+++ b/docs/diagrams/findFunction/FindCommandClassDiagram.puml
@@ -0,0 +1,18 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+abstract class "{abstract}\nCommand" as Command
+abstract class "{abstract}\nFindCommand" as FindCommand
+class FindContactCommand
+class FindEventCommand
+class FindTodoCommand
+
+Command <|-- FindCommand
+FindCommand <|-- FindContactCommand
+FindCommand <|-- FindEventCommand
+FindCommand <|-- FindTodoCommand
+
+@enduml
diff --git a/docs/diagrams/findFunction/FindCommandParserClassDiagram.puml b/docs/diagrams/findFunction/FindCommandParserClassDiagram.puml
new file mode 100644
index 00000000000..bd7e6dcc23e
--- /dev/null
+++ b/docs/diagrams/findFunction/FindCommandParserClassDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+interface "Parser" <>
+class FindCommandParser
+
+"Parser" <|.. FindCommandParser
+
+@enduml
diff --git a/docs/diagrams/findFunction/FindCommandSequenceDiagram.puml b/docs/diagrams/findFunction/FindCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..c75ef67221d
--- /dev/null
+++ b/docs/diagrams/findFunction/FindCommandSequenceDiagram.puml
@@ -0,0 +1,94 @@
+@startuml
+!include ../style.puml
+
+box Ui UI_COLOR_T1
+Participant ":UI" as ui UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "s:FindContactCommand" as FindContactCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+Participant ":Storage" as Storage STORAGE_COLOR
+end box
+
+ -[USER_COLOR]> ui : "find contact n/John t/CS2100Teammate"
+activate ui UI_COLOR
+
+ui -[UI_COLOR]> LogicManager : execute("find ...")
+activate LogicManager LOGIC_COLOR
+
+LogicManager -[LOGIC_COLOR]> AddressBookParser : parseCommand("find ...")
+activate AddressBookParser LOGIC_COLOR
+
+create FindCommandParser
+AddressBookParser -[LOGIC_COLOR]> FindCommandParser
+activate FindCommandParser LOGIC_COLOR
+
+FindCommandParser -[LOGIC_COLOR]-> AddressBookParser
+deactivate FindCommandParser
+
+
+AddressBookParser -[LOGIC_COLOR]> FindCommandParser : parse("contact...")
+activate FindCommandParser LOGIC_COLOR
+
+create FindContactCommand
+FindCommandParser -[LOGIC_COLOR]> FindContactCommand
+activate FindContactCommand LOGIC_COLOR
+
+FindContactCommand -[LOGIC_COLOR]-> FindCommandParser : s
+deactivate FindContactCommand
+
+FindCommandParser -[LOGIC_COLOR]-> AddressBookParser : s
+deactivate FindCommandParser
+
+FindCommandParser -[hidden]-> AddressBookParser
+destroy FindCommandParser
+
+
+AddressBookParser -[LOGIC_COLOR]-> LogicManager : s
+deactivate AddressBookParser
+
+LogicManager -[LOGIC_COLOR]> FindContactCommand : execute()
+activate FindContactCommand LOGIC_COLOR
+
+FindContactCommand -[LOGIC_COLOR]> Model : updateFilteredPersonList(predicate)
+activate Model MODEL_COLOR
+
+Model -[MODEL_COLOR]-> FindContactCommand
+deactivate Model
+
+group ref save file
+LogicManager -[hidden]-> Storage
+end group
+
+create CommandResult
+FindContactCommand -[LOGIC_COLOR]> CommandResult
+activate CommandResult LOGIC_COLOR
+
+CommandResult -[LOGIC_COLOR]-> FindContactCommand
+deactivate CommandResult
+CommandResult -[hidden]-> FindContactCommand
+destroy CommandResult
+
+FindContactCommand -[LOGIC_COLOR]-> LogicManager : result
+deactivate FindContactCommand
+
+FindContactCommand -[hidden]-> FindCommandParser
+destroy FindContactCommand
+
+ui <-[LOGIC_COLOR]-LogicManager : result
+deactivate LogicManager
+
+ <-[UI_COLOR]-ui : result
+deactivate ui
+@enduml
diff --git a/docs/diagrams/findFunction/SaveLifebook.puml b/docs/diagrams/findFunction/SaveLifebook.puml
new file mode 100644
index 00000000000..175aa251434
--- /dev/null
+++ b/docs/diagrams/findFunction/SaveLifebook.puml
@@ -0,0 +1,29 @@
+@startuml
+!include ../style.puml
+
+group sd save file
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":Model" as Model MODEL_COLOR
+Participant ":Storage" as Storage STORAGE_COLOR
+
+LogicManager -[LOGIC_COLOR]> Model : getAddressBook()
+activate Model MODEL_COLOR
+Model --[MODEL_COLOR]> LogicManager : ReadOnlyAddressBook
+deactivate Model
+
+LogicManager -[LOGIC_COLOR]> Storage : saveAddressBook(ReadOnlyAddressBook)
+activate Storage STORAGE_COLOR
+Storage --[STORAGE_COLOR]> LogicManager
+deactivate Storage
+
+LogicManager -[LOGIC_COLOR]> Model : getTaskList()
+activate Model MODEL_COLOR
+Model --[MODEL_COLOR]> LogicManager : ReadOnlyTaskList
+deactivate Model
+
+LogicManager -[LOGIC_COLOR]> Storage : saveTaskList(ReadOnlyTaskList)
+activate Storage STORAGE_COLOR
+Storage --[STORAGE_COLOR]> LogicManager
+deactivate Storage
+end group
+@enduml
diff --git a/docs/diagrams/findFunction/TaskMatchesFindKeywordPredicate.puml b/docs/diagrams/findFunction/TaskMatchesFindKeywordPredicate.puml
new file mode 100644
index 00000000000..5d8f947190a
--- /dev/null
+++ b/docs/diagrams/findFunction/TaskMatchesFindKeywordPredicate.puml
@@ -0,0 +1,12 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+interface "Predicate" <>
+class TaskMatchesFindKeywordPredicate
+
+"Predicate" <|.. TaskMatchesFindKeywordPredicate
+
+@enduml
diff --git a/docs/diagrams/linkFunction/CommandClassDiagram.puml b/docs/diagrams/linkFunction/CommandClassDiagram.puml
new file mode 100644
index 00000000000..8438cc6e8e9
--- /dev/null
+++ b/docs/diagrams/linkFunction/CommandClassDiagram.puml
@@ -0,0 +1,16 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+abstract class "{abstract}\nCommand" as Command
+class LinkCollaborativeCommand
+class LinkMeetingCommand
+abstract class "{abstract}\nLinkCommand" as LinkCommand
+
+Command <|-- LinkCommand
+LinkCommand <|-- LinkCollaborativeCommand
+LinkCommand <|-- LinkMeetingCommand
+
+@enduml
diff --git a/docs/diagrams/linkFunction/LinkActivityDiagram.puml b/docs/diagrams/linkFunction/LinkActivityDiagram.puml
new file mode 100644
index 00000000000..469c9c9ae3b
--- /dev/null
+++ b/docs/diagrams/linkFunction/LinkActivityDiagram.puml
@@ -0,0 +1,19 @@
+@startuml
+start
+:User executes `link meeting` / `link doc` command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([command is valid])
+ if () then([command is link meeting])
+ : Add or edit the link to event;
+ else ([else])
+ : Add or edit the link to todo;
+ endif
+else ([else])
+ : Error message displayed;
+endif
+ : Results shown;
+stop
+@enduml
diff --git a/docs/diagrams/linkFunction/LinkSequenceDiagram.puml b/docs/diagrams/linkFunction/LinkSequenceDiagram.puml
new file mode 100644
index 00000000000..3041322ffcf
--- /dev/null
+++ b/docs/diagrams/linkFunction/LinkSequenceDiagram.puml
@@ -0,0 +1,106 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":LinkCommandParser" as LinkCommandParser LOGIC_COLOR
+participant "l:LinkCollaborativeCommand" as LinkCollaborativeCommand LOGIC_COLOR
+participant "l:LinkMeetingCommand" as LinkMeetingCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+Participant ":Storage" as Storage STORAGE_COLOR
+end box
+
+[-> LogicManager : execute("link doc desc/CS2103T url/www.github.com i/1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("link ...")
+activate AddressBookParser
+
+create LinkCommandParser
+AddressBookParser -> LinkCommandParser
+activate LinkCommandParser
+
+LinkCommandParser --> AddressBookParser
+deactivate LinkCommandParser
+alt is a collaborative link
+
+ AddressBookParser -> LinkCommandParser : parse("doc desc/CS2103T url/www.github.com i/1")
+ activate LinkCommandParser
+
+ create LinkCollaborativeCommand
+ LinkCommandParser -> LinkCollaborativeCommand
+ activate LinkCollaborativeCommand
+
+ LinkCollaborativeCommand --> LinkCommandParser : l
+ deactivate LinkCollaborativeCommand
+
+ LinkCommandParser --> AddressBookParser : l
+ deactivate LinkCommandParser
+ 'Hidden arrow to position the destroy marker below the end of the activation bar.
+ LinkCommandParser -[hidden]-> AddressBookParser
+ destroy LinkCommandParser
+
+else is a meeting link
+
+ AddressBookParser -> LinkCommandParser : parse("meeting desc/CS2103T Lecture url/www.zoom.com i/1 ...")
+ activate LinkCommandParser
+
+ create LinkMeetingCommand
+ LinkCommandParser -> LinkMeetingCommand
+ activate LinkMeetingCommand
+
+ LinkMeetingCommand --> LinkCommandParser : l
+ deactivate LinkMeetingCommand
+
+ LinkCommandParser --> AddressBookParser : l
+ deactivate LinkCommandParser
+ 'Hidden arrow to position the destroy marker below the end of the activation bar.
+ LinkCommandParser -[hidden]-> AddressBookParser
+ destroy LinkCommandParser
+end
+
+AddressBookParser --> LogicManager : l
+deactivate AddressBookParser
+
+LogicManager -> LinkCollaborativeCommand : execute()
+activate LinkCollaborativeCommand
+
+LinkCollaborativeCommand -> Model : setTask(taskToEdit, editedTask)
+activate Model
+
+Model --> LinkCollaborativeCommand
+deactivate Model
+
+LogicManager -[LOGIC_COLOR]> Storage : saveLifeBook(LifeBook)
+activate Storage STORAGE_COLOR
+
+Storage -[STORAGE_COLOR]> Storage : Save\nto file
+activate Storage STORAGE_COLOR_T1
+Storage --[STORAGE_COLOR]> Storage
+deactivate Storage
+
+Storage --[STORAGE_COLOR]> LogicManager
+deactivate Storage
+
+create CommandResult
+LinkCollaborativeCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> LinkCollaborativeCommand
+deactivate CommandResult
+
+LinkCollaborativeCommand --> LogicManager : result
+deactivate LinkCollaborativeCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/linkFunction/ParserClassDiagram.puml b/docs/diagrams/linkFunction/ParserClassDiagram.puml
new file mode 100644
index 00000000000..3dc80b480a8
--- /dev/null
+++ b/docs/diagrams/linkFunction/ParserClassDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR
+skinparam classBackgroundColor LOGIC_COLOR
+
+interface Parser <>
+class LinkCommandParser
+
+Parser <|.. LinkCommandParser
+
+@enduml
diff --git a/docs/diagrams/linkFunction/style.puml b/docs/diagrams/linkFunction/style.puml
new file mode 100644
index 00000000000..fad8b0adeaa
--- /dev/null
+++ b/docs/diagrams/linkFunction/style.puml
@@ -0,0 +1,75 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define UI_COLOR #1D8900
+!define UI_COLOR_T1 #83E769
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define STORAGE_COLOR #A38300
+!define STORAGE_COLOR_T1 #FFE374
+!define STORAGE_COLOR_T2 #EDC520
+!define STORAGE_COLOR_T3 #806600
+!define STORAGE_COLOR_T2 #544400
+
+!define USER_COLOR #000000
+
+skinparam BackgroundColor #FFFFFFF
+
+skinparam Shadowing false
+
+skinparam Class {
+ FontColor #FFFFFF
+ BorderThickness 1
+ BorderColor #FFFFFF
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+hide footbox
+hide members
+hide circle
diff --git a/docs/diagrams/storage/ReadFileActivityDiagram.puml b/docs/diagrams/storage/ReadFileActivityDiagram.puml
new file mode 100644
index 00000000000..04e8d1a9476
--- /dev/null
+++ b/docs/diagrams/storage/ReadFileActivityDiagram.puml
@@ -0,0 +1,18 @@
+@startuml
+start
+:Lifebook reads TaskList from file;
+
+if () then ([hasFile])
+ : Parse file;
+ if () then ([hasException]);
+ : Throw exception;
+ stop
+ else ([success])
+ : Return TaskList;
+ endif
+
+else ([else])
+ : Return empty;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/storage/ReadTaskList.puml b/docs/diagrams/storage/ReadTaskList.puml
new file mode 100644
index 00000000000..ad34006a18f
--- /dev/null
+++ b/docs/diagrams/storage/ReadTaskList.puml
@@ -0,0 +1,19 @@
+@startuml
+!include ../style.puml
+
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":Storage" as Storage STORAGE_COLOR
+participant ":TaskListStorage" as TaskListStorage STORAGE_COLOR
+
+
+
+LogicManager -[LOGIC_COLOR]> Storage : readTaskList()
+activate Storage STORAGE_COLOR
+Storage -[STORAGE_COLOR]> TaskListStorage : readTaskList()
+activate TaskListStorage STORAGE_COLOR
+TaskListStorage --[STORAGE_COLOR]> Storage
+deactivate TaskListStorage
+Storage --[STORAGE_COLOR]> LogicManager
+deactivate Storage
+
+@enduml
diff --git a/docs/diagrams/storage/SaveTaskList.puml b/docs/diagrams/storage/SaveTaskList.puml
new file mode 100644
index 00000000000..ef58f5d5163
--- /dev/null
+++ b/docs/diagrams/storage/SaveTaskList.puml
@@ -0,0 +1,19 @@
+@startuml
+!include ../style.puml
+
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":Storage" as Storage STORAGE_COLOR
+participant ":TaskListStorage" as TaskListStorage STORAGE_COLOR
+
+
+
+LogicManager -[LOGIC_COLOR]> Storage : saveTaskList(ReadOnlyTaskList)
+activate Storage STORAGE_COLOR
+Storage -[STORAGE_COLOR]> TaskListStorage : saveTaskList(ReadOnlyTaskList)
+activate TaskListStorage STORAGE_COLOR
+TaskListStorage --[STORAGE_COLOR]> Storage
+deactivate TaskListStorage
+Storage --[STORAGE_COLOR]> LogicManager
+deactivate Storage
+
+@enduml
diff --git a/docs/diagrams/storage/StorageClassDiagram.puml b/docs/diagrams/storage/StorageClassDiagram.puml
new file mode 100644
index 00000000000..80850c92bb6
--- /dev/null
+++ b/docs/diagrams/storage/StorageClassDiagram.puml
@@ -0,0 +1,25 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor STORAGE_COLOR
+skinparam classBackgroundColor STORAGE_COLOR
+
+
+Class Storage <>
+Class UserPrefsStorage <>
+Class TaskListStorage <>
+Class AddressBookStorage <>
+Class StorageManager
+Class JsonUserPrefsStorage
+Class JsonTaskListStorage
+
+
+StorageManager .up.|> Storage
+StorageManager o-down-> UserPrefsStorage
+StorageManager o-down-> AddressBookStorage
+StorageManager o-down-> TaskListStorage
+
+JsonUserPrefsStorage .up.|> UserPrefsStorage
+JsonAddressBookStorage .up.|> AddressBookStorage
+JsonTaskListStorage .up.|> TaskListStorage
+@enduml
diff --git a/docs/diagrams/storage/TaskListStorageClassDiagram.puml b/docs/diagrams/storage/TaskListStorageClassDiagram.puml
new file mode 100644
index 00000000000..5a6b8b0c8cf
--- /dev/null
+++ b/docs/diagrams/storage/TaskListStorageClassDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor STORAGE_COLOR
+skinparam classBackgroundColor STORAGE_COLOR
+
+
+Class TaskListStorage <>
+Class JsonTaskListStorage
+Class JsonSerializableTaskListManager
+Class "{abstract}\nJsonAdaptedTask" as JsonAdaptedTask
+
+JsonTaskListStorage .up.|> TaskListStorage
+JsonTaskListStorage ..> JsonSerializableTaskListManager
+JsonSerializableTaskListManager --> " * " JsonAdaptedTask
+JsonAdaptedTodo .up.|> JsonAdaptedTask
+JsonAdaptedEvent .up.|> JsonAdaptedTask
+
+JsonAdaptedTodo --> " * " JsonAdaptedTag
+JsonAdaptedEvent --> " * " JsonAdaptedTag
+JsonAdaptedTodo --> " 0,1 " JsonAdaptedRecurrence
+JsonAdaptedEvent --> "0,1 " JsonAdaptedRecurrence
+
+@enduml
diff --git a/docs/images/DeleteContactSequenceDiagram.png b/docs/images/DeleteContactSequenceDiagram.png
new file mode 100644
index 00000000000..b27248828af
Binary files /dev/null and b/docs/images/DeleteContactSequenceDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
deleted file mode 100644
index fa327b39618..00000000000
Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/EditEventSequenceDiagram.png b/docs/images/EditEventSequenceDiagram.png
new file mode 100644
index 00000000000..2d6b158853b
Binary files /dev/null and b/docs/images/EditEventSequenceDiagram.png differ
diff --git a/docs/images/ExampleOfARecurringTask.png b/docs/images/ExampleOfARecurringTask.png
new file mode 100644
index 00000000000..29643e43109
Binary files /dev/null and b/docs/images/ExampleOfARecurringTask.png differ
diff --git a/docs/images/LifebookHomescreen.png b/docs/images/LifebookHomescreen.png
new file mode 100644
index 00000000000..39a21e4149c
Binary files /dev/null and b/docs/images/LifebookHomescreen.png differ
diff --git a/docs/images/PPP-kevnw/linkmeeting.png b/docs/images/PPP-kevnw/linkmeeting.png
new file mode 100644
index 00000000000..9f44a30a3fd
Binary files /dev/null and b/docs/images/PPP-kevnw/linkmeeting.png differ
diff --git a/docs/images/PPP-kevnw/tableofcontents.png b/docs/images/PPP-kevnw/tableofcontents.png
new file mode 100644
index 00000000000..ae2e1cf9a4e
Binary files /dev/null and b/docs/images/PPP-kevnw/tableofcontents.png differ
diff --git a/docs/images/PPP-luciatirta/findContactCommand.png b/docs/images/PPP-luciatirta/findContactCommand.png
new file mode 100644
index 00000000000..4ba885deacc
Binary files /dev/null and b/docs/images/PPP-luciatirta/findContactCommand.png differ
diff --git a/docs/images/SortedTaskList.PNG b/docs/images/SortedTaskList.PNG
new file mode 100644
index 00000000000..241457f17c9
Binary files /dev/null and b/docs/images/SortedTaskList.PNG differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..083cdcabd39 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UpdatedBetterModelClassDiagram.png b/docs/images/UpdatedBetterModelClassDiagram.png
new file mode 100644
index 00000000000..69b52c2806d
Binary files /dev/null and b/docs/images/UpdatedBetterModelClassDiagram.png differ
diff --git a/docs/images/UpdatedModelClassDiagram.png b/docs/images/UpdatedModelClassDiagram.png
new file mode 100644
index 00000000000..931a78657d2
Binary files /dev/null and b/docs/images/UpdatedModelClassDiagram.png differ
diff --git a/docs/images/UpdatedUiClassDiagram.png b/docs/images/UpdatedUiClassDiagram.png
new file mode 100644
index 00000000000..85bf52ef6d7
Binary files /dev/null and b/docs/images/UpdatedUiClassDiagram.png differ
diff --git a/docs/images/addTask/AddSequenceDiagram.png b/docs/images/addTask/AddSequenceDiagram.png
new file mode 100644
index 00000000000..e5ee12cfd53
Binary files /dev/null and b/docs/images/addTask/AddSequenceDiagram.png differ
diff --git a/docs/images/addTask/AddTaskActivityDiagram.png b/docs/images/addTask/AddTaskActivityDiagram.png
new file mode 100644
index 00000000000..9d360c74959
Binary files /dev/null and b/docs/images/addTask/AddTaskActivityDiagram.png differ
diff --git a/docs/images/addTask/AddTaskParserClassDiagram.png b/docs/images/addTask/AddTaskParserClassDiagram.png
new file mode 100644
index 00000000000..76d2523167c
Binary files /dev/null and b/docs/images/addTask/AddTaskParserClassDiagram.png differ
diff --git a/docs/images/addTask/AddTaskSequenceDiagram.png b/docs/images/addTask/AddTaskSequenceDiagram.png
new file mode 100644
index 00000000000..e481bf93074
Binary files /dev/null and b/docs/images/addTask/AddTaskSequenceDiagram.png differ
diff --git a/docs/images/addTask/CommandClassDiagram.png b/docs/images/addTask/CommandClassDiagram.png
new file mode 100644
index 00000000000..2f6cd573909
Binary files /dev/null and b/docs/images/addTask/CommandClassDiagram.png differ
diff --git a/docs/images/caleblyx.png b/docs/images/caleblyx.png
new file mode 100644
index 00000000000..e7fb73ea225
Binary files /dev/null and b/docs/images/caleblyx.png differ
diff --git a/docs/images/contactTaskTag.png b/docs/images/contactTaskTag.png
new file mode 100644
index 00000000000..8565abe3a24
Binary files /dev/null and b/docs/images/contactTaskTag.png differ
diff --git a/docs/images/contactTaskTag/CommandClassDiagram.png b/docs/images/contactTaskTag/CommandClassDiagram.png
new file mode 100644
index 00000000000..1a9b32986c3
Binary files /dev/null and b/docs/images/contactTaskTag/CommandClassDiagram.png differ
diff --git a/docs/images/contactTaskTag/ParserClassDiagram.png b/docs/images/contactTaskTag/ParserClassDiagram.png
new file mode 100644
index 00000000000..e568023a0e4
Binary files /dev/null and b/docs/images/contactTaskTag/ParserClassDiagram.png differ
diff --git a/docs/images/contactTaskTag/contactTaskTagActivityDiagram.png b/docs/images/contactTaskTag/contactTaskTagActivityDiagram.png
new file mode 100644
index 00000000000..11e9a2be4e7
Binary files /dev/null and b/docs/images/contactTaskTag/contactTaskTagActivityDiagram.png differ
diff --git a/docs/images/contactTaskTag/contactTaskTagSequenceDiagram.png b/docs/images/contactTaskTag/contactTaskTagSequenceDiagram.png
new file mode 100644
index 00000000000..0b207b24d26
Binary files /dev/null and b/docs/images/contactTaskTag/contactTaskTagSequenceDiagram.png differ
diff --git a/docs/images/dueSoonTasks.png b/docs/images/dueSoonTasks.png
new file mode 100644
index 00000000000..c6dd5ccbaca
Binary files /dev/null and b/docs/images/dueSoonTasks.png differ
diff --git a/docs/images/editTask/CommandClassDiagram.png b/docs/images/editTask/CommandClassDiagram.png
new file mode 100644
index 00000000000..43b5c36dc99
Binary files /dev/null and b/docs/images/editTask/CommandClassDiagram.png differ
diff --git a/docs/images/editTask/EditTaskActivityDiagram.png b/docs/images/editTask/EditTaskActivityDiagram.png
new file mode 100644
index 00000000000..2bb6bab93cb
Binary files /dev/null and b/docs/images/editTask/EditTaskActivityDiagram.png differ
diff --git a/docs/images/editTask/EditTaskParserClassDiagram.png b/docs/images/editTask/EditTaskParserClassDiagram.png
new file mode 100644
index 00000000000..93ff070b14d
Binary files /dev/null and b/docs/images/editTask/EditTaskParserClassDiagram.png differ
diff --git a/docs/images/editTask/EditTaskSequence.png b/docs/images/editTask/EditTaskSequence.png
new file mode 100644
index 00000000000..58517854455
Binary files /dev/null and b/docs/images/editTask/EditTaskSequence.png differ
diff --git a/docs/images/editTask/EditTaskSequenceDiagram.png b/docs/images/editTask/EditTaskSequenceDiagram.png
new file mode 100644
index 00000000000..d3e52870e41
Binary files /dev/null and b/docs/images/editTask/EditTaskSequenceDiagram.png differ
diff --git a/docs/images/filterFunction/CommandClassDiagram.png b/docs/images/filterFunction/CommandClassDiagram.png
new file mode 100644
index 00000000000..4ab05704adb
Binary files /dev/null and b/docs/images/filterFunction/CommandClassDiagram.png differ
diff --git a/docs/images/filterFunction/FilterActivityDiagram.png b/docs/images/filterFunction/FilterActivityDiagram.png
new file mode 100644
index 00000000000..dc3954e490a
Binary files /dev/null and b/docs/images/filterFunction/FilterActivityDiagram.png differ
diff --git a/docs/images/filterFunction/FilterSequenceDiagram.png b/docs/images/filterFunction/FilterSequenceDiagram.png
new file mode 100644
index 00000000000..a7548e0f95c
Binary files /dev/null and b/docs/images/filterFunction/FilterSequenceDiagram.png differ
diff --git a/docs/images/filterFunction/ParserClassDiagram.png b/docs/images/filterFunction/ParserClassDiagram.png
new file mode 100644
index 00000000000..2790ddc500b
Binary files /dev/null and b/docs/images/filterFunction/ParserClassDiagram.png differ
diff --git a/docs/images/filterFunction/PredicateClassDiagram.png b/docs/images/filterFunction/PredicateClassDiagram.png
new file mode 100644
index 00000000000..a6fd61484fe
Binary files /dev/null and b/docs/images/filterFunction/PredicateClassDiagram.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
index 235da1c273e..6e2f34296a1 100644
Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ
diff --git a/docs/images/findFunction/ContactMatchesFindKeywordPredicate.png b/docs/images/findFunction/ContactMatchesFindKeywordPredicate.png
new file mode 100644
index 00000000000..bb38bccaae9
Binary files /dev/null and b/docs/images/findFunction/ContactMatchesFindKeywordPredicate.png differ
diff --git a/docs/images/findFunction/FindCommandActivityDiagram.png b/docs/images/findFunction/FindCommandActivityDiagram.png
new file mode 100644
index 00000000000..3bc4c594b0a
Binary files /dev/null and b/docs/images/findFunction/FindCommandActivityDiagram.png differ
diff --git a/docs/images/findFunction/FindCommandClassDiagram.png b/docs/images/findFunction/FindCommandClassDiagram.png
new file mode 100644
index 00000000000..30e423071de
Binary files /dev/null and b/docs/images/findFunction/FindCommandClassDiagram.png differ
diff --git a/docs/images/findFunction/FindCommandParserClassDiagram.png b/docs/images/findFunction/FindCommandParserClassDiagram.png
new file mode 100644
index 00000000000..83ca6628fc8
Binary files /dev/null and b/docs/images/findFunction/FindCommandParserClassDiagram.png differ
diff --git a/docs/images/findFunction/FindCommandSequenceDiagram.png b/docs/images/findFunction/FindCommandSequenceDiagram.png
new file mode 100644
index 00000000000..3054eb9887f
Binary files /dev/null and b/docs/images/findFunction/FindCommandSequenceDiagram.png differ
diff --git a/docs/images/findFunction/SaveLifebook.png b/docs/images/findFunction/SaveLifebook.png
new file mode 100644
index 00000000000..06d5a96be50
Binary files /dev/null and b/docs/images/findFunction/SaveLifebook.png differ
diff --git a/docs/images/findFunction/TaskMatchesFindKeywordPredicate.png b/docs/images/findFunction/TaskMatchesFindKeywordPredicate.png
new file mode 100644
index 00000000000..8bbaa9d1881
Binary files /dev/null and b/docs/images/findFunction/TaskMatchesFindKeywordPredicate.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
index b1f70470137..9043f6e356f 100644
Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ
diff --git a/docs/images/kevnw.png b/docs/images/kevnw.png
new file mode 100644
index 00000000000..4c364e5e944
Binary files /dev/null and b/docs/images/kevnw.png differ
diff --git a/docs/images/lerxcl.png b/docs/images/lerxcl.png
new file mode 100644
index 00000000000..25dd4bfe727
Binary files /dev/null and b/docs/images/lerxcl.png differ
diff --git a/docs/images/lifebook-Logo.png b/docs/images/lifebook-Logo.png
new file mode 100644
index 00000000000..e3375849816
Binary files /dev/null and b/docs/images/lifebook-Logo.png differ
diff --git a/docs/images/linkFunction/CommandClassDiagram.png b/docs/images/linkFunction/CommandClassDiagram.png
new file mode 100644
index 00000000000..dab4f9d2d06
Binary files /dev/null and b/docs/images/linkFunction/CommandClassDiagram.png differ
diff --git a/docs/images/linkFunction/LinkActivityDiagram.png b/docs/images/linkFunction/LinkActivityDiagram.png
new file mode 100644
index 00000000000..f91a2dcb55e
Binary files /dev/null and b/docs/images/linkFunction/LinkActivityDiagram.png differ
diff --git a/docs/images/linkFunction/LinkSequenceDiagram.png b/docs/images/linkFunction/LinkSequenceDiagram.png
new file mode 100644
index 00000000000..0b5b2d2918b
Binary files /dev/null and b/docs/images/linkFunction/LinkSequenceDiagram.png differ
diff --git a/docs/images/linkFunction/ParserClassDiagram.png b/docs/images/linkFunction/ParserClassDiagram.png
new file mode 100644
index 00000000000..be857c8ed79
Binary files /dev/null and b/docs/images/linkFunction/ParserClassDiagram.png differ
diff --git a/docs/images/luciatirta.png b/docs/images/luciatirta.png
new file mode 100644
index 00000000000..370ac4b5520
Binary files /dev/null and b/docs/images/luciatirta.png differ
diff --git a/docs/images/storage/ReadFileActivityDiagram.png b/docs/images/storage/ReadFileActivityDiagram.png
new file mode 100644
index 00000000000..fb12cbc89fd
Binary files /dev/null and b/docs/images/storage/ReadFileActivityDiagram.png differ
diff --git a/docs/images/storage/ReadTaskList.png b/docs/images/storage/ReadTaskList.png
new file mode 100644
index 00000000000..43abef34565
Binary files /dev/null and b/docs/images/storage/ReadTaskList.png differ
diff --git a/docs/images/storage/SaveTaskList.png b/docs/images/storage/SaveTaskList.png
new file mode 100644
index 00000000000..dd079444575
Binary files /dev/null and b/docs/images/storage/SaveTaskList.png differ
diff --git a/docs/images/storage/StorageClassDiagram.png b/docs/images/storage/StorageClassDiagram.png
new file mode 100644
index 00000000000..78d5f18ffeb
Binary files /dev/null and b/docs/images/storage/StorageClassDiagram.png differ
diff --git a/docs/images/storage/TaskListStorageClassDiagram.png b/docs/images/storage/TaskListStorageClassDiagram.png
new file mode 100644
index 00000000000..51c9b622702
Binary files /dev/null and b/docs/images/storage/TaskListStorageClassDiagram.png differ
diff --git a/docs/images/urieltan.png b/docs/images/urieltan.png
new file mode 100644
index 00000000000..27b3c85507d
Binary files /dev/null and b/docs/images/urieltan.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..b51e6c46ddb 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,26 @@
----
-layout: page
-title: AddressBook Level-3
----
+## Lifebook
+[](https://github.com/AY2021S1-CS2103T-F12-4/tp/actions)
+[](https://codecov.io/gh/AY2021S1-CS2103T-F12-4/tp)
+
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+**Introduction**
-
+* Lifebook is a project developed for an introductory Software Engineering (SE) module (CS2103T) at the National University of Singapore.
-**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).
+ * Lifebook is a desktop application intended for University students to manage contact details, assignments, projects, module details, etc.
-* 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.
+ * 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 is morphed from AddressBook Level 3 (AB3).
+
+ * It comes with a reasonable level of user and developer documentation.
+
+* For the detailed user documentation of this project, see [here](https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html).
+
+* For contributing to the ongoing development of the Lifebook, do check out the [Developer Guide](https://ay2021s1-cs2103t-f12-4.github.io/tp/DeveloperGuide.html).
+
+This project is developed from an se-education.org initiative. If you would like to contribute code to the initiative, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+Libraries used: JavaFX, Jackson, JUnit5
diff --git a/docs/team/caleblyx.md b/docs/team/caleblyx.md
new file mode 100644
index 00000000000..4eb2178eb6d
--- /dev/null
+++ b/docs/team/caleblyx.md
@@ -0,0 +1,70 @@
+---
+layout: page
+title: Lin Yuan Xun, Caleb's Project Portfolio Page
+---
+
+## Project: Lifebook
+
+Lifebook is a desktop task management application created during a collaborative project for a module that teaches 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 13 kLoC.
+
+Given below are my contributions to the project.
+
+* **New feature: TaskList and tasks**: Added the ability to create `Tasks`, and add or delete them to or from the `TaskList` respectively.
+ * What it does: allows the user to create `Tasks` that may either be `Todos` or `Events` and keep a record of them in the `TaskList`.
+ * Justification: This feature improves the product significantly because a user can now record information about tasks in Lifebook for future reference and planning.
+ * Highlights: This required an implementation of the TaskList and Tasks (along with its subclasses). This a basis for more commands and features to be added in the future.
+ * Credits: inspired from the creation of Bob - the personal assistant, which was my individual project.
+
+* **New feature List out Tasks, Events or Todos**: Implemented the ability for users to list out all `Tasks`, `Events`, or `Todos`.
+ * What it does: allows the user to list out `Tasks`, `Events`, or `Todos`.
+ * Justification: allows the user to view all `Tasks`, or if needed, a filtered list of all `Events` or `Todos`.
+ * Credits: inspired from the creation of Bob - the personal assistant, which was my individual project.
+
+* **New feature: Mark tasks as done**: Implemented the ability for users to mark tasks as done.
+ * What it does: allows the user to mark completed tasks as done.
+ * Justification: allows users to have a record of the tasks that have been completed.
+ * Credits: inspired from the creation of Bob - the personal assistant, which was my individual project.
+
+* **New feature: Sort TaskList and AddressBook** Implemented the ability for users to sort the `TaskList` and `AddressBook`.
+ * What it does: allows users to sort the `TaskList` and `AddressBook` by name and date respectively. Users also have the option of restoring both lists to their natural order if needed.
+ * Justification: provides users with intuitively sorted lists. Users normally prefer to look through contact details in alphabetical order, and task details in the order of imminence.
+ * Highlights: To implement sorting easily, the `TaskList` and `AddressBook` had to be wrapped by the JavaFX sorted list class, since the JavaFX filtered list class does not support sorting. Also, thanks to bug reporting by peers, I was able to make further refinements to the sorting command in cases whereby the displayed filtered list is empty, or whereby the unfiltered list had no added items.
+
+* **New feature: TaskList GUI**: Created a simple GUI for the `TaskList` and `Tasks` in the first iteration, which was further enhanced and modified in proceeding iterations by other members.
+ * What it does: allows users to view the `TaskList` and its contents.
+ * Justification: provides a means for users to easily access the contents of the `TaskList` via a graphical representation.
+ * Highlights: made it easier for the developing team to visualise any features that they may have implemented in regards to the `TaskList` and/or `Tasks`. Implementing this was initially challenging due to my lack of experience with JavaFX. It required me to scrap my initial implementation of the TaskList to create a new one that could provide an observable list to the GUI.
+ * Credits: this GUI implementation for the `TaskList` was inspired by the GUI of contact list of AB3.
+
+* **New feature: Storage for TaskList**: Implemented storage for the `TaskList`
+ * What it does: allows users to store `TaskList` data.
+ * Justification: provides a means for users to easily store and access the contents of the `TaskList` for future sessions.
+ * Highlights: enables greater ease of implementing storage for data of other features pertaining to `Tasks` in the future. I had encountered some challenges due to my lack of familiarity with JSON. For instance, I was not aware I had to make additional annotations to serialize/deserialize polymorphic objects with JSON.
+ * Credits: implementation inspired by the existing `Storage` component of address book. I referred to a [Stack Overflow discussion forum](https://stackoverflow.com/questions/30362446/deserialize-json-with-jackson-into-polymorphic-types-a-complete-example-is-giv) to gain insight on how to resolve my issue regarding JSON adapted polymorphic objects.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=F12-4&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=functional-code~docs~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=caleblyx&tabRepo=AY2021S1-CS2103T-F12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code~docs~test-code)
+
+* **Project management**:
+ * Created release `v1.3` on [GitHub](https://github.com/AY2021S1-CS2103T-F12-4/tp/releases/tag/v1.3)
+
+* **Enhancements to existing features**:
+ * Reduced coupling of `Model` and `Logic` in the implementation of the Recurrence feature. [\#148](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/148)
+ * Wrote unit tests for all added storage components that support storage of the contents of `TaskList`. [\#134](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/134)
+ * Wrote unit tests for the logical components of the Sorting feature, and updated various test files to fix bugs after correcting the `equals` method of `ModelManager`.[\#248](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/248)
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features:
+ * `add todo`, `remove todo`, `mark todo as done` and `list todo`. These features were updated in future iterations. [\#20](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/20)
+ * `sort contact`, `sort task`, and `sort clear` [\#110](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/110) [\#145](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/145)
+ * Developer Guide:
+ * Updated the model architecture diagram to include the newly added model components of Lifebook. [\#234](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/234)
+ * Added implementation details of the `Add Task` feature. [\#120](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/120)
+ * Added user stories, use cases, and manual testing for the features I implemented [\#37](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/37/files), [\#234](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/234), [\#259](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/259)
+ * Contributed to the "Effort" section of the appendix [\#276](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/276)
+* **Community**:
+ * Reviewed and merged multiple PRs. Examples: [\#149](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/149), [\#216](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/216), [\#221](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/221).
+ * Resolve failing CI with PR: [\#150](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/150)
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/Caleblyx/ped/issues/6), [2](https://github.com/Caleblyx/ped/issues/4), [3](https://github.com/Caleblyx/ped/issues/3))
+
+* **Other contributions**:
+ * Contributed to the creation of Lifebook's demonstration video (script and voiceover).
diff --git a/docs/team/kevnw.md b/docs/team/kevnw.md
new file mode 100644
index 00000000000..96daf803536
--- /dev/null
+++ b/docs/team/kevnw.md
@@ -0,0 +1,85 @@
+---
+layout: page
+title: Kevin William's Project Portfolio Page
+---
+
+## Project: Lifebook
+
+Lifebook is a desktop task management 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.
+
+* **Link Feature**: Added the ability to add a link to a task. (Pull requests [\#86](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/86), [\#100](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/100))
+ * What it does: allows the user to add a meeting link or collaborative link to a task.
+ * Justification: This feature improves the product significantly because finding Zoom links or Google Drive links can be tedious for the users and the app should provide a convenient way for the users to find the links easily.
+ * Highlights: This enhancement affects the model of the task. The implementation was quite challenging as UI matters must also be taken into consideration.
+
+* **Edit Task Feature**: Added the ability to edit the properties of a task. (Pull request [\#135](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/135))
+ * What it does: allows the user to edit the properties of a todo or an event.
+ * Justification: This feature improves the product significantly because a user can make mistake when typing the properties of a task and the app should provide a convenient way for the users to edit them.
+ * Highlights: This enhancement does not affect other commands because it is independent from other commands.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=kevnw&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Setting up the GitHub team organization
+ * Created the skeleton and morph the commands to suite Lifebook (Pull request [\#45](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/45))
+ * Maintaining the issue tracker for every Milestone from `v.1` - `v1.4`
+
+* **Enhancements to existing features**:
+ * Updated exception handling for list, add, and delete command to be more meaningful (Pull request [\#79](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/79))
+ * Added extra validation and meaningful exception handling for link (Pull request [\#104](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/104))
+ * Updated the GUI for Meeting Link and Collaborative Link (Pull requests [\#86](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/86/))
+ * Wrote additional tests for existing features to increase coverage from 58% to 67% (Pull requests [\#213](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/213), [\#214](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/214), [\#208](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/208), [\#149](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/149))
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `add event`, `add todo`, `link`, and many more. [\#57](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/57/)
+ * Did cosmetic tweaks to existing documentation of features `edit`, `delete`: [\#137](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/137/)
+ * Added CodeCoverage widget to the User Guide. [\#151](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/151)
+ * Major User Guide fix after the PE-Dry Run. [\#193](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/193)
+ * Developer Guide:
+ * Added user stories and use case for the `link` feature. [\#42](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/42/)
+ * Added implementation details of the `link` feature. [\#116](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/116/)
+ * Added UML diagrams for `link` feature. [\#116](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/116/)
+ * Added user stories and use case for `edit` feature. [\#251](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/251/)
+ * Added UML diagrams for `edit` feature. [\#251](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/251/)
+
+* **Contributions to the Developer Guide**:
+
+ * LinkCommandParser Class Diagram
+
+ 
+
+ * LinkCommand Class Diagram
+
+ 
+
+ * Sequence Diagram of Link Feature
+
+ 
+
+ * Activity Diagram of Link Feature
+
+ 
+
+* **Contributions to the User Guide**:
+
+ * Table of Contents numbering
+
+ 
+
+ * Link Command
+
+ 
+
+ * And others
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#98](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/98), [\#69](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/69), [\#134](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/134), [\#132](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/132)
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2021S1-CS2103T-T15-3/tp/issues/231), [2](https://github.com/AY2021S1-CS2103T-T15-3/tp/issues/233), [3](https://github.com/AY2021S1-CS2103T-T15-3/tp/issues/235))
+
+* **Tools**:
+ * Integrated a new Github pulgin (GuardRails) to the team repo
+ * Integrated a new Github plugin (CommitCheck) to the team repo
+
diff --git a/docs/team/lerxcl.md b/docs/team/lerxcl.md
new file mode 100644
index 00000000000..5a74284fac0
--- /dev/null
+++ b/docs/team/lerxcl.md
@@ -0,0 +1,82 @@
+---
+layout: page
+title: Chua Chen Ler's Project Portfolio Page
+---
+
+## Project: Lifebook
+
+Lifebook is a desktop task management 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 13 kLoC.
+
+Given below are my contributions to the project.
+
+* **Added the ability to filter tasks due at/before a specified date/time**
+ * What it does: It allows users to search todos/meetings due at/before a certain date/time.
+ * Justification: This feature improves the product functionality because a user may have a long task-list and it could be hard to search for specific deadlines.
+ * Highlights: The implementation of this feature requires accessing the task-list and each task's deadline.
+
+* **Added ability to view tasks that are due soon (within 1 week)**
+ * What it does: At the bottom right-hand corner of Lifebook, a list of tasks is shown that are due 1 week from the current date/time.
+ * Justification: This feature improves the product usefulness as the user will be more inclined to use Lifebook for the ease of looking at tasks that are due soon.
+ * Highlights: It is a little challenging to incorporate the "Due by" panel into Lifebook, and I have to ensure that the "Due soon" panel is updated dynamically each time the user does a task operation.
+
+* **Add a common tag to both a contact and a task**
+ * What it does: With a single command, you can add the same tag(s) to a particular contact and task.
+ * Justification: This feature helps to speed up the process of adding a tag to a contact and a task if the user decides to have a commonn tag.
+ * Highlights: Instead of writing all new code, I have used the "tagging" feature for a contact and task (individually) to reduce chance of bugs and overlaps.
+
+* **Added ability to add a recurring task**
+ * What it does: A recurring task is created when a user includes the optional `recurring/` field in adding a new todo/event.
+ When a recurring task is marked as done, it will automatically generate another recurring task with the new deadline based on the recurrence field. (while the remaining details of the task remains the same)
+
+ * Justification: This feature improves the product functionality as the user would not have to manually add recurring tasks every time.
+ * Highlights: I have to ensure that the recurring task is generated properly after it is marked as done. We have considered whether should the "done" recurrence task be deleted automatically too, but we decide to leave it in case the user wants to trace back their done tasks.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=f12&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=lerxcl&tabRepo=AY2021S1-CS2103T-F12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Created release `v1.3(trial)` on [GitHub](https://github.com/AY2021S1-CS2103T-F12-4/tp/releases/tag/v1.3a)
+ * Created release `v1.4` on [GitHub](https://github.com/AY2021S1-CS2103T-F12-4/tp/releases/tag/v1.4)
+
+* **Enhancements to existing features**:
+ * Wrote tests for the basic task operations to increase coverage from 49.89% to 56.75% (Pull requests [\#69](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/69))
+ * Wrote tests for:
+ - Filter function [\#71](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/71)
+ - Recurring function [\#98](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/98)
+ - Common tag for contact and task [\#132](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/132)
+ - Due soon [\#257](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/257)
+
+ * Modify GUI to show "Recurring task" [\#125](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/125)
+ * Modify GUI to show "Due soon" panel [\#126](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/126)
+
+ * Modify delete task command [\#129](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/129)
+ * Improve validation of date and time input when adding a task [\#194](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/194)
+
+* **Documentation**:
+ * User Guide:
+ * Edited initial documentation to match Lifebook description. [\#13](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/13)
+ * Fix commands syntax (Pull requests [\#78](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/78), [\#88](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/88))
+ * Added documentation for the features:
+ - Filter function [\#16](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/16)
+ - Recurring function (Pull requests [\#98](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/98), [\#125](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/125))
+ - Common tag for contact and task [\#132](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/132)
+ * Did cosmetic tweaks to command summary : [\#138](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/138)
+ * Update release link for v1.3a [\#121](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/121)
+ * Update release link for v1.4 [\#274](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/274)
+ * Added more notices and warnings (Pull requests [\#243](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/243), [\#271](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/271), [\#266](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/266))
+ * Resize some images [\#273](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/273)
+ * Developer Guide:
+ * Added MSS + use case for `filter`, `recurring task` and `contactTaskTag` (Pull requests [\#19](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/19), [\#113](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/113), [\#239](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/239))
+ * Added implementation details of the `filter` feature (Pull requests [\#113](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/113), [\#114](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/114), [\#115](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/115))
+ * Added implementation details of the `contactTaskTag` feature [\#239](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/239)
+ * Added manual testing instructions for `adding task`, `filter`, `due soon` and `contactTaskTag` (Pull requests [\#239](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/239), [\#271](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/271))
+ * Standardize UML diagram colors [\#254](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/242)
+ * Added brief "Effort" section [\#271](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/271)
+
+* **Community**:
+ * PRs reviewed with non-trivial comments: (Pull requests [\#270](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/270), [\#240](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/240), [\#232](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/232))
+ * Contributed to forum discussions:
+ - Sharing tips: [Testing your application using Windows Sandbox](https://github.com/nus-cs2103-AY2021S1/forum/issues/227), [iP .jar tip](https://github.com/nus-cs2103-AY2021S1/forum/issues/174)
+ - Asking for help: [Unable to display ✓ and ✘](https://github.com/nus-cs2103-AY2021S1/forum/issues/64)
+
+* **Other contributions**:
+ * Contributed to the creation of Lifebook's demonstration video. (Script and voice-over)
diff --git a/docs/team/luciatirta.md b/docs/team/luciatirta.md
new file mode 100644
index 00000000000..311b92293a4
--- /dev/null
+++ b/docs/team/luciatirta.md
@@ -0,0 +1,90 @@
+---
+layout: page
+title: Lucia Tirta Gunawan's Project Portfolio Page
+---
+
+## Project: Lifebook
+
+Lifebook is a desktop task management application created as an assignment for a module that teaches 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 13 kLoC.
+
+Given below are my contributions to the project.
+
+* **Tagging of task**: Added tag field to `Tasks`, and include the tags during the creation of `Task`. ([\#107](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/107))
+ * What it does: allows the user to add tags to `Todos` or `Events` during object creation and keep a record of them as the attribute of `Task` in the `TaskList`.
+ * Justification: allows user to label their tasks for better task organisation.
+ * Credits: inspired from the tagging of task in Duke.
+
+* **Find Contacts by Name or Tag, Find Events or Todos by Description or Tag**: Implemented the ability for users to find particular `Contacts`, `Events`, or `Todos` by name/description/tag. ([\#200](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/200), [\#109](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/109), [\#80](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/80), [\#55](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/55)). Find tag was previously show tag, but then later merged with find name feature as suggested in the mock PE issue [\#180](https://github.com/AY2021S1-CS2103T-F12-4/tp/issues/180)
+ * What it does: allows the user to find `Contact` by name or tag, and `Events` or `Todos` by description or tag.
+ * Justification: allows user to easily find a specific contact or task.
+ * Highlights: this requires predicate that can handle filter by name/description only, filter by tag only, and filter by both name/description and tag depending on the user input.
+ * Credits: inspired from find contact by name from AddressBook 3.
+
+* **Overall GUI Layout and Styling**: Change the GUI theme, style the UI components to follow the layout in the previously designed mock UI. ([\#130](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/130), [\#82](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/82), [\#76](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/76), [\#56](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/56))
+ * What it does: Organise contacts and task in the tab panel which can automatically navigate to the corresponding tab on command execution, organise the panels with grid for better UI, make the UI responsive
+ * Justification: allows users to easily access the contact and task by displaying the UI in a nice layout and style.
+ * Highlights: made it easier for the developing team to visualise any features that they may have implemented. Implementing this GUI was challenging at first due to my unfamiliarity with JavaFx. The auto navigation of the tab panel requires additional field in the Command Result to indicate the tab a command corresponds to.
+ * Credits: the initial GUI implementation for the `TaskList` was inspired by the GUI of the AddressBook.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=luciatirta&tabRepo=AY2021S1-CS2103T-F12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code)
+
+* **Project management**:
+ * Authored, tracked and closed issues on Github
+ * Reviewed PRs on GitHub
+
+* **Enhancements to existing features**:
+ * Provide more meaningful error message, add exception handling to edit command and link command. [\#136](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/136)
+ * Add testing to the command parser that increase code coverage from 69% to 71% [\#253](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/253)
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features: `find contact`, `find todo`, `find event`. [\#221](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/221), [\#40](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/40), [\#36](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/36)
+ * Developer Guide:
+ * Added implementation details, use cases, diagram of the `find contact`, `find todo`, `find event` feature. [\#223](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/223), [\#111](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/111), [\#41](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/41)
+ * Updated the architecture diagram to include the updated UI components of Lifebook. [\#244](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/244)
+ * Added manual testing of the find command [\#280](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/280)
+
+* **Community**:
+ * PRs reviewed and merged: [\#239](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/239), [\#234](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/234), [\#207](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/207), [\#78](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/78), [\#54](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/54).
+ * Reported bugs and suggestions for other teams in the class (examples: [\#1](https://github.com/luciatirta/ped/issues/1), [\#2](https://github.com/Caleblyx/ped/issues/2), [\#3](https://github.com/Caleblyx/ped/issues/3))
+
+* **Other contributions**:
+ * Designed the Mock UI using [Figma](https://www.figma.com/file/B1tC0wFEH80wViZf6EQCId/Lifebook?node-id=0%3A1).
+ * Contributed to the creation of Lifebook's demonstration video (script, voiceover, and video editing).
+
+* **Contributions to the Developer Guide**:
+
+ * Ui Class Diagram
+
+ 
+
+ * FindCommandParser Class Diagram
+
+ 
+
+ * Predicate Diagram
+
+ 
+ 
+
+ * FindCommand Class Diagram
+
+ 
+
+ * Sequence Diagram of Find Command Feature
+
+ 
+ 
+
+ * Activity Diagram of Find Command Feature
+
+ 
+
+* **Contributions to the User Guide**:
+
+ * Find Contact Command
+
+ 
+
+ * Find Event and Find Todo Commands have similar usage as the command above.
diff --git a/docs/team/urieltan.md b/docs/team/urieltan.md
new file mode 100644
index 00000000000..cfc85df3065
--- /dev/null
+++ b/docs/team/urieltan.md
@@ -0,0 +1,44 @@
+---
+layout: page
+title: Uriel Tan's Project Portfolio Page
+---
+
+## Project: Lifebook
+
+Lifebook is a desktop task management 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.
+
+* **Link Feature**: Added the ability to add a link to a task. (Pull requests [\#86](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/86), [\#100](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/100))
+ * What it does: allows the user to add a meeting link or collaborative link to a task.
+ * Justification: This feature improves the product significantly because finding Zoom links or Google Drive links can be tedious for the users and the app should provide a convenient way for the users to find the links easily.
+ * Highlights: This enhancement affects the model of the task. The implementation was quite challenging as UI matters must also be taken into consideration.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=kevnw&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Bug fixing**:
+ * Add checks for invalid data during data input [\#268](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/268) [\#270](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/270)
+
+* **Project management**:
+ * Setting up the GitHub team repository and website
+
+* **Enhancements to existing features**:
+ * Added 'all' (refactored to 'task') to list feature (Pull request [\#54](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/54))
+ * Added optional to links in tasks for defensive programming (Pull request [\#105](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/105))
+ * Added tests for link classes in model (Pull request [\#155](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/155))
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `link`. [\#18](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/18)
+ * Developer Guide:
+ * Added user stories and use case for the `link` feature. [\#43](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/43/)
+
+* **Contributions to the Developer Guide**:
+ * Storage component documentation
+
+* **Contributions to the User Guide**:
+ * Link meeting command
+
+* **Community**:
+ * PRs reviewed: [\#17](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/17), [\#104](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/104), [\#224](https://github.com/AY2021S1-CS2103T-F12-4/tp/pull/224)
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2021S1-CS2103T-F13-3/tp/issues/183), [2](https://github.com/AY2021S1-CS2103T-F13-3/tp/issues/182), [3](https://github.com/AY2021S1-CS2103T-F13-3/tp/issues/184))
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index e5cfb161b73..bdd7237bac1 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -19,14 +19,18 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.TaskList;
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.JsonTaskListStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
+import seedu.address.storage.TaskListStorage;
import seedu.address.storage.UserPrefsStorage;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -36,7 +40,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 6, 0, true);
+ public static final Version VERSION = new Version(1, 4, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -48,16 +52,16 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing Lifebook ]===========================");
super.init();
-
AppParameters appParameters = AppParameters.parse(getParameters());
config = initConfig(appParameters.getConfigPath());
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ TaskListStorage taskListStorage = new JsonTaskListStorage(userPrefs.getTaskListFilePath());
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, taskListStorage);
initLogging(config);
@@ -75,22 +79,38 @@ public void init() throws Exception {
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional taskListOptional;
+ ReadOnlyAddressBook initialAddressData;
+ ReadOnlyTaskList initialTaskData;
try {
addressBookOptional = storage.readAddressBook();
if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ logger.info("Address Book data file not found. Will be starting with a sample AddressBook");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialAddressData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
} catch (DataConversionException e) {
logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressData = new AddressBook();
} catch (IOException e) {
logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressData = new AddressBook();
+ }
+
+ try {
+ taskListOptional = storage.readTaskList();
+ if (!taskListOptional.isPresent()) {
+ logger.info("Task List data file not found. Will be starting with a sample TaskList");
+ }
+ initialTaskData = taskListOptional.orElseGet(SampleDataUtil::getSampleTaskList);
+ } catch (DataConversionException e) {
+ logger.warning("Data file not in the correct format. Will be starting with an empty TaskList");
+ initialTaskData = new TaskList();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the file. Will be starting with an empty TaskList");
+ initialTaskData = new TaskList();
}
- return new ModelManager(initialData, userPrefs);
+ return new ModelManager(initialAddressData, userPrefs, initialTaskData);
}
private void initLogging(Config config) {
@@ -158,6 +178,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
//Update prefs file in case it was missing to begin with or there are new/unused fields
try {
storage.saveUserPrefs(initializedPrefs);
+ logger.info("UserPrefs successfully saved. ");
} catch (IOException e) {
logger.warning("Failed to save config file : " + StringUtil.getDetails(e));
}
@@ -167,7 +188,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting Lifebook " + MainApp.VERSION);
ui.start(primaryStage);
}
@@ -176,6 +197,7 @@ public void stop() {
logger.info("============================ [ Stopping Address Book ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
+ logger.info("UserPrefs successfully saved. ");
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
}
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..4fa3c9eb427 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -5,9 +5,28 @@
*/
public class Messages {
+ public static final String UNKNOWN_EDIT_COMMAND = "Only edit contact, todo, and event are available";
+ public static final String UNKNOWN_FIND_COMMAND = "Only find contact, todo, and event are available";
+ public static final String UNKNOWN_ADD_COMMAND = "Only add contact, todo, and event are available";
+ public static final String UNKNOWN_SHOW_TAG_COMMAND = "Only add contact, todo, and event are available \n%1$s";
+ public static final String UNKNOWN_CLEAR_COMMAND = "Only clear contact is available";
+ public static final String UNKNOWN_DELETE_COMMAND = "Only delete contact and task are available";
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_DISPLAYED_INDEX = "The person/task index provided is invalid "
+ + "(Cannot be 0, negative number, or greater than person/task list's index!)";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_TODO_DISPLAYED_INDEX = "The todo index provided is invalid";
+ public static final String MESSAGE_INVALID_INDEX_NOT_TODO = "The task at the given index is not a todo";
+ public static final String MESSAGE_INVALID_INDEX_NOT_EVENT = "The task at the given index is not an event";
+ public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid";
+ public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!";
+ public static final String MESSAGE_INVALID_DATE_FORMAT = "Date should be in DD-MM-YYYY "
+ + "and the numbers have to be valid!";
+ public static final String MESSAGE_INVALID_TIME_FORMAT = "Time should be in HHmm and the numbers have to be valid!";
+ public static final String EXTRA_ARGUMENT_MESSAGE = "Extra parameter found! Please remove parameter: %1$s";
+ public static final String EXTRA_SINGULAR_ARGUMENT_MESSAGE =
+ "Extra parameter found! Please have only 1 parameter: %1$s";
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..f88119ee6e1 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -9,6 +9,7 @@
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
/**
* API of the Logic component
@@ -47,4 +48,8 @@ public interface Logic {
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ ObservableList getFilteredTaskList();
+
+ ObservableList getDueSoonTaskList();
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..57fc5960120 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -15,6 +15,7 @@
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
import seedu.address.storage.Storage;
/**
@@ -47,10 +48,10 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
try {
storage.saveAddressBook(model.getAddressBook());
+ storage.saveTaskList(model.getTaskList());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
-
return commandResult;
}
@@ -64,6 +65,16 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return model.getFilteredTaskList();
+ }
+
+ @Override
+ public ObservableList getDueSoonTaskList() {
+ return model.getDueSoonTaskList();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..655dd13ee1d 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,67 +1,10 @@
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 abstract 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));
- }
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add an item to lifebook.\n"
+ + "Parameters: [todo|event|contact]\n"
+ + COMMAND_WORD + " [todo|event|contact] /h to see more.";
}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..7d3cb158aca 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -6,18 +6,24 @@
import seedu.address.model.Model;
/**
- * Clears the address book.
+ * Clears the Lifebook.
*/
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
+ public static final String MESSAGE_SUCCESS = "Lifebook has been cleared!";
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
+ return new CommandResult(MESSAGE_SUCCESS, "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || other instanceof ClearCommand;
}
}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..1d1102759af 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -17,21 +17,25 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ /** The category the command belongs to */
+ private final String category;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, String category) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.category = category;
}
/**
* Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
* and other fields set to their default value.
*/
- public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ public CommandResult(String feedbackToUser, String category) {
+ this(feedbackToUser, false, false, category);
}
public String getFeedbackToUser() {
@@ -46,6 +50,10 @@ public boolean isExit() {
return exit;
}
+ public String getCategory() {
+ return category;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,12 +68,13 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && exit == otherCommandResult.exit
+ && category == otherCommandResult.category;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, exit, category);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ContactTaskTagCommand.java b/src/main/java/seedu/address/logic/commands/ContactTaskTagCommand.java
new file mode 100644
index 00000000000..0cd2bef178a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ContactTaskTagCommand.java
@@ -0,0 +1,280 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.commands.edit.EditContactCommand.MESSAGE_DUPLICATE_PERSON;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+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.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
+
+/**
+ * Adds a same tag to an existing person and task in the Lifebook.
+ */
+public class ContactTaskTagCommand extends Command {
+ public static final String COMMAND_WORD = "contactTaskTag";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a same tag for a specified Person and Task.\n"
+ + "Parameters: " + PREFIX_TAG + "TAG "
+ + PREFIX_CONTACT_INDEX + "CONTACT INDEX "
+ + PREFIX_TASK_INDEX + "TASK INDEX\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_TAG + "CS2103T "
+ + PREFIX_CONTACT_INDEX + "1 "
+ + PREFIX_TASK_INDEX + "2";
+ public static final String MESSAGE_CONTACT_TASK_TAG_SUCCESS = "Created tag for \n Person: %1$s, \n Task: %2$s";
+
+ private final Index contactIndex;
+ private final Index taskIndex;
+ private final EditPersonTags editPersonTags;
+ private final EditTaskTags editTaskTags;
+
+ /**
+ * Creates a ContactTaskTagCommand to modify a Person and a Task.
+ * @param contactIndex index of the Person.
+ * @param taskIndex index of the Task.
+ * @param editPersonTags the tag that is to be added to the person.
+ * @param editTaskTags the tag that is to be added to the task.
+ */
+ public ContactTaskTagCommand(Index contactIndex, Index taskIndex,
+ EditPersonTags editPersonTags, EditTaskTags editTaskTags) {
+ requireNonNull(contactIndex);
+ requireNonNull(taskIndex);
+ this.contactIndex = contactIndex;
+ this.taskIndex = taskIndex;
+ this.editPersonTags = new EditPersonTags(editPersonTags);
+ this.editTaskTags = new EditTaskTags(editTaskTags);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List contactList = model.getFilteredPersonList();
+ List taskList = model.getFilteredTaskList();
+ boolean isContactIndexValid = contactIndex.getZeroBased() >= contactList.size();
+ boolean isCheckTaskIndexValid = taskIndex.getZeroBased() >= taskList.size();
+
+ if (isContactIndexValid && isCheckTaskIndexValid) {
+ throw new CommandException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX);
+ }
+ if (isContactIndexValid) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+ if (isCheckTaskIndexValid) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = contactList.get(contactIndex.getZeroBased());
+ Task taskToEdit = taskList.get(taskIndex.getZeroBased());
+
+ Person editedPerson = createEditedPerson(personToEdit, editPersonTags);
+ Task editedTask = createEditedTask(taskToEdit, editTaskTags);
+
+ if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.setTask(taskToEdit, editedTask);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+
+ return new CommandResult(String.format(MESSAGE_CONTACT_TASK_TAG_SUCCESS, editedPerson, editedTask), "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ContactTaskTagCommand)) {
+ return false;
+ }
+
+ // state check
+ ContactTaskTagCommand e = (ContactTaskTagCommand) other;
+ return contactIndex.equals(e.contactIndex)
+ && editPersonTags.equals(e.editPersonTags)
+ && taskIndex.equals(e.taskIndex)
+ && editTaskTags.equals(e.editTaskTags);
+ }
+
+ /**
+ * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * edited with {@code editPersonTags}.
+ */
+ private static Person createEditedPerson(Person personToEdit, EditPersonTags editPersonTags) {
+ assert personToEdit != null;
+
+ Set updatedTags = mergeSet(editPersonTags.getTags()
+ .orElse(personToEdit.getTags()), personToEdit.getTags());
+
+ return new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), updatedTags);
+ }
+
+ /**
+ * Creates and returns a {@code Task} with the details of {@code taskToEdit}
+ * edited with {@code editTaskTags}.
+ */
+ private static Task createEditedTask(Task taskToEdit, EditTaskTags editTaskTags) {
+ assert taskToEdit != null;
+
+ Set updatedTags = mergeSet(editTaskTags.getTags()
+ .orElse(taskToEdit.getTags()), taskToEdit.getTags());
+
+ if (taskToEdit.isTodo()) {
+ return new Todo(taskToEdit.getDescription(), taskToEdit.getDeadline(), taskToEdit.getRecurrence(), (
+ (Todo) taskToEdit).getCollaborativeLink(), updatedTags);
+ } else if (taskToEdit.isEvent()) {
+ return new Event(taskToEdit.getDescription(), taskToEdit.getStart(), taskToEdit.getEnd(),
+ taskToEdit.getRecurrence(), updatedTags);
+ } else {
+ assert false : "task is neither a todo or event!";
+ return null;
+ }
+ }
+
+ /**
+ * 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 EditPersonTags {
+ private Set tags;
+
+ public EditPersonTags() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditPersonTags(EditPersonTags toCopy) {
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * 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 a tag set.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(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 EditPersonTags)) {
+ return false;
+ }
+
+ // state check
+ EditPersonTags e = (EditPersonTags) other;
+
+ return getTags().equals(e.getTags());
+ }
+ }
+
+
+ /**
+ * Stores the details to edit the task with. Each non-empty field value will replace the
+ * corresponding field value of the task.
+ */
+ public static class EditTaskTags {
+ private Set tags;
+
+ public EditTaskTags() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditTaskTags(EditTaskTags toCopy) {
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * 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 a tag set.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(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 EditTaskTags)) {
+ return false;
+ }
+
+ // state check
+ EditTaskTags e = (EditTaskTags) other;
+
+ return getTags().equals(e.getTags());
+ }
+ }
+
+ /**
+ * Merges two sets of tags.
+ * @param currentTags current tags of a Person/Task.
+ * @param newTags new tags to be added to Person and Task.
+ * @return a merged set.
+ */
+ public static Set mergeSet(Set currentTags, Set newTags) {
+ if (currentTags.equals(newTags)) {
+ return currentTags;
+ } else {
+ return new HashSet() {
+ {
+ addAll(currentTags);
+ addAll(newTags);
+ }
+ };
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..f3d02ed27c4 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,53 +1,6 @@
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 abstract 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/DoneCommand.java b/src/main/java/seedu/address/logic/commands/DoneCommand.java
new file mode 100644
index 00000000000..50b3e08c222
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DoneCommand.java
@@ -0,0 +1,92 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+
+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.tag.Tag;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Recurrence;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
+/**
+ * Marks a task identified using it's displayed index from the task list as done.
+ */
+public class DoneCommand extends Command {
+ public static final String COMMAND_WORD = "done";
+ public static final String MESSAGE_MARK_TASK_AS_DONE_SUCCESS = "Task marked as done: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Marks the task identified by the index number used in the displayed task list as done.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ public DoneCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToMark = lastShownList.get(targetIndex.getZeroBased());
+ if (taskToMark.isRecurring()) {
+ Recurrence recurrence = taskToMark.getRecurrence();
+ Set tags = taskToMark.getTags();
+ String description = taskToMark.getDescription();
+ if (taskToMark.isTodo()) {
+ LocalDateTime newDateTime = taskToMark.getLocalDateTime().plus(taskToMark.getRecurrence().getValue(),
+ taskToMark.getRecurrence().getChronoUnit());
+ if (taskToMark.getLink().isEmpty()) {
+ model.addTask(new Todo(description, newDateTime, recurrence, tags));
+ } else {
+ CollaborativeLink collaborativeLink = ((Todo) taskToMark).getCollaborativeLink();
+ model.addTask(new Todo(description, newDateTime, recurrence, collaborativeLink, tags));
+ }
+ } else {
+ LocalDateTime newStartDateTime = taskToMark.getStart()
+ .plus(taskToMark.getRecurrence().getValue(), taskToMark.getRecurrence().getChronoUnit());
+ LocalDateTime newEndDateTime = taskToMark.getEnd()
+ .plus(taskToMark.getRecurrence().getValue(), taskToMark.getRecurrence().getChronoUnit());
+ if (taskToMark.getLink().isEmpty()) {
+ model.addTask(new Event(description, newStartDateTime, newEndDateTime, recurrence, tags));
+ } else {
+ MeetingLink currentMeeting = ((Event) taskToMark).getMeetingLink();
+ LocalDateTime newTiming = currentMeeting.getLocalDateTime()
+ .plus(taskToMark.getRecurrence().getValue(), taskToMark.getRecurrence().getChronoUnit());
+
+ String meetingDescription = currentMeeting.getDescription();
+ int positionOfOldTiming = meetingDescription.indexOf("(on: ");
+ meetingDescription = meetingDescription.substring(0, positionOfOldTiming - 1);
+
+ MeetingLink newMeeting = new MeetingLink(meetingDescription, currentMeeting.getUrl(), newTiming);
+ model.addTask(new Event(description, newStartDateTime, newEndDateTime,
+ recurrence, newMeeting, tags));
+ }
+ }
+ }
+ model.markAsDone(taskToMark);
+ return new CommandResult(String.format(MESSAGE_MARK_TASK_AS_DONE_SUCCESS, taskToMark), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DoneCommand // instanceof handles nulls
+ && targetIndex.equals(((DoneCommand) other).targetIndex));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 7e36114902f..a4e50ae3a85 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,226 +1,10 @@
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 abstract 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());
- }
- }
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edit todos, events or contacts.\n"
+ + "Parameters: [todo|contact|event]"
+ + COMMAND_WORD + " [todo|event|contact] /h to see more.";
}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..11155b133bd 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -13,7 +13,7 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, "EXIT");
}
}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index d6b19b0a0de..7fd4c9c46b6 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -1,42 +1,10 @@
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 abstract 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
- }
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find todos, events or contacts.\n"
+ + "Parameters: [todo|contact|event]"
+ + COMMAND_WORD + " [todo|event|contact] /h to see more.";
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..b71f9ebc5ff 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -16,6 +16,6 @@ public class HelpCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ return new CommandResult(SHOWING_HELP_MESSAGE, true, false, "HELP");
}
}
diff --git a/src/main/java/seedu/address/logic/commands/LinkCommand.java b/src/main/java/seedu/address/logic/commands/LinkCommand.java
new file mode 100644
index 00000000000..03aef822a60
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LinkCommand.java
@@ -0,0 +1,10 @@
+package seedu.address.logic.commands;
+
+public abstract class LinkCommand extends Command {
+
+ public static final String COMMAND_WORD = "link";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add a link to your todo or event\n"
+ + "Parameters: [meeting|doc]\n"
+ + COMMAND_WORD + " [meeting|doc] /h to see more.";
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..f1edaa2ddca 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -1,24 +1,10 @@
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 abstract 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);
- }
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": List all items in events, todos or contacts"
+ + "Parameters: [todo|event|contact|task]\n"
+ + "Example: " + COMMAND_WORD + " todo";
}
diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java
new file mode 100644
index 00000000000..7bb10a283e6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/SortCommand.java
@@ -0,0 +1,9 @@
+package seedu.address.logic.commands;
+
+public abstract class SortCommand extends Command {
+ public static final String COMMAND_WORD = "sort";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sort items of lifebook.\n"
+ + "Parameters: [contact|task|clear]\n"
+ + "Usage: " + COMMAND_WORD + " [contact|task|clear]\n"
+ + "Example: " + COMMAND_WORD + " task";
+}
diff --git a/src/main/java/seedu/address/logic/commands/add/AddContactCommand.java b/src/main/java/seedu/address/logic/commands/add/AddContactCommand.java
new file mode 100644
index 00000000000..198258c7b9e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/add/AddContactCommand.java
@@ -0,0 +1,67 @@
+package seedu.address.logic.commands.add;
+
+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.AddCommand;
+import seedu.address.logic.commands.CommandResult;
+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 AddContactCommand extends AddCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " contact: 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 + " contact "
+ + 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 AddContactCommand(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), "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddContactCommand // instanceof handles nulls
+ && toAdd.equals(((AddContactCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/add/AddEventCommand.java b/src/main/java/seedu/address/logic/commands/add/AddEventCommand.java
new file mode 100644
index 00000000000..62f242a73d9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/add/AddEventCommand.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands.add;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Event;
+
+/**
+ * Adds a event to the address book.
+ */
+public class AddEventCommand extends AddCommand {
+
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " event: Adds a event to the TaskList. "
+ + "Parameters: "
+ + PREFIX_DESCRIPTION + "DESCRIPTION "
+ + PREFIX_STARTDATE + "STARTDATE "
+ + PREFIX_STARTTIME + "STARTTIME "
+ + PREFIX_ENDDATE + "ENDDATE "
+ + PREFIX_ENDTIME + "ENDTIME "
+ + " [" + PREFIX_RECURRING + "RECURRING] "
+ + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD + " event "
+ + PREFIX_DESCRIPTION + "Attend meeting "
+ + PREFIX_STARTDATE + "10-11-2020 "
+ + PREFIX_STARTTIME + "1200 "
+ + PREFIX_ENDDATE + "10-11-2020 "
+ + PREFIX_ENDTIME + "1400 "
+ + PREFIX_TAG + "CS2103T";
+
+ public static final String DATE_TIME_USAGE = "Date and time format should be: startdate/DD-MM-YYYY "
+ + "starttime/HHmm (24-hour) enddate/DD-MM-YYYY endtime/HHmm (24-hour)";
+
+ public static final String MESSAGE_SUCCESS = "New event added: %1$s";
+ public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists in the address book";
+
+ private final Event toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Event}
+ */
+ public AddEventCommand(Event event) {
+ requireNonNull(event);
+ toAdd = event;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (model.hasTask(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+ model.addEvent(toAdd);
+ model.getDueSoonTaskList();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.getDescriptionDateTime()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddEventCommand // instanceof handles nulls
+ && toAdd.equals(((AddEventCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/add/AddTodoCommand.java b/src/main/java/seedu/address/logic/commands/add/AddTodoCommand.java
new file mode 100644
index 00000000000..b25307ed49c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/add/AddTodoCommand.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands.add;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+
+import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Todo;
+
+/**
+ * Adds a todo to the address book.
+ */
+public class AddTodoCommand extends AddCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " todo: Adds a todo to the TodoList. "
+ + "Parameters: "
+ + PREFIX_DESCRIPTION + "DESCRIPTION "
+ + PREFIX_DATE + "DATE "
+ + PREFIX_TIME + "TIME "
+ + "[" + PREFIX_RECURRING + "RECURRING] "
+ + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD + " todo "
+ + PREFIX_DESCRIPTION + "Finish assignment "
+ + PREFIX_DATE + "23-11-2020 "
+ + PREFIX_TIME + "2359 "
+ + PREFIX_TAG + "CS2100";
+
+ public static final String DATE_TIME_USAGE = "Date and time format should be: date/DD-MM-YYYY time/HHmm (24-hour)";
+
+ public static final String MESSAGE_SUCCESS = "New todo added: %1$s";
+ public static final String MESSAGE_DUPLICATE_TODO = "This todo already exists in the TodoList";
+
+ private final Todo toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Todo}
+ */
+ public AddTodoCommand(Todo todo) {
+ requireNonNull(todo);
+ toAdd = todo;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (model.hasTask(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TODO);
+ }
+ model.addTodo(toAdd);
+ model.getDueSoonTaskList();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.getDescriptionDateTime()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddTodoCommand // instanceof handles nulls
+ && toAdd.equals(((AddTodoCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteContactCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteContactCommand.java
new file mode 100644
index 00000000000..4fa86b0ce78
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/delete/DeleteContactCommand.java
@@ -0,0 +1,53 @@
+package seedu.address.logic.commands.delete;
+
+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.CommandResult;
+import seedu.address.logic.commands.DeleteCommand;
+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 DeleteContactCommand extends DeleteCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " contact"
+ + ": 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 + " contact 1";
+
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteContactCommand(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), "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteContactCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteContactCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteTaskCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteTaskCommand.java
new file mode 100644
index 00000000000..a3cbec0f977
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/delete/DeleteTaskCommand.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.commands.delete;
+
+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.CommandResult;
+import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+/**
+ * Deletes a task identified using it's displayed index from the task list.
+ */
+public class DeleteTaskCommand extends DeleteCommand {
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " task"
+ + ": Deletes the task identified by the index number used in the displayed TaskList.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " task 1";
+
+ public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteTaskCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX);
+ }
+
+ Task taskToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteTodo(taskToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteTaskCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/due/DueAtCommand.java b/src/main/java/seedu/address/logic/commands/due/DueAtCommand.java
new file mode 100644
index 00000000000..521174ce4e7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/due/DueAtCommand.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands.due;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.DueAtPredicate;
+
+/**
+ * Finds all tasks (Todos and Events) that are due at a given date and time.
+ */
+public class DueAtCommand extends Command {
+ public static final String COMMAND_WORD = "itemsDueAt";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Shows Todos/Events due at a certain date and time.\n"
+ + "Parameters: "
+ + PREFIX_DATE + "DD-MM-YYYY "
+ + PREFIX_TIME + "HHmm \n"
+ + "EXAMPLE: " + COMMAND_WORD + " "
+ + PREFIX_DATE + "12-12-2020 "
+ + PREFIX_TIME + "2359";
+
+ public static final String MESSAGE_SUCCESS = "Here are the list of Todos/Events due at: %1$s";
+
+ private DueAtPredicate predicate;
+
+ public DueAtCommand(DueAtPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.updateFilteredTaskList(predicate);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, predicate.getDateTime()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DueAtCommand // instanceof handles nulls
+ && predicate.equals(((DueAtCommand) other).predicate));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/due/DueBeforeCommand.java b/src/main/java/seedu/address/logic/commands/due/DueBeforeCommand.java
new file mode 100644
index 00000000000..a9b8c6e5680
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/due/DueBeforeCommand.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands.due;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.DueBeforePredicate;
+
+/**
+ * Finds all tasks (Todos and Events) that are due before a given date and time.
+ */
+public class DueBeforeCommand extends Command {
+ public static final String COMMAND_WORD = "itemsDueBefore";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Shows Todos/Events due before a certain date and time.\n"
+ + "Parameters: "
+ + PREFIX_DATE + "DD-MM-YYYY "
+ + PREFIX_TIME + "HHmm \n"
+ + "EXAMPLE: " + COMMAND_WORD + " "
+ + PREFIX_DATE + "12-12-2020 "
+ + PREFIX_TIME + "2359";
+
+ public static final String MESSAGE_SUCCESS = "Here are the list of Todos/Events due before: %1$s";
+
+ private DueBeforePredicate predicate;
+
+ public DueBeforeCommand(DueBeforePredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.updateFilteredTaskList(predicate);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, predicate.getDateTime()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DueBeforeCommand // instanceof handles nulls
+ && predicate.equals(((DueBeforeCommand) other).predicate));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditContactCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditContactCommand.java
new file mode 100644
index 00000000000..dbdc39209cd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditContactCommand.java
@@ -0,0 +1,229 @@
+package seedu.address.logic.commands.edit;
+
+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_INDEX;
+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.CommandResult;
+import seedu.address.logic.commands.EditCommand;
+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 Lifebook.
+ */
+public class EditContactCommand extends EditCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " contact: 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: "
+ + PREFIX_INDEX + "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 + " contact "
+ + PREFIX_INDEX + "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 EditContactCommand(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), "CONTACT");
+ }
+
+ /**
+ * 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 EditContactCommand)) {
+ return false;
+ }
+
+ // state check
+ EditContactCommand e = (EditContactCommand) 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/edit/EditEventCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditEventCommand.java
new file mode 100644
index 00000000000..c8c4856345d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditEventCommand.java
@@ -0,0 +1,265 @@
+package seedu.address.logic.commands.edit;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+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.CommandResult;
+import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Task;
+
+/**
+ * Edits the details of an existing event in the Lifebook.
+ */
+public class EditEventCommand extends EditCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " event: Edits the details of the event identified "
+ + "by the index number used in the displayed task list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: "
+ + PREFIX_INDEX + "INDEX (must be a positive integer) "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ + "[" + PREFIX_STARTDATE + "STARTDATE] "
+ + "[" + PREFIX_STARTTIME + "STARTTIME] "
+ + "[" + PREFIX_ENDDATE + "ENDDATE] "
+ + "[" + PREFIX_ENDTIME + "ENDTIME] "
+ + "Example: " + COMMAND_WORD + " event "
+ + PREFIX_INDEX + "1 "
+ + PREFIX_DESCRIPTION + "A new description "
+ + PREFIX_STARTDATE + "20-01-2020 "
+ + PREFIX_STARTTIME + "2350 "
+ + PREFIX_ENDDATE + "23-01-2020 "
+ + PREFIX_ENDTIME + "2359";
+
+ public static final String MESSAGE_EDIT_EVENT_SUCCESS = "Edited Event: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists in the Lifebook.";
+
+ private final Index index;
+ private final EditEventCommand.EditEventDescriptor editEventDescriptor;
+
+ /**
+ * @param index of the event in the filtered task list to edit
+ * @param editEventDescriptor details to edit the event with
+ */
+ public EditEventCommand(Index index, EditEventCommand.EditEventDescriptor editEventDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editEventDescriptor);
+
+ this.index = index;
+ this.editEventDescriptor = new EditEventCommand.EditEventDescriptor(editEventDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ if (lastShownList.get(index.getZeroBased()).getClass() != Event.class) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_EVENT);
+ }
+
+ Event eventToEdit = (Event) lastShownList.get(index.getZeroBased());
+ Event editedEvent = createEditedEvent(eventToEdit, editEventDescriptor);
+
+ if (!eventToEdit.isSameEvent(editedEvent) && model.hasTask(editedEvent)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+
+ model.setTask(eventToEdit, editedEvent);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ return new CommandResult(String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent), "TASK");
+ }
+
+ /**
+ * Creates and returns a {@code Event} with the details of {@code eventToEdit}
+ * edited with {@code editEventDescriptor}.
+ */
+ private static Event createEditedEvent(Event eventToEdit,
+ EditEventCommand.EditEventDescriptor editEventDescriptor) {
+ assert eventToEdit != null;
+
+ String description = editEventDescriptor.getDescription().orElse(eventToEdit.getDescription());
+ String previousStartDateTime = eventToEdit.getStartTime();
+ String startDate = editEventDescriptor.getStartDate().orElse(previousStartDateTime.split(" ")[0]);
+ String startTime = editEventDescriptor.getStartTime().orElse(previousStartDateTime.split(" ")[1]);
+ String previousEndDateTime = eventToEdit.getEndTime();
+ String endDate = editEventDescriptor.getEndDate().orElse(previousEndDateTime.split(" ")[0]);
+ String endTime = editEventDescriptor.getEndTime().orElse(previousEndDateTime.split(" ")[1]);
+ Set updatedTags = editEventDescriptor.getTags().orElse(eventToEdit.getTags());
+
+ if (eventToEdit.getLink().isPresent()) {
+ MeetingLink link = eventToEdit.getMeetingLink();
+ if (eventToEdit.hasRecurrence()) {
+ return new Event(eventToEdit.getStatus(), description, startDate + " " + startTime,
+ endDate + " " + endTime, link, eventToEdit.getRecurrence(), updatedTags);
+ } else {
+ return new Event(eventToEdit.getStatus(), description,
+ startDate + " " + startTime, endDate + " " + endTime, link, updatedTags);
+ }
+ } else {
+ if (eventToEdit.hasRecurrence()) {
+ return new Event(eventToEdit.getStatus(), description, startDate + " " + startTime,
+ endDate + " " + endTime, eventToEdit.getRecurrence(), updatedTags);
+ } else {
+ return new Event(eventToEdit.getStatus(), description,
+ startDate + " " + startTime, endDate + " " + endTime, updatedTags);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditEventCommand)) {
+ return false;
+ }
+
+ // state check
+ EditEventCommand e = (EditEventCommand) other;
+ return index.equals(e.index)
+ && editEventDescriptor.equals(e.editEventDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the event with. Each non-empty field value will replace the
+ * corresponding field value of the event.
+ */
+ public static class EditEventDescriptor {
+ private String description;
+ private String startDate;
+ private String startTime;
+ private String endDate;
+ private String endTime;
+ private Set tags;
+
+ public EditEventDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditEventDescriptor(EditEventCommand.EditEventDescriptor toCopy) {
+ setDescription(toCopy.description);
+ setStartDate(toCopy.startDate);
+ setStartTime(toCopy.startTime);
+ setEndDate(toCopy.endDate);
+ setEndTime(toCopy.endTime);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(description, startDate, startTime, endDate, endTime, tags);
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setStartDate(String startDate) {
+ this.startDate = startDate;
+ }
+
+ public Optional getStartDate() {
+ return Optional.ofNullable(startDate);
+ }
+
+ public void setStartTime(String startTime) {
+ this.startTime = startTime;
+ }
+
+ public Optional getStartTime() {
+ return Optional.ofNullable(startTime);
+ }
+
+ public void setEndDate(String endDate) {
+ this.endDate = endDate;
+ }
+
+ public Optional getEndDate() {
+ return Optional.ofNullable(endDate);
+ }
+
+ public void setEndTime(String endTime) {
+ this.endTime = endTime;
+ }
+
+ /**
+ * 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();
+ }
+
+ public Optional getEndTime() {
+ return Optional.ofNullable(endTime);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditEventCommand.EditEventDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditEventCommand.EditEventDescriptor e = (EditEventCommand.EditEventDescriptor) other;
+
+ return getDescription().equals(e.getDescription())
+ && getStartDate().equals(e.getStartDate())
+ && getStartTime().equals(e.getStartTime())
+ && getEndDate().equals(e.getEndDate())
+ && getEndTime().equals(e.getEndTime());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditTodoCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditTodoCommand.java
new file mode 100644
index 00000000000..c4766d95d1d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditTodoCommand.java
@@ -0,0 +1,234 @@
+package seedu.address.logic.commands.edit;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+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.CommandResult;
+import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
+
+/**
+ * Edits the details of an existing todo in the Lifebook.
+ */
+public class EditTodoCommand extends EditCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " todo: Edits the details of the todo identified "
+ + "by the index number used in the displayed task list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: "
+ + PREFIX_INDEX + "INDEX (must be a positive integer) "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ + "[" + PREFIX_DATE + "DATE] "
+ + "[" + PREFIX_TIME + "TIME] "
+ + "Example: " + COMMAND_WORD + " todo "
+ + PREFIX_INDEX + "1 "
+ + PREFIX_DESCRIPTION + "A new description "
+ + PREFIX_DATE + "20-01-2020 "
+ + PREFIX_TIME + "2350";
+
+ public static final String MESSAGE_EDIT_TODO_SUCCESS = "Edited Todo: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_TODO = "This todo already exists in the Lifebook.";
+
+ private final Index index;
+ private final EditTodoCommand.EditTodoDescriptor editTodoDescriptor;
+
+ /**
+ * @param index of the todo in the filtered task list to edit
+ * @param editTodoDescriptor details to edit the todo with
+ */
+ public EditTodoCommand(Index index, EditTodoCommand.EditTodoDescriptor editTodoDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editTodoDescriptor);
+
+ this.index = index;
+ this.editTodoDescriptor = new EditTodoCommand.EditTodoDescriptor(editTodoDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTaskList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TODO_DISPLAYED_INDEX);
+ }
+
+ if (lastShownList.get(index.getZeroBased()).getClass() != Todo.class) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_TODO);
+ }
+
+ Todo todoToEdit = (Todo) lastShownList.get(index.getZeroBased());
+ Todo editedTodo = createEditedTodo(todoToEdit, editTodoDescriptor);
+
+ if (!todoToEdit.isSameTodo(editedTodo) && model.hasTask(editedTodo)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TODO);
+ }
+
+ model.setTask(todoToEdit, editedTodo);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ return new CommandResult(String.format(MESSAGE_EDIT_TODO_SUCCESS, editedTodo), "TASK");
+ }
+
+ /**
+ * Creates and returns a {@code Todo} with the details of {@code todoToEdit}
+ * edited with {@code editTodoDescriptor}.
+ */
+ private static Todo createEditedTodo(Todo todoToEdit,
+ EditTodoCommand.EditTodoDescriptor editTodoDescriptor) {
+ assert todoToEdit != null;
+
+ String description = editTodoDescriptor.getDescription().orElse(todoToEdit.getDescription());
+ String previousDateTime = todoToEdit.getInputDate();
+ String date = editTodoDescriptor.getDate().orElse(previousDateTime.split(" ")[0]);
+ String time = editTodoDescriptor.getTime().orElse(previousDateTime.split(" ")[1]);
+ Set updatedTags = editTodoDescriptor.getTags().orElse(todoToEdit.getTags());
+
+ if (todoToEdit.getLink().isPresent()) {
+ CollaborativeLink link = todoToEdit.getCollaborativeLink();
+ if (todoToEdit.hasRecurrence()) {
+ return new Todo(todoToEdit.getStatus(), description,
+ date + " " + time, link, todoToEdit.getRecurrence(), updatedTags);
+ } else {
+ return new Todo(todoToEdit.getStatus(), description, date + " " + time, link, updatedTags);
+ }
+ } else {
+ if (todoToEdit.hasRecurrence()) {
+ return new Todo(todoToEdit.getStatus(), description,
+ date + " " + time, todoToEdit.getRecurrence(), updatedTags);
+ } else {
+ return new Todo(todoToEdit.getStatus(), description, date + " " + time, updatedTags);
+ }
+ }
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTodoCommand)) {
+ return false;
+ }
+
+ // state check
+ EditTodoCommand e = (EditTodoCommand) other;
+ return index.equals(e.index)
+ && editTodoDescriptor.equals(e.editTodoDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the todo with. Each non-empty field value will replace the
+ * corresponding field value of the todo.
+ */
+ public static class EditTodoDescriptor {
+ private String description;
+ private String date;
+ private String time;
+ private Set tags;
+
+ public EditTodoDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditTodoDescriptor(EditTodoCommand.EditTodoDescriptor toCopy) {
+ setDescription(toCopy.description);
+ setDate(toCopy.date);
+ setTime(toCopy.time);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ System.out.print(tags == null);
+ return CollectionUtil.isAnyNonNull(description, date, time, tags);
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+
+ public Optional getDate() {
+ return Optional.ofNullable(date);
+ }
+
+ public void setTime(String time) {
+ this.time = time;
+ }
+
+ public Optional getTime() {
+ return Optional.ofNullable(time);
+ }
+
+ /**
+ * 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 EditTodoCommand.EditTodoDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditTodoCommand.EditTodoDescriptor e = (EditTodoCommand.EditTodoDescriptor) other;
+
+ return getDescription().equals(e.getDescription())
+ && getDate().equals(e.getDate())
+ && getTime().equals(e.getTime());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/find/FindContactCommand.java b/src/main/java/seedu/address/logic/commands/find/FindContactCommand.java
new file mode 100644
index 00000000000..d54f21c3731
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/find/FindContactCommand.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.commands.find;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.FindCommand;
+import seedu.address.model.Model;
+import seedu.address.model.person.ContactMatchesFindKeywordPredicate;
+
+/**
+ * Finds and lists all persons in address book whose name contains any of the name keywords
+ * and the tag matches the given tag keyword.
+ * Keyword matching is case insensitive.
+ */
+public class FindContactCommand extends FindCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " contact"
+ + ": Finds all persons whose names contain any of "
+ + "the specified name keywords and the tag matches the given tag keyword (case-insensitive) "
+ + "and displays them as a list with index numbers.\n"
+ + "Parameters: [" + PREFIX_NAME + "NAME_KEYWORD [MORE_NAME_KEYWORDS]...] "
+ + "[" + PREFIX_TAG + "TAG_KEYWORD]\n"
+ + "Remarks: at least one of " + PREFIX_NAME + " or " + PREFIX_TAG + " must be included in the command. "
+ + "Keyword cannot be empty\n"
+ + "Example: " + COMMAND_WORD + " contact " + PREFIX_NAME + "alice bob charlie " + PREFIX_TAG + "friends";
+
+ private final ContactMatchesFindKeywordPredicate predicate;
+
+ public FindContactCommand(ContactMatchesFindKeywordPredicate 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()),
+ "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindContactCommand // instanceof handles nulls
+ && predicate.equals(((FindContactCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/find/FindEventCommand.java b/src/main/java/seedu/address/logic/commands/find/FindEventCommand.java
new file mode 100644
index 00000000000..0e3d05525b6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/find/FindEventCommand.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands.find;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.FindCommand;
+import seedu.address.model.Model;
+import seedu.address.model.task.TaskMatchesFindKeywordPredicate;
+
+/**
+ * Finds and lists all events in lifebook whose description contains any of the desc keywords.
+ * and the tag matches the given tag keyword.
+ * Keyword matching is case insensitive.
+ */
+public class FindEventCommand extends FindCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " event"
+ + ": Finds all events whose descriptions contain any of "
+ + "the specified desc keywords and the tag matches the given tag keyword (case-insensitive) "
+ + "and displays them as a list with index numbers.\n"
+ + "Parameters: [" + PREFIX_DESCRIPTION + "DESC_KEYWORD [MORE_DESC_KEYWORDS]...] "
+ + "[" + PREFIX_TAG + "TAG_KEYWORD]\n"
+ + "Remarks: at least one of " + PREFIX_DESCRIPTION + " or " + PREFIX_TAG + " must be included in the command. "
+ + "Keyword cannot be empty\n"
+ + "Example: " + COMMAND_WORD + " event " + PREFIX_DESCRIPTION + "attend meeting " + PREFIX_TAG + "urgent";
+
+ private final TaskMatchesFindKeywordPredicate predicate;
+
+ public FindEventCommand(TaskMatchesFindKeywordPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTaskList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindEventCommand // instanceof handles nulls
+ && predicate.equals(((FindEventCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/find/FindTodoCommand.java b/src/main/java/seedu/address/logic/commands/find/FindTodoCommand.java
new file mode 100644
index 00000000000..bcc4352262b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/find/FindTodoCommand.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.commands.find;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.FindCommand;
+import seedu.address.model.Model;
+import seedu.address.model.task.TaskMatchesFindKeywordPredicate;
+
+/**
+ * Finds and lists all todos in address book whose description contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindTodoCommand extends FindCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " todo"
+ + ": Finds all todos whose descriptions contain any of "
+ + "the specified desc keywords and the tag matches the given tag keyword (case-insensitive) "
+ + "and displays them as a list with index numbers.\n"
+ + "Parameters: [" + PREFIX_DESCRIPTION + "DESC_KEYWORD [MORE_DESC_KEYWORDS]...] "
+ + "[" + PREFIX_TAG + "TAG_KEYWORD]\n"
+ + "Remarks: at least one of " + PREFIX_DESCRIPTION + " or " + PREFIX_TAG + " must be included in the command. "
+ + "Keyword cannot be empty\n"
+ + "Example: " + COMMAND_WORD + " todo " + PREFIX_DESCRIPTION + "assignment " + PREFIX_TAG + "CS2100";
+
+ private final TaskMatchesFindKeywordPredicate predicate;
+
+ public FindTodoCommand(TaskMatchesFindKeywordPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTaskList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindTodoCommand // instanceof handles nulls
+ && predicate.equals(((FindTodoCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/link/LinkCollaborativeCommand.java b/src/main/java/seedu/address/logic/commands/link/LinkCollaborativeCommand.java
new file mode 100644
index 00000000000..272e7440338
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/link/LinkCollaborativeCommand.java
@@ -0,0 +1,87 @@
+package seedu.address.logic.commands.link;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_URL;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.LinkCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
+
+/**
+ * Add a Collaborative link to a todo.
+ */
+public class LinkCollaborativeCommand extends LinkCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " doc: Add collaborative link to todo. "
+ + "Parameters: "
+ + PREFIX_DESCRIPTION + "DESCRIPTION "
+ + PREFIX_URL + "URL "
+ + PREFIX_INDEX + "INDEX\n"
+ + "Example: " + COMMAND_WORD + " doc "
+ + PREFIX_DESCRIPTION + "proposal "
+ + PREFIX_URL + "https://docs.google.com "
+ + PREFIX_INDEX + "1";
+
+ public static final String MESSAGE_SUCCESS = "New collaborative folder link added: %1$s";
+
+ private final CollaborativeLink collaborativeLink;
+
+ private final Index index;
+
+ /**
+ * Creates an LinkCollaborativeCommand to add collaborative link to a {@code Todo}
+ */
+ public LinkCollaborativeCommand(Index index, CollaborativeLink collaborativeLink) {
+ requireNonNull(collaborativeLink);
+ this.collaborativeLink = collaborativeLink;
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ List lastShownList = model.getFilteredTaskList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TODO_DISPLAYED_INDEX);
+ }
+ try {
+ if (lastShownList.get(index.getZeroBased()).getClass() != Todo.class) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_TODO);
+ }
+
+ Todo todoToEdit = (Todo) lastShownList.get(index.getZeroBased());
+ Todo editedTodo;
+ if (todoToEdit.getRecurrence() != null) {
+ editedTodo = new Todo(todoToEdit.getDescription(), todoToEdit.getDeadline(),
+ todoToEdit.getRecurrence(), collaborativeLink, todoToEdit.getTags());
+ } else {
+ editedTodo = new Todo(todoToEdit.getDescription(), todoToEdit.getDeadline(),
+ collaborativeLink, todoToEdit.getTags());
+ }
+
+ model.setTask(todoToEdit, editedTodo);
+ model.updateFilteredTaskList(Model.PREDICATE_SHOW_ALL_TASKS);
+ } catch (ClassCastException e) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_TODO);
+ }
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, collaborativeLink.getDescription()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof LinkCollaborativeCommand // instanceof handles nulls
+ && collaborativeLink.equals(((LinkCollaborativeCommand) other).collaborativeLink));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/link/LinkMeetingCommand.java b/src/main/java/seedu/address/logic/commands/link/LinkMeetingCommand.java
new file mode 100644
index 00000000000..ec766122180
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/link/LinkMeetingCommand.java
@@ -0,0 +1,92 @@
+package seedu.address.logic.commands.link;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_URL;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.LinkCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Task;
+
+/**
+ * Add a meeting link to an event.
+ */
+public class LinkMeetingCommand extends LinkCommand {
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " meeting: Add meeting link to event. "
+ + "Parameters: "
+ + PREFIX_DESCRIPTION + "DESCRIPTION "
+ + PREFIX_URL + "URL "
+ + PREFIX_INDEX + "INDEX "
+ + PREFIX_DATE + "DATE "
+ + PREFIX_TIME + "TIME\n"
+ + "Example: " + COMMAND_WORD + " meeting "
+ + PREFIX_DESCRIPTION + "workshop "
+ + PREFIX_URL + "https://www.youtube.com "
+ + PREFIX_INDEX + "1 "
+ + PREFIX_DATE + "29-10-2020 "
+ + PREFIX_TIME + "1200";
+
+ public static final String MESSAGE_SUCCESS = "New meeting added: %1$s";
+
+ private final MeetingLink meetingLink;
+
+ private final Index index;
+
+ /**
+ * Creates a LinkMeetingCommand to add the meeting link to an {@code Event}
+ */
+ public LinkMeetingCommand(Index index, MeetingLink meetingLink) {
+ requireNonNull(meetingLink);
+ this.meetingLink = meetingLink;
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ List lastShownList = model.getFilteredTaskList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+ try {
+ if (lastShownList.get(index.getZeroBased()).getClass() != Event.class) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_EVENT);
+ }
+
+ Event eventToEdit = (Event) lastShownList.get(index.getZeroBased());
+ Event editedEvent;
+ if (eventToEdit.getRecurrence() != null) {
+ editedEvent = new Event(eventToEdit.getDescription(), eventToEdit.getStartTime(),
+ eventToEdit.getEndTime(), eventToEdit.getRecurrence(), meetingLink, eventToEdit.getTags());
+ } else {
+ editedEvent = new Event(eventToEdit.getDescription(), eventToEdit.getStartTime(),
+ eventToEdit.getEndTime(), meetingLink, eventToEdit.getTags());
+ }
+ model.setTask(eventToEdit, editedEvent);
+ model.updateFilteredTaskList(Model.PREDICATE_SHOW_ALL_TASKS);
+ } catch (ClassCastException e) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX_NOT_EVENT);
+ }
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, meetingLink.getDescriptionDateTime()), "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof LinkMeetingCommand // instanceof handles nulls
+ && meetingLink.equals(((LinkMeetingCommand) other).meetingLink));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/list/ListContactCommand.java b/src/main/java/seedu/address/logic/commands/list/ListContactCommand.java
new file mode 100644
index 00000000000..29c19a607b0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/list/ListContactCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands.list;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.model.Model;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class ListContactCommand extends ListCommand {
+
+ 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, "CONTACT");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || other instanceof ListContactCommand;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/list/ListEventCommand.java b/src/main/java/seedu/address/logic/commands/list/ListEventCommand.java
new file mode 100644
index 00000000000..aa6010cca23
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/list/ListEventCommand.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.commands.list;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.model.Model;
+import seedu.address.model.task.TaskTypeMatchesKeywordsPredicate;
+
+/**
+ * Lists all events in the task list to the user.
+ */
+public class ListEventCommand extends ListCommand {
+
+ public static final String MESSAGE_SUCCESS = "Listed all events";
+ public static final String EVENT_KEYWORD = "Event";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ List keyword = new ArrayList<>();
+ keyword.add(EVENT_KEYWORD);
+ model.updateFilteredTaskList(new TaskTypeMatchesKeywordsPredicate(keyword));
+ return new CommandResult(MESSAGE_SUCCESS, "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || other instanceof ListEventCommand;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/list/ListTaskCommand.java b/src/main/java/seedu/address/logic/commands/list/ListTaskCommand.java
new file mode 100644
index 00000000000..8eae4eab57a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/list/ListTaskCommand.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.commands.list;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.model.Model;
+
+/**
+ * Lists all tasks in the task list to the user.
+ */
+public class ListTaskCommand extends ListCommand {
+
+ public static final String MESSAGE_SUCCESS = "Listed all tasks";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ return new CommandResult(MESSAGE_SUCCESS, "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || other instanceof ListTaskCommand;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/list/ListTodoCommand.java b/src/main/java/seedu/address/logic/commands/list/ListTodoCommand.java
new file mode 100644
index 00000000000..20b57a77838
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/list/ListTodoCommand.java
@@ -0,0 +1,35 @@
+package seedu.address.logic.commands.list;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.model.Model;
+import seedu.address.model.task.TaskTypeMatchesKeywordsPredicate;
+
+/**
+ * Lists all todos in the task list to the user.
+ */
+public class ListTodoCommand extends ListCommand {
+
+ public static final String MESSAGE_SUCCESS = "Listed all todo";
+ public static final String TODO_KEYWORD = "Todo";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ List keyword = new ArrayList<>();
+ keyword.add(TODO_KEYWORD);
+ model.updateFilteredTaskList(new TaskTypeMatchesKeywordsPredicate(keyword));
+ return new CommandResult(MESSAGE_SUCCESS, "TASK");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || other instanceof ListTodoCommand;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/sort/SortClearCommand.java b/src/main/java/seedu/address/logic/commands/sort/SortClearCommand.java
new file mode 100644
index 00000000000..13e80774931
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/sort/SortClearCommand.java
@@ -0,0 +1,52 @@
+package seedu.address.logic.commands.sort;
+
+import java.util.function.Predicate;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.SortCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.task.Task;
+
+/**
+ * Clears all sorting from both displayed lists.
+ */
+public class SortClearCommand extends SortCommand {
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Clears all sorting ";
+ public static final String MESSAGE_EMPTY_LISTS = "Both lists are currently empty. Please add items to adjust "
+ + "sorting.";
+ public static final String MESSAGE_EMPTY_TASKLIST = "The task list is currently empty. Please add tasks to adjust "
+ + "its sorting. Person list sorting cleared.";
+ public static final String MESSAGE_EMPTY_ADDRESSBOOK = "The person list is currently empty. Please add contacts "
+ + "to adjust its sorting. Task list sorting cleared.";
+ public static final String MESSAGE_SUCCESS = "All sorting cleared";
+ public static final Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ public static final Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true;
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ model.updateSortedPersonList(null);
+ model.updateSortedTaskList(null);
+ if (model.filteredTaskListIsEmpty()) {
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ }
+ if (model.filteredAddressBookIsEmpty()) {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ }
+ if (model.taskListIsEmpty() && model.addressBookIsEmpty()) {
+ return new CommandResult(MESSAGE_EMPTY_LISTS, "CONTACT");
+ } else if (model.taskListIsEmpty() && !model.addressBookIsEmpty()) {
+ return new CommandResult(MESSAGE_EMPTY_TASKLIST, "CONTACT");
+ } else if (!model.taskListIsEmpty() && model.addressBookIsEmpty()) {
+ return new CommandResult(MESSAGE_EMPTY_ADDRESSBOOK, "CONTACT");
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, "CONTACT");
+ }
+ }
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SortClearCommand);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/sort/SortContactCommand.java b/src/main/java/seedu/address/logic/commands/sort/SortContactCommand.java
new file mode 100644
index 00000000000..e0d1bdee430
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/sort/SortContactCommand.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.commands.sort;
+
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.SortCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.PersonNameComparator;
+/**
+ * Sorts contact list in alphabetical order.
+ */
+public class SortContactCommand extends SortCommand {
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts contacts by name ";
+ public static final String MESSAGE_SUCCESS = "Sorted contacts by name";
+ public static final String MESSAGE_EMPTY_FILTERED_PERSON_LIST = "The list is empty. Displaying an unfiltered"
+ + " sorted person"
+ + " list instead.";
+ public static final String MESSAGE_EMPTY_PERSON_LIST = "The person list is empty. Please add persons to sort them.";
+ public static final Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ Comparator comparator = new PersonNameComparator();
+ model.updateSortedPersonList(comparator);
+ if (model.filteredAddressBookIsEmpty()) {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.updateSortedPersonList(comparator);
+ if (model.filteredAddressBookIsEmpty()) {
+ return new CommandResult(MESSAGE_EMPTY_PERSON_LIST, "CONTACT");
+ } else {
+ return new CommandResult(MESSAGE_EMPTY_FILTERED_PERSON_LIST, "CONTACT");
+ }
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, "CONTACT");
+ }
+ }
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SortContactCommand);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/sort/SortTaskCommand.java b/src/main/java/seedu/address/logic/commands/sort/SortTaskCommand.java
new file mode 100644
index 00000000000..d5bc99c927d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/sort/SortTaskCommand.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.commands.sort;
+
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.SortCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.TaskDateComparator;
+/**
+ * Sorts task list according to date and time in ascending order.
+ */
+public class SortTaskCommand extends SortCommand {
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts tasks by date ";
+ public static final String MESSAGE_SUCCESS = "Sorted tasks by date";
+ public static final String MESSAGE_EMPTY_FILTERED_TASK_LIST = "The list is empty. Displaying an unfiltered"
+ + " sorted task"
+ + " list instead.";
+ public static final String MESSAGE_EMPTY_TASK_LIST = "The task list is empty. Please add tasks to sort them.";
+ public static final Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true;
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ Comparator comparator = new TaskDateComparator();
+ model.updateSortedTaskList(comparator);
+ if (model.filteredTaskListIsEmpty()) {
+ model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ model.updateSortedTaskList(comparator);
+ if (model.filteredTaskListIsEmpty()) {
+ return new CommandResult(MESSAGE_EMPTY_TASK_LIST, "TASK");
+ } else {
+ return new CommandResult(MESSAGE_EMPTY_FILTERED_TASK_LIST, "TASK");
+ }
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, "TASK");
+ }
+ }
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SortTaskCommand);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 3b8bfa035e8..05dfd35c817 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -2,15 +2,30 @@
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_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME;
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_RECURRING;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+import static seedu.address.model.task.Recurrence.DAY;
+import static seedu.address.model.task.Recurrence.MONTH;
+import static seedu.address.model.task.Recurrence.WEEK;
+import static seedu.address.model.task.Recurrence.YEAR;
import java.util.Set;
import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.add.AddContactCommand;
+import seedu.address.logic.commands.add.AddEventCommand;
+import seedu.address.logic.commands.add.AddTodoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
@@ -18,6 +33,9 @@
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.Recurrence;
+import seedu.address.model.task.Todo;
/**
* Parses input arguments and creates a new AddCommand object
@@ -30,23 +48,128 @@ public class AddCommandParser implements Parser {
* @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);
+ String[] splitArgs = args.trim().split(" ", 2);
+ if (splitArgs[0].trim().split(" ")[0].trim().equals("contact")) {
+ if (splitArgs.length != 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + splitArgs[1], 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));
- }
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.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 AddContactCommand(person);
+ } else if (splitArgs[0].trim().equals("todo")) {
+ if (splitArgs.length != 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTodoCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION,
+ PREFIX_DATE, PREFIX_TIME, PREFIX_RECURRING, PREFIX_TAG);
+ if (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_DATE, PREFIX_TIME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTodoCommand.MESSAGE_USAGE));
+ }
+ String description = argMultimap.getValue(PREFIX_DESCRIPTION).get().trim();
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ String date = argMultimap.getValue(PREFIX_DATE).get().trim();
+ String time = argMultimap.getValue(PREFIX_TIME).get().trim();
+ ParserUtil.checkDateValidity(date);
+ ParserUtil.checkTimeValidity(time);
+ ParserUtil.validateDescription(description);
+ String deadline = date + " " + time;
- 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));
+ Todo todo;
+ if (arePrefixesPresent(argMultimap, PREFIX_RECURRING)) {
+ String recurrenceInput = argMultimap.getValue(PREFIX_RECURRING).get();
+ try {
+ String[] recurrenceSplit = recurrenceInput.split(" ");
+ Integer recurrenceValue = Integer.parseInt(recurrenceSplit[0]);
+ String recurrenceTimePeriod = recurrenceSplit[1];
+ if (checkChronoUnitValidity(recurrenceTimePeriod)
+ && checkRecurrenceValueValidity(recurrenceValue)) {
+ Recurrence recurrence = new Recurrence(recurrenceValue, recurrenceTimePeriod);
+ todo = new Todo(description, deadline, recurrence, tagList);
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTodoCommand.MESSAGE_USAGE));
+ }
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddTodoCommand.MESSAGE_USAGE));
+ }
+ } else {
+ todo = new Todo(description, deadline, tagList);
+ }
+ return new AddTodoCommand(todo);
+ } else if (splitArgs[0].trim().equals("event")) {
+ if (splitArgs.length != 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEventCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION,
+ PREFIX_STARTDATE, PREFIX_STARTTIME, PREFIX_ENDDATE, PREFIX_ENDTIME, PREFIX_RECURRING, PREFIX_TAG);
- Person person = new Person(name, phone, email, address, tagList);
+ if (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_STARTDATE,
+ PREFIX_STARTTIME, PREFIX_ENDDATE, PREFIX_ENDTIME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEventCommand.MESSAGE_USAGE));
+ }
+ String description = argMultimap.getValue(PREFIX_DESCRIPTION).get().trim();
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ String stDate = argMultimap.getValue(PREFIX_STARTDATE).get().trim();
+ String stTime = argMultimap.getValue(PREFIX_STARTTIME).get().trim();
+ String endDate = argMultimap.getValue(PREFIX_ENDDATE).get().trim();
+ String endTime = argMultimap.getValue(PREFIX_ENDTIME).get().trim();
+ ParserUtil.checkDateValidity(stDate);
+ ParserUtil.checkTimeValidity(stTime);
+ ParserUtil.checkDateValidity(endDate);
+ ParserUtil.checkTimeValidity(endTime);
+ ParserUtil.validateDescription(description);
+ String stDateTime = stDate + " " + stTime;
+ String endDateTime = endDate + " " + endTime;
- return new AddCommand(person);
+ Event event;
+ if (arePrefixesPresent(argMultimap, PREFIX_RECURRING)) {
+ String recurrenceInput = argMultimap.getValue(PREFIX_RECURRING).get();
+ try {
+ String[] recurrenceSplit = recurrenceInput.split(" ");
+ Integer recurrenceValue = Integer.parseInt(recurrenceSplit[0]);
+ String recurrenceTimePeriod = recurrenceSplit[1];
+ if (checkChronoUnitValidity(recurrenceTimePeriod)
+ && checkRecurrenceValueValidity(recurrenceValue)) {
+ Recurrence recurrence = new Recurrence(recurrenceValue, recurrenceTimePeriod);
+ event = new Event(description, stDateTime, endDateTime, recurrence, tagList);
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEventCommand.MESSAGE_USAGE));
+ }
+ } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddEventCommand.MESSAGE_USAGE));
+ }
+ } else {
+ event = new Event(description, stDateTime, endDateTime, tagList);
+ }
+ return new AddEventCommand(event);
+ } else {
+ throw new ParseException(AddCommand.MESSAGE_USAGE);
+ }
}
/**
@@ -57,4 +180,22 @@ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Pre
return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
}
+ /**
+ * Returns true if the recurrence unit is day/week/month/year.
+ * @param recurrenceTimePeriod input by user
+ * @return boolean
+ */
+ private static boolean checkChronoUnitValidity(String recurrenceTimePeriod) {
+ return recurrenceTimePeriod.equals(DAY) || recurrenceTimePeriod.equals(WEEK)
+ || recurrenceTimePeriod.equals(MONTH) || recurrenceTimePeriod.equals(YEAR);
+ }
+
+ /**
+ * Returns true if the recurrence value is > 0.
+ * @param value input by user
+ * @return boolean
+ */
+ private static boolean checkRecurrenceValueValidity(Integer value) {
+ return value > 0;
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..17a9dd751be 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,12 +9,18 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.ContactTaskTagCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DoneCommand;
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.LinkCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.SortCommand;
+import seedu.address.logic.commands.due.DueAtCommand;
+import seedu.address.logic.commands.due.DueBeforeCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -43,7 +49,6 @@ public Command parseCommand(String userInput) throws ParseException {
final String commandWord = matcher.group("commandWord");
final String arguments = matcher.group("arguments");
switch (commandWord) {
-
case AddCommand.COMMAND_WORD:
return new AddCommandParser().parse(arguments);
@@ -54,13 +59,16 @@ public Command parseCommand(String userInput) throws ParseException {
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 ListCommand.COMMAND_WORD:
- return new ListCommand();
+ return new ListCommandParser().parse(arguments);
+
+ case LinkCommand.COMMAND_WORD:
+ return new LinkCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
@@ -68,6 +76,21 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case DueBeforeCommand.COMMAND_WORD:
+ return new DueBeforeCommandParser().parse(arguments);
+
+ case DueAtCommand.COMMAND_WORD:
+ return new DueAtCommandParser().parse(arguments);
+
+ case DoneCommand.COMMAND_WORD:
+ return new DoneCommandParser().parse(arguments);
+
+ case SortCommand.COMMAND_WORD:
+ return new SortCommandParser().parse(arguments);
+
+ case ContactTaskTagCommand.COMMAND_WORD:
+ return new ContactTaskTagParser().parse(arguments);
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..5143891051d 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
@@ -1,10 +1,17 @@
package seedu.address.logic.parser;
+import static seedu.address.commons.core.Messages.EXTRA_ARGUMENT_MESSAGE;
+import static seedu.address.commons.core.Messages.EXTRA_SINGULAR_ARGUMENT_MESSAGE;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import seedu.address.logic.parser.exceptions.ParseException;
+
/**
* Tokenizes arguments string of the form: {@code preamble value value ...}
* e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
@@ -23,8 +30,37 @@ public class ArgumentTokenizer {
* @param prefixes Prefixes to tokenize the arguments string with
* @return ArgumentMultimap object that maps prefixes to their arguments
*/
- public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
+ public static ArgumentMultimap tokenize (String argsString, Prefix... prefixes) throws ParseException {
List positions = findAllPrefixPositions(argsString, prefixes);
+ List allPrefixPositions = findEveryPrefixPositions(argsString);
+
+ // Check that there are no extra prefixes
+ for (PrefixPosition prefixPosX : allPrefixPositions) {
+ boolean isPrefixExist = false;
+ for (PrefixPosition prefixPosY : positions) {
+ if (prefixPosX.equals(prefixPosY)) {
+ isPrefixExist = true;
+ }
+ }
+ if (!isPrefixExist) {
+ throw new ParseException(String.format(EXTRA_ARGUMENT_MESSAGE, prefixPosX));
+ }
+ }
+ List singularPrefixList = positions
+ .stream()
+ .map(PrefixPosition::getPrefix)
+ .filter(CliSyntax::isPrefixSingular)
+ .collect(Collectors.toList());
+
+ List checkDuplicateList = new ArrayList<>();
+ for (Prefix prefix : singularPrefixList) {
+ if (checkDuplicateList.stream().anyMatch(prefix::equals)) {
+ throw new ParseException(String.format(EXTRA_SINGULAR_ARGUMENT_MESSAGE, prefix));
+ } else {
+ checkDuplicateList.add(prefix);
+ }
+ }
+
return extractArguments(argsString, positions);
}
@@ -41,6 +77,26 @@ private static List findAllPrefixPositions(String argsString, Pr
.collect(Collectors.toList());
}
+ /**
+ * Finds every zero-based prefix positions in the given arguments string.
+ * Primarily used for finding out prefixes that don't fit.
+ *
+ * @param argsString Arguments string of the form: {@code preamble value value ...}
+ * @return List of zero-based prefix positions in the given arguments string
+ */
+ private static List findEveryPrefixPositions(String argsString) {
+ String regex = " [a-zA-Z]*/";
+ Pattern p = Pattern.compile(regex);
+
+ ArrayList everyPrefixPosition = new ArrayList<>();
+ Matcher m = p.matcher(argsString);
+ while (m.find()) {
+ everyPrefixPosition.add(new PrefixPosition(new Prefix(argsString.substring(m.start() + 1, m.end())),
+ m.start() + 1));
+ }
+ return everyPrefixPosition;
+ }
+
/**
* {@see findAllPrefixPositions}
*/
@@ -143,6 +199,24 @@ int getStartPosition() {
Prefix getPrefix() {
return prefix;
}
- }
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PrefixPosition)) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+
+ PrefixPosition otherPrefix = (PrefixPosition) obj;
+ return otherPrefix.getPrefix().equals(getPrefix())
+ && startPosition == otherPrefix.getStartPosition();
+ }
+
+ @Override
+ public String toString() {
+ return prefix.toString();
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ClearCommandParser.java b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java
new file mode 100644
index 00000000000..96cccf085c2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.UNKNOWN_CLEAR_COMMAND;
+
+import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class ClearCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteCommand
+ * and returns a DeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ClearCommand parse(String args) throws ParseException {
+ String[] splitArgs = args.trim().split(" ", 2);
+ try {
+ if (splitArgs[0].trim().equals("contact")) {
+ return new ClearCommand();
+ } else {
+ throw new ParseException(UNKNOWN_CLEAR_COMMAND);
+ }
+ } catch (ParseException | ArrayIndexOutOfBoundsException pe) {
+ throw new ParseException(UNKNOWN_CLEAR_COMMAND);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..da23830f286 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -1,5 +1,7 @@
package seedu.address.logic.parser;
+import java.util.Arrays;
+
/**
* Contains Command Line Interface (CLI) syntax definitions common to multiple commands
*/
@@ -11,5 +13,22 @@ public class CliSyntax {
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/");
+ public static final Prefix PREFIX_INDEX = new Prefix("i/");
+ public static final Prefix PREFIX_URL = new Prefix("url/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("desc/");
+ public static final Prefix PREFIX_DATE = new Prefix("date/");
+ public static final Prefix PREFIX_TIME = new Prefix("time/");
+ public static final Prefix PREFIX_STARTDATE = new Prefix("startdate/");
+ public static final Prefix PREFIX_STARTTIME = new Prefix("starttime/");
+ public static final Prefix PREFIX_ENDDATE = new Prefix("enddate/");
+ public static final Prefix PREFIX_ENDTIME = new Prefix("endtime/");
+ public static final Prefix PREFIX_RECURRING = new Prefix("recurring/");
+ public static final Prefix PREFIX_CONTACT_INDEX = new Prefix("contactIndex/");
+ public static final Prefix PREFIX_TASK_INDEX = new Prefix("taskIndex/");
+
+ public static final Prefix[] PLURAL_PREFIX_ARRAY = {PREFIX_TAG};
+ public static boolean isPrefixSingular(Prefix p) {
+ return !Arrays.asList(PLURAL_PREFIX_ARRAY).contains(p);
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ContactTaskTagParser.java b/src/main/java/seedu/address/logic/parser/ContactTaskTagParser.java
new file mode 100644
index 00000000000..f55089191e0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ContactTaskTagParser.java
@@ -0,0 +1,61 @@
+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_CONTACT_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_INDEX;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ContactTaskTagCommand;
+import seedu.address.logic.commands.ContactTaskTagCommand.EditPersonTags;
+import seedu.address.logic.commands.ContactTaskTagCommand.EditTaskTags;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ContactTaskTagCommand object
+ */
+public class ContactTaskTagParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ContactTaskTagCommand
+ * and returns an ContactTaskTagCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ContactTaskTagCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ String[] splitArgs = args.trim().split(" ", 1);
+ if (splitArgs.length < 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ContactTaskTagCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + splitArgs[0], PREFIX_TAG,
+ PREFIX_CONTACT_INDEX, PREFIX_TASK_INDEX);
+
+ List tag = argMultimap.getAllValues(PREFIX_TAG);
+ if (tag.size() < 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ContactTaskTagCommand.MESSAGE_USAGE));
+ }
+ try {
+ Index contactIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CONTACT_INDEX).get());
+ Index taskIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TASK_INDEX).get());
+ EditPersonTags editPersonTags = new EditPersonTags();
+ editPersonTags.setTags(ParserUtil.parseTags(tag));
+
+ EditTaskTags editTaskTags = new EditTaskTags();
+ editTaskTags.setTags(ParserUtil.parseTags(tag));
+
+ return new ContactTaskTagCommand(contactIndex, taskIndex, editPersonTags, editTaskTags);
+ } catch (NoSuchElementException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ContactTaskTagCommand.MESSAGE_USAGE), pe);
+ } catch (ParseException e) {
+ throw new ParseException(Messages.MESSAGE_INVALID_DISPLAYED_INDEX);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 522b93081cc..c083e0cb17d 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,9 +1,12 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.UNKNOWN_DELETE_COMMAND;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.delete.DeleteContactCommand;
+import seedu.address.logic.commands.delete.DeleteTaskCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -17,12 +20,26 @@ public class DeleteCommandParser implements Parser {
* @throws ParseException if the user input does not conform the expected format
*/
public DeleteCommand parse(String args) throws ParseException {
+ String[] splitArgs = args.trim().split(" ", 2);
try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
- } catch (ParseException pe) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ Index index = ParserUtil.parseIndex(splitArgs[1]);
+ if (splitArgs[0].trim().equals("contact")) {
+ return new DeleteContactCommand(index);
+ } else if (splitArgs[0].trim().equals("task")) {
+ return new DeleteTaskCommand(index);
+ } else {
+ throw new ParseException(UNKNOWN_DELETE_COMMAND);
+ }
+ } catch (ParseException | ArrayIndexOutOfBoundsException pe) {
+ if (args.contains("task")) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE), pe);
+ } else if (args.contains("contact")) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteContactCommand.MESSAGE_USAGE), pe);
+ } else {
+ throw new ParseException(UNKNOWN_DELETE_COMMAND);
+ }
}
}
diff --git a/src/main/java/seedu/address/logic/parser/DoneCommandParser.java b/src/main/java/seedu/address/logic/parser/DoneCommandParser.java
new file mode 100644
index 00000000000..60d70473922
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DoneCommandParser.java
@@ -0,0 +1,24 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DoneCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class DoneCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DoneCommand
+ * and returns a DoneCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DoneCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DoneCommand(index);
+ } catch (ParseException e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DoneCommand.MESSAGE_USAGE), e);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DueAtCommandParser.java b/src/main/java/seedu/address/logic/parser/DueAtCommandParser.java
new file mode 100644
index 00000000000..c52e020bbda
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DueAtCommandParser.java
@@ -0,0 +1,54 @@
+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_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.due.DueAtCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.DueAtPredicate;
+
+/**
+ * Parses input arguments and creates a new DueAtCommand object
+ */
+public class DueAtCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DueAtCommand
+ * and returns a DueAtCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DueAtCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ String[] splitArgs = args.trim().split(" ", 1);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + splitArgs[0], PREFIX_DATE,
+ PREFIX_TIME);
+
+ String date = argMultimap.getValue(PREFIX_DATE).orElseThrow(() -> new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DueAtCommand.MESSAGE_USAGE)));
+ try {
+ DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy");
+ LocalDate checkDate = LocalDate.parse(date, dateFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(Messages.MESSAGE_INVALID_DATE_FORMAT);
+ }
+
+ String time = argMultimap.getValue(PREFIX_TIME).orElseThrow(() -> new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DueAtCommand.MESSAGE_USAGE)));
+ try {
+ DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HHmm");
+ LocalTime checkTime = LocalTime.parse(time, timeFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(Messages.MESSAGE_INVALID_TIME_FORMAT);
+ }
+
+ String deadline = date + " " + time;
+ return new DueAtCommand(new DueAtPredicate(deadline));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DueBeforeCommandParser.java b/src/main/java/seedu/address/logic/parser/DueBeforeCommandParser.java
new file mode 100644
index 00000000000..25445eed569
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DueBeforeCommandParser.java
@@ -0,0 +1,55 @@
+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_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.due.DueBeforeCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.DueBeforePredicate;
+
+/**
+ * Parses input arguments and creates a new DueBeforeCommand object
+ */
+public class DueBeforeCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DueBeforeCommand
+ * and returns a DueBeforeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DueBeforeCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ String[] splitArgs = args.trim().split(" ", 1);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(" " + splitArgs[0], PREFIX_DATE,
+ PREFIX_TIME);
+
+ String date = argMultimap.getValue(PREFIX_DATE).orElseThrow(() -> new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DueBeforeCommand.MESSAGE_USAGE)));
+ try {
+ DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy");
+ LocalDate checkDate = LocalDate.parse(date, dateFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(Messages.MESSAGE_INVALID_DATE_FORMAT);
+ }
+
+ String time = argMultimap.getValue(PREFIX_TIME).orElseThrow(() -> new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DueBeforeCommand.MESSAGE_USAGE)));
+ try {
+ DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HHmm");
+ LocalTime checkTime = LocalTime.parse(time, timeFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(Messages.MESSAGE_INVALID_TIME_FORMAT);
+ }
+
+ String deadline = date + " " + time;
+ return new DueBeforeCommand(new DueBeforePredicate(deadline));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..40945bd5cf9 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,11 +2,20 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.UNKNOWN_EDIT_COMMAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
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_STARTDATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
import java.util.Collection;
import java.util.Collections;
@@ -15,7 +24,12 @@
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditContactCommand;
+import seedu.address.logic.commands.edit.EditContactCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditEventCommand;
+import seedu.address.logic.commands.edit.EditEventCommand.EditEventDescriptor;
+import seedu.address.logic.commands.edit.EditTodoCommand;
+import seedu.address.logic.commands.edit.EditTodoCommand.EditTodoDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.tag.Tag;
@@ -27,41 +41,146 @@ 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);
+ String[] splitArgs = args.trim().split(" ", 2);
+ if (splitArgs[0].equals("contact")) {
+ if (splitArgs.length < 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditContactCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_INDEX, PREFIX_NAME,
+ PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
- Index index;
+ Index index;
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
- }
+ if (!argMultimap.getValue(PREFIX_INDEX).isPresent()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditContactCommand.MESSAGE_USAGE));
+ }
- 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);
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditContactCommand.MESSAGE_USAGE), pe);
+ }
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
- }
+ 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(EditContactCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditContactCommand(index, editPersonDescriptor);
+ } else if (splitArgs[0].equals("todo")) {
+ if (splitArgs.length < 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTodoCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1],
+ PREFIX_INDEX, PREFIX_DESCRIPTION, PREFIX_DATE, PREFIX_TIME, PREFIX_TAG);
+
+ Index index;
+
+ if (!argMultimap.getValue(PREFIX_INDEX).isPresent()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTodoCommand.MESSAGE_USAGE));
+ }
- return new EditCommand(index, editPersonDescriptor);
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTodoCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditTodoDescriptor editTodoDescriptor = new EditTodoDescriptor();
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editTodoDescriptor.setDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get());
+ }
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ String date = argMultimap.getValue(PREFIX_DATE).get();
+ editTodoDescriptor.setDate(date);
+ }
+ if (argMultimap.getValue(PREFIX_TIME).isPresent()) {
+ String time = argMultimap.getValue(PREFIX_TIME).get();
+ editTodoDescriptor.setTime(time);
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editTodoDescriptor::setTags);
+
+ if (!editTodoDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditTodoCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditTodoCommand(index, editTodoDescriptor);
+ } else if (splitArgs[0].equals("event")) {
+ if (splitArgs.length < 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditEventCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_INDEX, PREFIX_DESCRIPTION,
+ PREFIX_STARTDATE, PREFIX_STARTTIME, PREFIX_ENDDATE, PREFIX_ENDTIME, PREFIX_TAG);
+
+ Index index;
+
+ if (!argMultimap.getValue(PREFIX_INDEX).isPresent()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditEventCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditEventCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditEventDescriptor editEventDescriptor = new EditEventDescriptor();
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editEventDescriptor.setDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get());
+ }
+ if (argMultimap.getValue(PREFIX_STARTDATE).isPresent()) {
+ editEventDescriptor.setStartDate(argMultimap.getValue(PREFIX_STARTDATE).get());
+ }
+ if (argMultimap.getValue(PREFIX_STARTTIME).isPresent()) {
+ editEventDescriptor.setStartTime(argMultimap.getValue(PREFIX_STARTTIME).get());
+ }
+ if (argMultimap.getValue(PREFIX_ENDDATE).isPresent()) {
+ editEventDescriptor.setEndDate(argMultimap.getValue(PREFIX_ENDDATE).get());
+ }
+ if (argMultimap.getValue(PREFIX_ENDTIME).isPresent()) {
+ editEventDescriptor.setEndTime(argMultimap.getValue(PREFIX_ENDTIME).get());
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editEventDescriptor::setTags);
+
+ if (!editEventDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditEventCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditEventCommand(index, editEventDescriptor);
+ } else {
+ throw new ParseException(UNKNOWN_EDIT_COMMAND);
+ }
}
/**
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 4fb71f23103..56870f1ba01 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,12 +1,21 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.UNKNOWN_FIND_COMMAND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Arrays;
+import java.util.stream.Stream;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.find.FindContactCommand;
+import seedu.address.logic.commands.find.FindEventCommand;
+import seedu.address.logic.commands.find.FindTodoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.ContactMatchesFindKeywordPredicate;
+import seedu.address.model.task.TaskMatchesFindKeywordPredicate;
/**
* Parses input arguments and creates a new FindCommand object
@@ -19,15 +28,166 @@ public class FindCommandParser implements Parser {
* @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[] splitArgs = args.trim().split(" ", 2);
+ if (splitArgs[0].equals("contact")) {
+ if (splitArgs.length < 2 || splitArgs[1].trim().isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindContactCommand.MESSAGE_USAGE));
+ }
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_NAME, PREFIX_TAG);
+
+ if (areAllPrefixesNotPresent(argMultimap, PREFIX_NAME, PREFIX_TAG)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindContactCommand.MESSAGE_USAGE));
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_TAG)) {
+ String name = argMultimap.getValue(PREFIX_NAME).get();
+ if (name.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindContactCommand.MESSAGE_USAGE));
+ }
+ String[] nameKeywords = name.split("\\s+");
+ String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (tagKeywords.length != 1 || tagKeywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindContactCommand.MESSAGE_USAGE));
+ }
+ return new FindContactCommand(new ContactMatchesFindKeywordPredicate(
+ Arrays.asList(nameKeywords), tagKeywords[0]));
+ } else if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ String name = argMultimap.getValue(PREFIX_NAME).get();
+ if (name.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindContactCommand.MESSAGE_USAGE));
+ }
+ String[] nameKeywords = name.split("\\s+");
+ return new FindContactCommand(new ContactMatchesFindKeywordPredicate(Arrays.asList(nameKeywords)));
+ } else {
+ String[] keywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (keywords.length != 1 || keywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindContactCommand.MESSAGE_USAGE));
+ }
+ return new FindContactCommand(new ContactMatchesFindKeywordPredicate(keywords[0]));
+ }
+ } else if (splitArgs[0].equals("todo")) {
+ if (splitArgs.length < 2 || splitArgs[1].trim().isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTodoCommand.MESSAGE_USAGE));
+ }
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION, PREFIX_TAG);
+
+ if (areAllPrefixesNotPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_TAG)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTodoCommand.MESSAGE_USAGE));
+ }
+
+ if (arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_TAG)) {
+ String desc = argMultimap.getValue(PREFIX_DESCRIPTION).get();
+ if (desc.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTodoCommand.MESSAGE_USAGE));
+ }
+ String[] descKeywords = desc.split("\\s+");
+ String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (tagKeywords.length != 1 || tagKeywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTodoCommand.MESSAGE_USAGE));
+ }
+ return new FindTodoCommand(new TaskMatchesFindKeywordPredicate(
+ Arrays.asList(descKeywords), tagKeywords[0]));
+ } else if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ String desc = argMultimap.getValue(PREFIX_DESCRIPTION).get();
+ if (desc.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTodoCommand.MESSAGE_USAGE));
+ }
+ String[] descKeywords = desc.split("\\s+");
+ return new FindTodoCommand(new TaskMatchesFindKeywordPredicate(Arrays.asList(descKeywords)));
+ } else {
+ String[] keywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (keywords.length != 1 || keywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindTodoCommand.MESSAGE_USAGE));
+ }
+ return new FindTodoCommand(new TaskMatchesFindKeywordPredicate(keywords[0]));
+ }
+ } else if (splitArgs[0].equals("event")) {
+ if (splitArgs.length < 2 || splitArgs[1].trim().isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindEventCommand.MESSAGE_USAGE));
+ }
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION, PREFIX_TAG);
+
+ if (areAllPrefixesNotPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_TAG)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindEventCommand.MESSAGE_USAGE));
+ }
+
+ if (arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_TAG)) {
+ String desc = argMultimap.getValue(PREFIX_DESCRIPTION).get();
+ if (desc.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindEventCommand.MESSAGE_USAGE));
+ }
+ String[] descKeywords = desc.split("\\s+");
+ String[] tagKeywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (tagKeywords.length != 1 || tagKeywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindEventCommand.MESSAGE_USAGE));
+ }
+ return new FindEventCommand(new TaskMatchesFindKeywordPredicate(
+ Arrays.asList(descKeywords), tagKeywords[0]));
+ } else if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ String desc = argMultimap.getValue(PREFIX_DESCRIPTION).get();
+ if (desc.trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindEventCommand.MESSAGE_USAGE));
+ }
+ String[] descKeywords = desc.split("\\s+");
+ return new FindEventCommand(new TaskMatchesFindKeywordPredicate(Arrays.asList(descKeywords)));
+ } else {
+ String[] keywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+");
+
+ if (keywords.length != 1 || keywords[0].trim().equals("")) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FindEventCommand.MESSAGE_USAGE));
+ }
+ return new FindEventCommand(new TaskMatchesFindKeywordPredicate(keywords[0]));
+ }
+ } else {
+ throw new ParseException(UNKNOWN_FIND_COMMAND);
}
+ }
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ /**
+ * 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());
+ }
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ /**
+ * Returns true if all of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean areAllPrefixesNotPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> !argumentMultimap.getValue(prefix).isPresent());
}
}
diff --git a/src/main/java/seedu/address/logic/parser/LinkCommandParser.java b/src/main/java/seedu/address/logic/parser/LinkCommandParser.java
new file mode 100644
index 00000000000..7696c8e8310
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LinkCommandParser.java
@@ -0,0 +1,94 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_URL;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.LinkCommand;
+import seedu.address.logic.commands.link.LinkCollaborativeCommand;
+import seedu.address.logic.commands.link.LinkMeetingCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.MeetingLink;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class LinkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the LinkCommand
+ * and returns an LinkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public LinkCommand parse(String args) throws ParseException {
+ String[] splitArgs = args.trim().split(" ", 2);
+ if (splitArgs[0].trim().split(" ")[0].trim().equals("meeting")) {
+ if (splitArgs.length < 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ LinkMeetingCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION, PREFIX_URL, PREFIX_INDEX,
+ PREFIX_DATE, PREFIX_TIME);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_URL, PREFIX_INDEX, PREFIX_DATE, PREFIX_TIME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ LinkMeetingCommand.MESSAGE_USAGE));
+ }
+ String description = argMultimap.getValue(PREFIX_DESCRIPTION).get().trim();
+ String date = argMultimap.getValue(PREFIX_DATE).get().trim();
+ String time = argMultimap.getValue(PREFIX_TIME).get().trim();
+ ParserUtil.checkDateValidity(date);
+ ParserUtil.checkTimeValidity(time);
+ String url = argMultimap.getValue(PREFIX_URL).get().trim();
+ String meetingTime = date + " " + time;
+ Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get().trim());
+ ParserUtil.validateLink(url);
+
+ MeetingLink meetingLink = ParserUtil.parseMeetingLink(description, url, meetingTime);
+
+ return new LinkMeetingCommand(index, meetingLink);
+ } else if (splitArgs[0].trim().split(" ")[0].trim().equals("doc")) {
+ if (splitArgs.length < 2) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ LinkCollaborativeCommand.MESSAGE_USAGE));
+ }
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(" " + splitArgs[1], PREFIX_DESCRIPTION, PREFIX_URL, PREFIX_INDEX);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_URL, PREFIX_INDEX)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ LinkCollaborativeCommand.MESSAGE_USAGE));
+ }
+ String description = argMultimap.getValue(PREFIX_DESCRIPTION).get().trim();
+ String url = argMultimap.getValue(PREFIX_URL).get().trim();
+ Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get().trim());
+ ParserUtil.validateLink(url);
+
+ CollaborativeLink collaborativeLink = ParserUtil.parseCollaborativeLink(description, url);
+
+ return new LinkCollaborativeCommand(index, collaborativeLink);
+ } else {
+ throw new ParseException(LinkCommand.MESSAGE_USAGE);
+ }
+
+ }
+
+ /**
+ * 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/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
new file mode 100644
index 00000000000..db9d052aa84
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.list.ListContactCommand;
+import seedu.address.logic.commands.list.ListEventCommand;
+import seedu.address.logic.commands.list.ListTaskCommand;
+import seedu.address.logic.commands.list.ListTodoCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ListCommand object
+ */
+public class ListCommandParser 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 ListCommand parse(String args) throws ParseException {
+ if (args.trim().equals("contact")) {
+ return new ListContactCommand();
+ } else if (args.trim().equals("todo")) {
+ return new ListTodoCommand();
+ } else if (args.trim().equals("event")) {
+ return new ListEventCommand();
+ } else if (args.trim().equals("task")) {
+ return new ListTaskCommand();
+ } else {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..e09905803ca 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -1,7 +1,13 @@
package seedu.address.logic.parser;
import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TIME_FORMAT;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -14,6 +20,10 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.Link;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Task;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -95,6 +105,64 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Validates a {@code String url} an returns a {@Code Boolean}
+ *
+ * @throws ParseException if the given {@code String url} is invalid.
+ */
+ public static boolean validateLink(String url) throws ParseException {
+ requireNonNull(url);
+ String trimmedUrl = url.trim();
+ boolean isValid = Link.isValidUrl(trimmedUrl);
+ if (!isValid) {
+ throw new ParseException((Link.MESSAGE_CONSTRAINTS));
+ }
+ return true;
+ }
+
+ /**
+ * Validates a {@code String description} an returns a {@Code Boolean}
+ *
+ * @throws ParseException if the given {@code String description} is invalid (too long).
+ */
+ public static boolean validateDescription(String description) throws ParseException {
+ requireNonNull(description);
+ String trimmedDescription = description.trim();
+ boolean isValid = Task.isValidDescription(trimmedDescription);
+ if (!isValid) {
+ throw new ParseException((Task.MESSAGE_CONSTRAINTS));
+ }
+ return true;
+ }
+
+
+ /**
+ * Returns a Collaborative Link that is guaranteed to have a valid URL.
+ *
+ * @param description The description of the Collaborative Link
+ * @param url The url to the Collaborative Folder
+ * @return A Collaborative Link object with description and url
+ */
+ public static CollaborativeLink parseCollaborativeLink(String description, String url) {
+ requireNonNull(description);
+ String trimmedUrl = url.trim();
+ return new CollaborativeLink(description, trimmedUrl);
+ }
+
+ /**
+ * Returns a Meeting Link that is guaranteed to have a valid URL.
+ *
+ * @param description The description of the Meeting Link
+ * @param url The url to the Meeting
+ * @param meetingTime The meeting time
+ * @return A Meeting Link object with description, url, and meeting time
+ */
+ public static MeetingLink parseMeetingLink(String description, String url, String meetingTime) {
+ requireNonNull(description, meetingTime);
+ String trimmedUrl = url.trim();
+ return new MeetingLink(description, trimmedUrl, meetingTime);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +189,31 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+
+ /**
+ * Checks if date input is valid.
+ * @param date input by user
+ */
+ public static void checkDateValidity(String date) throws ParseException {
+ try {
+ DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy");
+ LocalDate checkDate = LocalDate.parse(date, dateFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(MESSAGE_INVALID_DATE_FORMAT);
+ }
+ }
+
+ /**
+ * Checks if time input is valid.
+ * @param time input by user
+ */
+ public static void checkTimeValidity(String time) throws ParseException {
+ try {
+ DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HHmm");
+ LocalTime checkTime = LocalTime.parse(time, timeFormat);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(MESSAGE_INVALID_TIME_FORMAT);
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java
new file mode 100644
index 00000000000..301aa493dac
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.SortCommand;
+import seedu.address.logic.commands.sort.SortClearCommand;
+import seedu.address.logic.commands.sort.SortContactCommand;
+import seedu.address.logic.commands.sort.SortTaskCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class SortCommandParser implements Parser {
+ @Override
+ public SortCommand parse(String args) throws ParseException {
+ if (args.trim().equals("task")) {
+ return new SortTaskCommand();
+ } else if (args.trim().equals("contact")) {
+ return new SortContactCommand();
+ } else if (args.trim().equals("clear")) {
+ return new SortClearCommand();
+ } else {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..79ff0387d86 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -117,4 +117,7 @@ public boolean equals(Object other) {
public int hashCode() {
return persons.hashCode();
}
+ public boolean isEmpty() {
+ return persons.isEmpty();
+ }
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..e6390f9b6ce 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,11 +1,15 @@
package seedu.address.model;
import java.nio.file.Path;
+import java.util.Comparator;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
/**
* The API of the Model component.
@@ -13,6 +17,7 @@
public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true;
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -84,4 +89,41 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ void updateFilteredTaskList(Predicate super Task> predicate);
+
+ void addTodo(Todo todo);
+
+ void addEvent(Event event);
+
+ void addTask(Task task);
+
+ /**
+ * Replaces the given Task {@code target} with {@code editedTask}.
+ * {@code target} must exist in the address book.
+ * The task identity of {@code editedTask} must not be the same as another existing Task in the life book.
+ */
+ void setTask(Task target, Task editedTask);
+
+ void deleteTodo(Task task);
+
+ void deleteEvent(Task task);
+
+ boolean hasTask(Task task);
+
+ ObservableList getFilteredTaskList();
+
+ ObservableList getDueSoonTaskList();
+
+ ReadOnlyTaskList getTaskList();
+
+ void markAsDone(Task target);
+
+ void updateSortedTaskList(Comparator taskComparator);
+
+ void updateSortedPersonList(Comparator personComparator);
+ boolean filteredTaskListIsEmpty();
+ boolean filteredAddressBookIsEmpty();
+ boolean taskListIsEmpty();
+ boolean addressBookIsEmpty();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 0650c954f5c..0bc8ae6f00f 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -1,17 +1,24 @@
package seedu.address.model;
+import static java.time.temporal.ChronoUnit.WEEKS;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.time.LocalDateTime;
+import java.util.Comparator;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
import seedu.address.model.person.Person;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
/**
* Represents the in-memory model of the address book data.
@@ -21,12 +28,17 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
+ private final SortedList sortedPersons;
private final FilteredList filteredPersons;
+ private final SortedList sortedTasks;
+ private final FilteredList filteredTasks;
+ private final FilteredList dueSoonTasks;
+ private final TaskList taskList;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, ReadOnlyTaskList taskList) {
super();
requireAllNonNull(addressBook, userPrefs);
@@ -34,11 +46,30 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ this.taskList = new TaskList(taskList);
+ sortedPersons = new SortedList<>(this.addressBook.getPersonList());
+ filteredPersons = new FilteredList<>(sortedPersons);
+ sortedTasks = new SortedList<>(this.taskList.getTaskList());
+ filteredTasks = new FilteredList<>(sortedTasks);
+ dueSoonTasks = new FilteredList<>(sortedTasks, task -> {
+ if (task.getStatus()) {
+ return false;
+ } else {
+ LocalDateTime currentDateTimePlusOneWeek = LocalDateTime.now().plus(1, WEEKS);
+ LocalDateTime deadline = null;
+ if (task instanceof Todo) {
+ deadline = task.getDeadline();
+ } else if (task instanceof Event) {
+ deadline = task.getEnd();
+ }
+ assert deadline != null : "Task's deadline is not defined properly!";
+ return deadline.isBefore(currentDateTimePlusOneWeek) && deadline.isAfter(LocalDateTime.now());
+ }
+ });
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new TaskList());
}
//=========== UserPrefs ==================================================================================
@@ -112,6 +143,15 @@ public void setPerson(Person target, Person editedPerson) {
addressBook.setPerson(target, editedPerson);
}
+ @Override
+ public boolean filteredAddressBookIsEmpty() {
+ return filteredPersons.isEmpty();
+ }
+ @Override
+ public boolean addressBookIsEmpty() {
+ return addressBook.isEmpty();
+ }
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -145,7 +185,85 @@ public boolean equals(Object obj) {
ModelManager other = (ModelManager) obj;
return addressBook.equals(other.addressBook)
&& userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
+ && filteredPersons.equals(other.filteredPersons)
+ && filteredTasks.equals(other.filteredTasks);
+ }
+
+ @Override
+ public void updateSortedPersonList(Comparator personComparator) {
+ sortedPersons.setComparator(personComparator);
+ }
+ //=========== TaskList ================================================================================
+ @Override
+ public void addTodo(Todo todo) {
+ taskList.addTask(todo);
+ updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ }
+ @Override
+ public void addEvent(Event event) {
+ this.taskList.addTask(event);
+ updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ }
+ @Override
+ public void addTask(Task task) {
+ this.taskList.addTask(task);
+ updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS);
+ }
+ @Override
+ public void deleteTodo(Task task) {
+ this.taskList.removeTask(task);
+ }
+ @Override
+ public void deleteEvent(Task task) {
+ this.taskList.removeTask(task);
+ }
+ @Override
+ public ReadOnlyTaskList getTaskList() {
+ return taskList;
+ }
+
+ public boolean hasTask(Task task) {
+ return this.taskList.hasTask(task);
+ }
+ @Override
+ public void setTask(Task target, Task editedTask) {
+ requireAllNonNull(target, editedTask);
+ taskList.setTask(target, editedTask);
+ }
+ @Override
+ public void markAsDone(Task target) {
+ requireAllNonNull(target);
+ taskList.markAsDone(target);
+ }
+ @Override
+ public boolean filteredTaskListIsEmpty() {
+ return filteredTasks.isEmpty();
+ }
+
+ @Override
+ public boolean taskListIsEmpty() {
+ return taskList.isEmpty();
+ }
+
+ //=========== Filtered Task List Accessors =============================================================
+ @Override
+ public ObservableList getFilteredTaskList() {
+ return filteredTasks;
+ }
+
+ @Override
+ public ObservableList getDueSoonTaskList() {
+ return dueSoonTasks;
+ }
+
+ @Override
+ public void updateFilteredTaskList(Predicate super Task> predicate) {
+ requireNonNull(predicate);
+ filteredTasks.setPredicate(predicate);
+ }
+ @Override
+ public void updateSortedTaskList(Comparator taskComparator) {
+ sortedTasks.setComparator(taskComparator);
}
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyTaskList.java b/src/main/java/seedu/address/model/ReadOnlyTaskList.java
new file mode 100644
index 00000000000..c06e65336d5
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyTaskList.java
@@ -0,0 +1,13 @@
+package seedu.address.model;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.task.Task;
+
+public interface ReadOnlyTaskList {
+
+ /**
+ * Returns an unmodifiable view of the persons list.
+ * This list will not contain any duplicate persons.
+ */
+ ObservableList getTaskList();
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..b983acde4c0 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -13,4 +13,6 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ Path getTaskListFilePath();
+
}
diff --git a/src/main/java/seedu/address/model/TaskList.java b/src/main/java/seedu/address/model/TaskList.java
new file mode 100644
index 00000000000..ee1023f0e46
--- /dev/null
+++ b/src/main/java/seedu/address/model/TaskList.java
@@ -0,0 +1,127 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.UniqueTaskList;
+
+/**
+ * Wraps all data at the address-book level
+ * Duplicates are not allowed (by .isSamePerson comparison)
+ */
+public class TaskList implements ReadOnlyTaskList {
+
+ private final UniqueTaskList tasks;
+
+ /*
+ * 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.
+ */
+ {
+ tasks = new UniqueTaskList();
+ }
+
+ public TaskList() {}
+
+ /**
+ * Creates an AddressBook using the Persons in the {@code toBeCopied}
+ */
+ public TaskList(ReadOnlyTaskList 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 setTasks(List tasks) {
+ this.tasks.setTasks(tasks);
+ }
+
+ /**
+ * Resets the existing data of this {@code AddressBook} with {@code newData}.
+ */
+ public void resetData(ReadOnlyTaskList newData) {
+ requireNonNull(newData);
+
+ setTasks(newData.getTaskList());
+ }
+
+ //// person-level operations
+
+ /**
+ * Returns true if a person with the same identity as {@code person} exists in the address book.
+ */
+ public boolean hasTask(Task task) {
+ requireNonNull(task);
+ return tasks.contains(task);
+ }
+
+ /**
+ * Adds a person to the address book.
+ * The person must not already exist in the address book.
+ */
+ public void addTask(Task t) {
+ tasks.add(t);
+ }
+
+ /**
+ * 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 setTask(Task target, Task editedTask) {
+ requireNonNull(editedTask);
+
+ tasks.setTask(target, editedTask);
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeTask(Task key) {
+ tasks.remove(key);
+ }
+
+ //// util methods
+
+ @Override
+ public String toString() {
+ return tasks.asUnmodifiableObservableList().size() + " tasks";
+ // TODO: refine later
+ }
+
+ @Override
+ public ObservableList getTaskList() {
+ return tasks.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskList // instanceof handles nulls
+ && this.tasks.equals(((TaskList) other).tasks));
+ }
+
+ @Override
+ public int hashCode() {
+ return tasks.hashCode();
+ }
+
+ public void markAsDone(Task target) {
+ this.tasks.markAsDone(target);
+ }
+ public boolean isEmpty() {
+ return tasks.isEmpty();
+ }
+}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..e9b5a56f7c4 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path taskListFilePath = Paths.get("data", "tasklist.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -50,12 +51,19 @@ public void setGuiSettings(GuiSettings guiSettings) {
public Path getAddressBookFilePath() {
return addressBookFilePath;
}
+ public Path getTaskListFilePath() {
+ return taskListFilePath;
+ }
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
this.addressBookFilePath = addressBookFilePath;
}
+ public void setTaskListFilePath (Path taskListFilePath) {
+ requireNonNull(taskListFilePath);
+ this.taskListFilePath = taskListFilePath;
+ }
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/person/ContactMatchesFindKeywordPredicate.java b/src/main/java/seedu/address/model/person/ContactMatchesFindKeywordPredicate.java
new file mode 100644
index 00000000000..2987746ebcb
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactMatchesFindKeywordPredicate.java
@@ -0,0 +1,64 @@
+package seedu.address.model.person;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Tag} and {@code Name} matches the tag keyword given.
+ */
+public class ContactMatchesFindKeywordPredicate implements Predicate {
+ private final List keywordName;
+ private final String keywordTag;
+
+ /**
+ * Construct a predicate to match keyword tag to contact's tag
+ * @param keywordTag the keyword for the tag
+ */
+ public ContactMatchesFindKeywordPredicate(String keywordTag) {
+ // make sure keyword is only one word
+ assert !keywordTag.contains("\\s+");
+ this.keywordTag = keywordTag;
+ this.keywordName = new ArrayList<>();
+ }
+
+ /**
+ * Construct a predicate to match keyword name to contact's name
+ * @param keywordName the keyword for the name
+ */
+ public ContactMatchesFindKeywordPredicate(List keywordName) {
+ this.keywordName = keywordName;
+ this.keywordTag = "";
+ }
+
+ /**
+ * Construct a predicate to match keyword name and tag to contact's name and tag
+ * @param keywordName the keyword for the name
+ * @param keywordTag the keyword for the tag
+ */
+ public ContactMatchesFindKeywordPredicate(List keywordName, String keywordTag) {
+ // make sure keyword is only one word
+ assert !keywordTag.contains("\\s+");
+ this.keywordTag = keywordTag;
+ this.keywordName = keywordName;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ boolean matchName = keywordName.size() == 0 || keywordName.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ boolean matchTag = keywordTag == "" || person.getTags().stream()
+ .anyMatch(keyword -> this.keywordTag.trim().toLowerCase().equals(keyword.tagName.trim().toLowerCase()));
+ return matchName && matchTag;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactMatchesFindKeywordPredicate // instanceof handles nulls
+ && keywordName.equals(((ContactMatchesFindKeywordPredicate) other).keywordName) // state check
+ && keywordTag.equals(((ContactMatchesFindKeywordPredicate) other).keywordTag));
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 557a7a60cd5..0062e2fdc92 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -108,10 +108,13 @@ public String toString() {
builder.append(getName())
.append(" Phone: ")
.append(getPhone())
+ .append("\n")
.append(" Email: ")
.append(getEmail())
+ .append("\n")
.append(" Address: ")
.append(getAddress())
+ .append("\n")
.append(" Tags: ");
getTags().forEach(builder::append);
return builder.toString();
diff --git a/src/main/java/seedu/address/model/person/PersonNameComparator.java b/src/main/java/seedu/address/model/person/PersonNameComparator.java
new file mode 100644
index 00000000000..bfc6afc5cbc
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PersonNameComparator.java
@@ -0,0 +1,10 @@
+package seedu.address.model.person;
+
+import java.util.Comparator;
+
+public class PersonNameComparator implements Comparator {
+ @Override
+ public int compare(Person o1, Person o2) {
+ return o1.getName().fullName.compareTo(o2.getName().fullName);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index 0fee4fe57e6..2f7b259ffc8 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -134,4 +134,7 @@ private boolean personsAreUnique(List persons) {
}
return true;
}
+ public boolean isEmpty() {
+ return internalList.isEmpty();
+ }
}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index b0ea7e7dad7..31d8594b34d 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -44,11 +44,14 @@ public int hashCode() {
return tagName.hashCode();
}
+ public Tag getTag() {
+ return this;
+ }
+
/**
* Format state as text for viewing.
*/
public String toString() {
return '[' + tagName + ']';
}
-
}
diff --git a/src/main/java/seedu/address/model/task/CollaborativeLink.java b/src/main/java/seedu/address/model/task/CollaborativeLink.java
new file mode 100644
index 00000000000..27fda673855
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/CollaborativeLink.java
@@ -0,0 +1,45 @@
+package seedu.address.model.task;
+
+public class CollaborativeLink extends Link {
+
+ public CollaborativeLink() {
+ this("No collaborative link", "-");
+ }
+
+ public CollaborativeLink(String description, String url) {
+ super(description, url);
+ }
+
+ /**
+ * Returns a String representation of the Collaborative Link.
+ * This representation includes the description and url in the format of outputFormatter.
+ *
+ * @return a String representation of the Collaborative Link.
+ */
+ @Override
+ public String toString() {
+ return getDescription() + " " + getUrl();
+ }
+
+ /**
+ * Returns a boolean value indicating if the Collaborative Link is equal to
+ * another object by determining if descriptions and url parameters
+ * are equal.
+ *
+ * @param o an object that is compared to the Collaborative Link to determine if both are equal
+ * @return true or false if the Collaborative Link is equal or not equal to the object respectively.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof CollaborativeLink) {
+ CollaborativeLink link = (CollaborativeLink) o;
+ boolean isEqual = this.getDescription().equals(link.getDescription())
+ && this.getUrl().equals(link.getUrl());
+ return isEqual;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..6b47afbdfa5
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DescriptionContainsKeywordsPredicate.java
@@ -0,0 +1,31 @@
+package seedu.address.model.task;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class DescriptionContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public DescriptionContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getDescription(), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DescriptionContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((DescriptionContainsKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/DueAtPredicate.java b/src/main/java/seedu/address/model/task/DueAtPredicate.java
new file mode 100644
index 00000000000..54dd74955bd
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DueAtPredicate.java
@@ -0,0 +1,22 @@
+package seedu.address.model.task;
+
+/**
+ * Checks for every task (To-do and event) that is due at the given date and time.
+ */
+public class DueAtPredicate extends DuePredicate {
+ public DueAtPredicate(String strDeadline) {
+ super(strDeadline);
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return task.getLocalDateTime().isEqual(deadline);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DueAtPredicate // instanceof handles nulls
+ && deadline.equals(((DueAtPredicate) other).deadline)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/DueBeforePredicate.java b/src/main/java/seedu/address/model/task/DueBeforePredicate.java
new file mode 100644
index 00000000000..cd2da1fb77c
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DueBeforePredicate.java
@@ -0,0 +1,22 @@
+package seedu.address.model.task;
+
+/**
+ * Checks for every task (To-do and event) that is due before the given date and time.
+ */
+public class DueBeforePredicate extends DuePredicate {
+ public DueBeforePredicate(String strDeadline) {
+ super(strDeadline);
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return task.getLocalDateTime().isBefore(deadline);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DueBeforePredicate // instanceof handles nulls
+ && deadline.equals(((DueBeforePredicate) other).deadline)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/DuePredicate.java b/src/main/java/seedu/address/model/task/DuePredicate.java
new file mode 100644
index 00000000000..d36d23050e9
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/DuePredicate.java
@@ -0,0 +1,36 @@
+package seedu.address.model.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.function.Predicate;
+
+public abstract class DuePredicate implements Predicate {
+ protected final LocalDateTime deadline;
+ private final String strDeadline;
+
+ /**
+ * Converts deadline into a LocalDateTime type.
+ *
+ * @param strDeadline string format of date + time
+ */
+ public DuePredicate(String strDeadline) {
+ this.strDeadline = strDeadline;
+ DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm");
+ LocalDateTime deadline = LocalDateTime.parse(strDeadline, dateTimeFormat);
+ this.deadline = deadline;
+ }
+
+ public String getDateTime() {
+ return this.strDeadline;
+ }
+
+ public abstract boolean test(Task task);
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DueBeforePredicate // instanceof handles nulls
+ && deadline.equals(((DueBeforePredicate) other).deadline)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/Event.java b/src/main/java/seedu/address/model/task/Event.java
new file mode 100644
index 00000000000..fab39b08e22
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Event.java
@@ -0,0 +1,497 @@
+package seedu.address.model.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.model.tag.Tag;
+
+/**
+ * Encapsulates a task to be completed over a span of time (i.e period).
+ */
+
+public class Event extends Task {
+
+ /**The format of inputted dates and times that the class can accept. */
+ private static final DateTimeFormatter INPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm");
+
+ /**The format of outputted dates and times by the class. */
+ private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd MMM yyyy HHmm");
+
+ /**Index of the end of the first provided date and time.*/
+ private static final int END_OF_FIRST_DATE_TIME_INDEX = 15;
+
+ /**Index of the start of the second provided date and time.*/
+ private static final int START_OF_SECOND_DATE_TIME_INDEX = 19;
+
+ /**The date and time at which the event begins. */
+ private LocalDateTime start;
+
+ /**The date and time at which the event ends. */
+ private LocalDateTime end;
+
+ /**The meeting link url. */
+ private MeetingLink meetingLink;
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ */
+ public Event (boolean isDone, String description, String start, String end, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.isDone = isDone;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param recurrence the recurrence of event.
+ */
+ public Event (boolean isDone, String description, String start, String end, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.isDone = isDone;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ */
+ public Event (boolean isDone, String description, String start, String end, MeetingLink link, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.isDone = isDone;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.meetingLink = link;
+ }
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param recurrence the recurrence of event.
+ */
+ public Event (boolean isDone, String description, String start, String end,
+ MeetingLink link, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.isDone = isDone;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.meetingLink = link;
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event (String description, String start, String end, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Constructs an event that has not been completed with a brief
+ * description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param meetingLink the meeting link of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event (String description, String start, String end, MeetingLink meetingLink, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.meetingLink = meetingLink;
+ }
+
+ /**
+ * Constructs an event that may or may not be completed with a brief
+ * description and period of time and a meeting link.
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param meetingLink the meeting link of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event (boolean isDone, String description, LocalDateTime start,
+ LocalDateTime end, MeetingLink meetingLink, Set tags) {
+ super(isDone, description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.meetingLink = meetingLink;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description and period of time.
+ *
+ * @param isDone indicates if the event has been completed.
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(boolean isDone, String description, LocalDateTime start, LocalDateTime end, Set tags) {
+ super(isDone, description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description, period of time, and a meeting link
+ *
+ * @param isDone indicates if the event has been completed.
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param recurrence the recurrence of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(boolean isDone, String description, LocalDateTime start,
+ LocalDateTime end, Recurrence recurrence, MeetingLink link, Set tags) {
+ super(isDone, description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.recurrence = recurrence;
+ this.meetingLink = link;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description, period of time, and a meeting link
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param recurrence the recurrence of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(String description, LocalDateTime start,
+ LocalDateTime end, Recurrence recurrence, MeetingLink link, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.recurrence = recurrence;
+ this.meetingLink = link;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description, period of time, and a meeting link
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param link the Meeting Link of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(String description, String start, String end, Recurrence recurrence, MeetingLink link, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.recurrence = recurrence;
+ this.meetingLink = link;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description, period of time, and a meeting link
+ *
+ * @param isDone indicates if the event has been completed.
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event.
+ * @param recurrence the recurrence of event.
+ * @param end the ending date and time of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(boolean isDone, String description, LocalDateTime start,
+ LocalDateTime end, Recurrence recurrence, Set tags) {
+ super(isDone, description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event (String).
+ * @param end the ending date and time of event (String).
+ * @param recurrence the recurrence of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(String description, String start, String end, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ this.start = LocalDateTime.parse(start, INPUT_DATE_TIME_FORMAT);
+ this.end = LocalDateTime.parse(end, INPUT_DATE_TIME_FORMAT);
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs an event which may or may not be completed
+ * with a brief description and period of time.
+ *
+ * @param description a brief description of the event.
+ * @param start the starting date and time of event (LocalDateTime).
+ * @param end the ending date and time of event (LocalDateTime).
+ * @param recurrence the recurrence of event.
+ * @param tags a set of tags attached to the event.
+ */
+ public Event(String description, LocalDateTime start, LocalDateTime end, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ assert start != null;
+ assert end != null;
+ this.start = start;
+ this.end = end;
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Returns the String representation of the period of time which the event occurred over. in the
+ * form of -start- to -end-.
+ * Dates and times are in the format of outputFormatter.
+ *
+ * @return the String representation of the period of which the event occurred over.
+ */
+ public String getPeriod() {
+ return this.start.format(OUTPUT_DATE_TIME_FORMAT).toString() + " to "
+ + this.end.format(OUTPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ /**
+ * Returns the String representation of the start time which the event occurred over. In the
+ * form of -start-.
+ * Dates and times are in the format of inputFormatter.
+ *
+ * @return the String representation of the period of which the event starts.
+ */
+ public String getStartTime() {
+ return this.start.format(INPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ /**
+ * Returns the String representation of the start time which the event occurred over. In the
+ * form of -end-.
+ * Dates and times are in the format of inputFormatter.
+ *
+ * @return the String representation of the period of which the event ends.
+ */
+ public String getEndTime() {
+ return this.end.format(INPUT_DATE_TIME_FORMAT).toString();
+ }
+ /**
+ * Returns true if both events of the same description have at least one other identity field that is the same.
+ * This defines a weaker notion of equality between two events.
+ */
+ public boolean isSameEvent(Event otherEvent) {
+ if (otherEvent == this) {
+ return true;
+ }
+
+ return otherEvent != null
+ && otherEvent.getDescription().equals(getDescription())
+ && (otherEvent.getDescriptionDateTime().equals(getDescriptionDateTime()));
+ }
+
+ @Override
+ public void markAsDone() {
+ this.isDone = true;
+ }
+
+ /**
+ * Returns the string representation of the event, which includes the status icon, description, and period.
+ *
+ * @return the string representation of the event.
+ */
+ @Override
+ public String toString() {
+ return "[" + getStatusIcon() + "] " + getDescription() + " (at: " + getPeriod() + ") " + getTagsToString();
+ }
+
+ public String getDescriptionDateTime() {
+ return this.description + " (at: " + getPeriod() + ")";
+ }
+
+ /**
+ * Returns a boolean value indicating if the event is equal to another object by
+ * determining if isDone, start, end, and description parameters are equal.
+ *
+ * @param o an object that is compared to the task to determine if both are equal
+ * @return true or false if the event is equal or not equal to the object.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof Event) {
+ Event task = (Event) o;
+ boolean isEqualEvents;
+ if (this.meetingLink == null && this.recurrence != null) {
+ isEqualEvents = this.description.equals(task.description) && this.start.equals(task.start)
+ && this.end.equals(task.end) && this.isDone == task.isDone
+ && this.recurrence.equals(task.recurrence) && task.meetingLink == null
+ && this.tags.equals(task.tags);
+ } else if (this.meetingLink != null && this.recurrence == null) {
+ isEqualEvents = this.description.equals(task.description) && this.start.equals(task.start)
+ && this.end.equals(task.end) && this.isDone == task.isDone
+ && this.meetingLink.equals(task.meetingLink) && task.recurrence == null
+ && this.tags.equals(task.tags);
+ } else if (this.meetingLink == null && this.recurrence == null) {
+ isEqualEvents = this.description.equals(task.description) && this.start.equals(task.start)
+ && this.end.equals(task.end) && this.isDone == task.isDone && task.meetingLink == null
+ && task.recurrence == null
+ && this.tags.equals(task.tags);
+ } else {
+ isEqualEvents = this.description.equals(task.description) && this.start.equals(task.start)
+ && this.end.equals(task.end) && this.isDone == task.isDone
+ && this.meetingLink.equals(task.meetingLink) && this.recurrence.equals(task.recurrence)
+ && this.tags.equals(task.tags);
+ }
+ return isEqualEvents;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the string representation of the task in a format to be inputted into a text file for data storage.
+ *
+ * @return the string representation of the task to be saved in a text file.
+ */
+ @Override
+ public String saveFormat() {
+ if (isDone) {
+ return "E | 1 | " + this.getDescription() + " | " + this.start.format(INPUT_DATE_TIME_FORMAT)
+ + " to " + this.end.format(INPUT_DATE_TIME_FORMAT) + " | " + this.getTagsToString();
+ } else {
+ return "E | 0 | " + this.getDescription() + " | " + this.start.format(INPUT_DATE_TIME_FORMAT)
+ + " to " + this.end.format(INPUT_DATE_TIME_FORMAT) + " | " + this.getTagsToString();
+ }
+ }
+
+ @Override
+ public LocalDateTime getDeadline() {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public String getDateTime() {
+ return this.getPeriod();
+ }
+
+ @Override
+ public LocalDateTime getLocalDateTime() {
+ return this.end;
+ }
+
+ public MeetingLink getMeetingLink() {
+ return this.meetingLink;
+ }
+
+ @Override
+ public boolean isTodo() {
+ return false;
+ }
+
+ @Override
+ public boolean isEvent() {
+ return true;
+ }
+
+ @Override
+ public LocalDateTime getStart() {
+ return this.start;
+ }
+
+ @Override
+ public LocalDateTime getEnd() {
+ return this.end;
+ }
+
+ @Override
+ public Optional getLink() {
+ return Optional.ofNullable(this.meetingLink);
+ }
+
+ public String getStartDateTime() {
+ return start.format(INPUT_DATE_TIME_FORMAT);
+ }
+
+ public String getEndDateTime() {
+ return end.format(INPUT_DATE_TIME_FORMAT);
+ }
+
+ @Override
+ public String getType() {
+ return "Event";
+ }
+
+ @Override
+ public Recurrence getRecurrence() {
+ return this.recurrence;
+ }
+
+ public boolean hasRecurrence() {
+ return getRecurrence() != null;
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/Link.java b/src/main/java/seedu/address/model/task/Link.java
new file mode 100644
index 00000000000..a69a15a2ea6
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Link.java
@@ -0,0 +1,79 @@
+package seedu.address.model.task;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+public abstract class Link {
+
+ public static final String MESSAGE_CONSTRAINTS = "Link must be in URL Format: "
+ + "\"^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]\"\n"
+ + "Example: " + "https://nus-sg.zoom.us/j/12350904475?pwd=T0Jw";
+
+ public static final String VALIDATION_REGEX =
+ "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
+
+ /** A brief description of the link. */
+ private final String description;
+
+ /** The url for the link */
+ private final String url;
+
+ /**
+ * Constructs a link with description and url.
+ *
+ * @param description a brief description of the link
+ * @param url the url of the link
+ */
+ public Link(String description, String url) {
+ requireAllNonNull(description, url);
+ this.description = description;
+ checkArgument(isValidUrl(url), MESSAGE_CONSTRAINTS);
+ this.url = url;
+ }
+
+ /**
+ * Returns if a given string is a valid email.
+ */
+ public static boolean isValidUrl(String url) {
+ return url.matches(VALIDATION_REGEX) || url.equals(null);
+ }
+
+ /**
+ * Returns the description of the link as a string.
+ *
+ * @return the description of the link.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Returns the url of the link as a string.
+ *
+ * @return the url of the link.
+ */
+ public String getUrl() {
+ return this.url;
+ }
+
+ /**
+ * Returns a boolean value indicating if the link is equal to
+ * another object by determining if descriptions and url
+ * are equal.
+ *
+ * @param o an object that is compared to the link to determine if both are equal
+ * @return true or false if the object is equal or not equal to the link respectively.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof Link) {
+ Link link = (Link) o;
+ boolean isEqualLink = this.description.equals(link.description) && this.url == link.url;
+ return isEqualLink;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/MeetingLink.java b/src/main/java/seedu/address/model/task/MeetingLink.java
new file mode 100644
index 00000000000..9b722b4eac4
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/MeetingLink.java
@@ -0,0 +1,132 @@
+package seedu.address.model.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class MeetingLink extends Link {
+ /**
+ * The format of inputted dates that the class can accept.
+ */
+ private static final DateTimeFormatter INPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm");
+
+ /**
+ * The format of outputted dates by the class.
+ */
+ private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("MMM d yyyy HHmm");
+
+ /**
+ * The deadline of the task to be completed by.
+ */
+ private LocalDateTime meetingTime;
+
+ /**
+ * Constructs a Meeting Link
+ * with a brief description, url, and meeting time.
+ *
+ * @param description a brief description of the meeting link.
+ * @param url a String in a URL format which specifies the link.
+ * @param meetingTime a String which describes the meeting time.
+ */
+ public MeetingLink(String description, String url, String meetingTime) {
+ super(description, url);
+ this.meetingTime = LocalDateTime.parse(meetingTime, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Constructs a updated Meeting Link for recurring events
+ * with the description, url, and new meeting time.
+ *
+ * @param description a brief description of the meeting link.
+ * @param url a String in a URL format which specifies the link.
+ * @param newTiming a LocalDateTime of the new meeting time.
+ */
+ public MeetingLink(String description, String url, LocalDateTime newTiming) {
+ super(description, url);
+ this.meetingTime = newTiming;
+ }
+
+ /**
+ * Changes meeting time of this meeting.
+ *
+ * @param meetingTime the meeting Time
+ */
+ public void snooze(String meetingTime) {
+ this.meetingTime = LocalDateTime.parse(meetingTime, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Returns a String representation of the meeting time with the format of outputFormatter.
+ *
+ * @return a String representation of the meeting time with the format of outputFormatter.
+ */
+ public String getMeetingTime() {
+ return this.meetingTime.format(OUTPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ /**
+ * Returns a String representation of the Meeting link.
+ * This representation includes the description, meeting time, and url in the format of outputFormatter.
+ *
+ * @return a String representation of the Meeting Link.
+ */
+ @Override
+ public String toString() {
+ return getDescription() + " " + getMeetingTime() + "\n" + getUrl();
+ }
+
+ /**
+ * Returns a boolean value indicating if the task is equal to
+ * another object by determining if descriptions, deadline, and isDone parameters
+ * are equal.
+ *
+ * @param o an object that is compared to the task to determine if both are equal
+ * @return true or false if the task is equal or not equal to the object respectively.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof MeetingLink) {
+ MeetingLink link = (MeetingLink) o;
+ boolean isEqual = this.getDescription().equals(link.getDescription())
+ && this.getMeetingTime().equals(link.getMeetingTime()) && this.getUrl().equals(link.getUrl());
+ return isEqual;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the string representation of the task in a format to be inputted into a text file for data storage.
+ *
+ * @return the string representation of the task to be saved in a text file.
+ */
+ public String saveTimeFormat() {
+ return this.meetingTime.format(INPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ public LocalDateTime getLocalDateTime() {
+ return this.meetingTime;
+ }
+
+ @Override
+ public String getDescription() {
+ if (super.getDescription().equals("No meeting link")) {
+ return super.getDescription();
+ } else {
+ return this.getDescriptionDateTime();
+ }
+ }
+
+ public String getDateTime() {
+ return this.meetingTime.format(INPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ public String getRawDescription() {
+ return super.getDescription();
+ }
+
+ public String getDescriptionDateTime() {
+ return super.getDescription() + " (on: " + getMeetingTime() + ")";
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/Recurrence.java b/src/main/java/seedu/address/model/task/Recurrence.java
new file mode 100644
index 00000000000..bdef6858e70
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Recurrence.java
@@ -0,0 +1,73 @@
+package seedu.address.model.task;
+
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Contains recurring properties of a task.
+ */
+public class Recurrence {
+ public static final String DAY = "day";
+ public static final String WEEK = "week";
+ public static final String MONTH = "month";
+ public static final String YEAR = "year";
+
+ private final Integer value;
+ private final ChronoUnit chronoUnit;
+
+ /**
+ * Converts the unit from a String to a ChronoUnit type, which is used later
+ * for LocalDateTime operations.
+ *
+ * @param value magnitude of the recurrence
+ * @param timePeriod unit of the magnitude (day/week/month/year)
+ */
+ public Recurrence(Integer value, String timePeriod) {
+ this.value = value;
+
+ assert this.value > 0 : "recurrence should not be 0";
+
+ if (timePeriod.equals(DAY)) {
+ this.chronoUnit = ChronoUnit.DAYS;
+ } else if (timePeriod.equals(WEEK)) {
+ this.chronoUnit = ChronoUnit.WEEKS;
+ } else if (timePeriod.equals(MONTH)) {
+ this.chronoUnit = ChronoUnit.MONTHS;
+ } else if (timePeriod.equals(YEAR)) {
+ this.chronoUnit = ChronoUnit.YEARS;
+ } else {
+ this.chronoUnit = null;
+ }
+ }
+
+ public Integer getValue() {
+ return this.value;
+ }
+
+ public ChronoUnit getChronoUnit() {
+ return this.chronoUnit;
+ }
+
+ public String getUnit() {
+ String rawChronoUnitToString = this.chronoUnit.toString();
+ String unit = rawChronoUnitToString.substring(0, rawChronoUnitToString.length() - 1);
+ unit += "(s)";
+ return unit;
+ }
+
+ @Override
+ public boolean equals (Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof Recurrence) {
+ Recurrence recur = (Recurrence) o;
+ return this.value.equals(recur.value) && this.chronoUnit.equals(recur.chronoUnit);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[" + this.value + "," + this.chronoUnit.toString() + "]";
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java
new file mode 100644
index 00000000000..2f28d23bf6b
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Task.java
@@ -0,0 +1,209 @@
+package seedu.address.model.task;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.model.tag.Tag;
+
+public abstract class Task {
+ /** Maximum length for description supported by Lifebook */
+ private static final int MAX_DESCRIPTION_LENGTH = 30;
+
+ /** Message for denoting max description length */
+ public static final String MESSAGE_CONSTRAINTS = "Description must have less than "
+ + MAX_DESCRIPTION_LENGTH + " characters.";
+
+
+ /** A brief description of the task. */
+ protected String description;
+
+ /** Tracks the completion of the task */
+ protected boolean isDone;
+
+ /** Optional link for documents and online meetings */
+ protected Link link;
+
+ /** Optional tag of the task**/
+ protected final Set tags = new HashSet<>();
+
+ protected Recurrence recurrence;
+
+ /**
+ * Constructs a task that has not been completed with a description.
+ *
+ * @param description a brief description of the task
+ * @param tags a set of tags attached to the task.
+ */
+ public Task(String description, Set tags) {
+ assert description != null;
+ assert tags != null;
+ checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS);
+ this.description = description;
+ this.isDone = false;
+ this.tags.addAll(tags);
+ }
+
+ /**
+ * Constructs a task, which may or may not have been completed, with a description.
+ *
+ * @param isDone indicates if the task has been completed.
+ * @param description a brief description of the task.
+ * @param tags a set of tags attached to the task.
+ */
+ public Task(boolean isDone, String description, Set tags) {
+ assert description != null;
+ assert tags != null;
+ checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS);
+ this.description = description;
+ this.isDone = isDone;
+ this.tags.addAll(tags);
+ }
+
+ /**
+ * Constructs a task, which have not been completed, with a description.
+ *
+ * @param description a brief description of the task.
+ */
+ public Task(String description) {
+ assert description != null;
+ checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS);
+ this.description = description;
+ this.isDone = false;
+ }
+ /**
+ * Returns the status icon of the task.
+ * Returns tick symbol when task is indicated as done.
+ * Returns X symbol when task is not indicated as done.
+ *
+ * @return the status icon of the task.
+ */
+ public String getStatusIcon() {
+ return (isDone ? "\u2713" : "\u2718");
+ }
+
+ /**
+ * Returns the description of the task as a string.
+ *
+ * @return the description of the task.
+ */
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * Indicates that the task has been completed.
+ *
+ */
+ public void markAsDone() {
+ this.isDone = true;
+ }
+ public boolean getStatus() {
+ return isDone;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getTags() {
+ return Collections.unmodifiableSet(tags);
+ }
+
+ public String getTagsToString() {
+ StringBuilder builder = new StringBuilder();
+ getTags().forEach(builder::append);
+ return builder.toString();
+ }
+
+ /**
+ * Returns a boolean value indicating if the task is equal to
+ * another object by determining if descriptions and isDone parameters
+ * are equal.
+ *
+ * @param o an object that is compared to the task to determine if both are equal
+ * @return true or false if the object is equal or not equal to the task respectively.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof Task) {
+ Task task = (Task) o;
+ boolean isEqualTask = this.description.equals(task.description) && this.isDone == task.isDone;
+ return isEqualTask;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isSameTask(Task task) {
+ return this.equals(task);
+ }
+
+ /** Tracks if there is a link present in this task */
+ public boolean hasLink() {
+ if (this.link != null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the string representation of task, which includes the status icon
+ * and description.
+ *
+ * @return the string representation of the task.
+ */
+ @Override
+ public String toString() {
+ return "[" + getStatusIcon() + "]" + getDescription() + " " + getTagsToString();
+ }
+
+ /**
+ * Returns the string representation of the task in a format to be inputted into a text file for data storage.
+ *
+ * @return the string representation of the task to be saved in a text file.
+ */
+ public String saveFormat() {
+ if (isDone) {
+ return "T | 1 | " + this.getDescription() + " | " + getTagsToString();
+ } else {
+ return "T | 0 | " + this.getDescription() + " | " + getTagsToString();
+ }
+ }
+
+ /**
+ * Indicates if task is recurring.
+ * @return true if it is recurring, and false otherwise.
+ */
+ public boolean isRecurring() {
+ System.out.println(this.getRecurrence() != null);
+ return this.getRecurrence() != null;
+ }
+
+ /**
+ * Indicates if description is valid.
+ * @return true if it is valid, and false otherwise.
+ */
+ public static boolean isValidDescription(String description) {
+ return description.length() <= MAX_DESCRIPTION_LENGTH;
+ }
+
+ public abstract LocalDateTime getDeadline();
+ public abstract LocalDateTime getStart();
+ public abstract LocalDateTime getEnd();
+ public abstract String getDateTime();
+ public abstract Optional getLink();
+ public abstract LocalDateTime getLocalDateTime();
+ public abstract boolean isTodo();
+ public abstract boolean isEvent();
+ public abstract String getType();
+ public abstract Recurrence getRecurrence();
+}
diff --git a/src/main/java/seedu/address/model/task/TaskDateComparator.java b/src/main/java/seedu/address/model/task/TaskDateComparator.java
new file mode 100644
index 00000000000..0d01a76d386
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskDateComparator.java
@@ -0,0 +1,17 @@
+package seedu.address.model.task;
+
+import java.util.Comparator;
+
+public class TaskDateComparator implements Comparator {
+
+ @Override
+ public int compare(Task o1, Task o2) {
+ if (o1.equals(o2)) {
+ return 0;
+ } else if (o1.getStart().compareTo(o2.getStart()) == 0) {
+ return o1.getDescription().compareTo(o2.getDescription());
+ } else {
+ return o1.getStart().compareTo(o2.getStart());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/TaskMatchesFindKeywordPredicate.java b/src/main/java/seedu/address/model/task/TaskMatchesFindKeywordPredicate.java
new file mode 100644
index 00000000000..dca3533f5c7
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskMatchesFindKeywordPredicate.java
@@ -0,0 +1,64 @@
+package seedu.address.model.task;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Task}'s {@code Tag} and {@code Description} matches the tag keyword given.
+ */
+public class TaskMatchesFindKeywordPredicate implements Predicate {
+ private final List keywordDescription;
+ private final String keywordTag;
+
+ /**
+ * Construct a predicate to match keyword tag to task's tag
+ * @param keywordTag the keyword for the tag
+ */
+ public TaskMatchesFindKeywordPredicate(String keywordTag) {
+ // make sure keyword is only one word
+ assert !keywordTag.contains("\\s+");
+ this.keywordTag = keywordTag;
+ this.keywordDescription = new ArrayList<>();
+ }
+
+ /**
+ * Construct a predicate to match keyword description to task's description
+ * @param keywordDescription the keyword for the description
+ */
+ public TaskMatchesFindKeywordPredicate(List keywordDescription) {
+ this.keywordDescription = keywordDescription;
+ this.keywordTag = "";
+ }
+
+ /**
+ * Construct a predicate to match keyword description and tag to task's description and tag
+ * @param keywordDescription the keyword for the description
+ * @param keywordTag the keyword for the tag
+ */
+ public TaskMatchesFindKeywordPredicate(List keywordDescription, String keywordTag) {
+ // make sure keyword is only one word
+ assert !keywordTag.contains("\\s+");
+ this.keywordTag = keywordTag;
+ this.keywordDescription = keywordDescription;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ boolean matchName = keywordDescription.size() == 0 || keywordDescription.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getDescription(), keyword));
+ boolean matchTag = keywordTag == "" || task.getTags().stream()
+ .anyMatch(keyword -> this.keywordTag.trim().toLowerCase().equals(keyword.tagName.trim().toLowerCase()));
+ return matchName && matchTag;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskMatchesFindKeywordPredicate // instanceof handles nulls
+ && keywordDescription.equals(((TaskMatchesFindKeywordPredicate) other).keywordDescription) // state check
+ && keywordTag.equals(((TaskMatchesFindKeywordPredicate) other).keywordTag));
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/TaskTypeMatchesKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskTypeMatchesKeywordsPredicate.java
new file mode 100644
index 00000000000..20f56d8b313
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/TaskTypeMatchesKeywordsPredicate.java
@@ -0,0 +1,31 @@
+package seedu.address.model.task;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class TaskTypeMatchesKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public TaskTypeMatchesKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Task task) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getType(), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TaskTypeMatchesKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((TaskTypeMatchesKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/task/Todo.java b/src/main/java/seedu/address/model/task/Todo.java
new file mode 100644
index 00000000000..539a830b03e
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/Todo.java
@@ -0,0 +1,439 @@
+package seedu.address.model.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.model.tag.Tag;
+
+public class Todo extends Task {
+ /**
+ * The format of inputted dates that the class can accept.
+ */
+ private static final DateTimeFormatter INPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm");
+
+ /**
+ * The format of outputted dates by the class.
+ */
+ private static final DateTimeFormatter OUTPUT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("MMM d yyyy HHmm");
+
+ /**
+ * The deadline of the task to be completed by.
+ */
+ private LocalDateTime deadline;
+
+ /**The collaborative link url. */
+ private CollaborativeLink collaborativeLink;
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ */
+ public Todo(boolean isDone, String description, String deadline, Set tags) {
+ super(description, tags);
+ this.isDone = isDone;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ */
+ public Todo(boolean isDone, String description, String deadline, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ this.isDone = isDone;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ */
+ public Todo(boolean isDone, String description, String deadline, CollaborativeLink link, Set tags) {
+ super(description, tags);
+ this.isDone = isDone;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ this.collaborativeLink = link;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ */
+ public Todo(boolean isDone, String description, String deadline,
+ CollaborativeLink link, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ this.isDone = isDone;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ this.collaborativeLink = link;
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, String deadline, Set tags) {
+ super(description, tags);
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param collaborativeLink the collaborative link of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, String deadline, CollaborativeLink collaborativeLink, Set tags) {
+ super(description, tags);
+ assert deadline != null;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ this.collaborativeLink = collaborativeLink;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param collaborativeLink the collaborative link of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, LocalDateTime deadline, CollaborativeLink collaborativeLink, Set tags) {
+ super(description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.collaborativeLink = collaborativeLink;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, String deadline, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ assert deadline != null;
+ this.deadline = LocalDateTime.parse(deadline, INPUT_DATE_TIME_FORMAT);
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a LocalDateTime in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, LocalDateTime deadline, Recurrence recurrence, Set tags) {
+ super(description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.recurrence = recurrence;
+ }
+
+
+ /**
+ * Constructs a task that has not been completed
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param isDone indicates if the deadline has been completed.
+ * @param description a brief description of the deadline.
+ * @param deadline a LocalDateTime in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ * @param link the Collaborative Link of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(boolean isDone, String description, LocalDateTime deadline,
+ Recurrence recurrence, CollaborativeLink link, Set tags) {
+ super(isDone, description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.recurrence = recurrence;
+ this.collaborativeLink = link;
+ }
+
+ /**
+ * Constructs a task, which may or may not have been completed,
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param isDone indicates if the deadline has been completed.
+ * @param description a brief description of the deadline.
+ * @param deadline a String in a specific format (inputFormatter) which specifies a date.
+ * @param recurrence the recurrence of todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(boolean isDone, String description, LocalDateTime deadline, Recurrence recurrence, Set tags) {
+ super(isDone, description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * Constructs a task, which may or may not have been completed,
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param isDone indicates if the deadline has been completed.
+ * @param description a brief description of the deadline.
+ * @param deadline a date and time specifies a deadline.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(boolean isDone, String description, LocalDateTime deadline, Set tags) {
+ super(isDone, description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ }
+
+ /**
+ * Constructs a task, which may or may not have been completed,
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param isDone indicates if the deadline has been completed.
+ * @param description a brief description of the deadline.
+ * @param deadline a date and time specifies a deadline.
+ * @param link a Collaborative Link for the todo.
+ * @param tags a set of tags attached to the todo
+ */
+ public Todo(boolean isDone, String description, LocalDateTime deadline, CollaborativeLink link, Set tags) {
+ super(isDone, description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.collaborativeLink = link;
+ }
+
+ /**
+ * Constructs a task, which may or may not have been completed,
+ * with a brief description and deadline for the task to be completed by.
+ *
+ * @param description a brief description of the deadline.
+ * @param deadline a date and time specifies a deadline.
+ * @param recurrence the recurrence of todo.
+ * @param link a Collaborative Link for the todo.
+ * @param tags a set of tags attached to the todo.
+ */
+ public Todo(String description, LocalDateTime deadline,
+ Recurrence recurrence, CollaborativeLink link, Set tags) {
+ super(description, tags);
+ assert deadline != null;
+ this.deadline = deadline;
+ this.recurrence = recurrence;
+ this.collaborativeLink = link;
+ }
+ /**
+ * Returns a String representation of the deadline with the format of outputFormatter.
+ *
+ * @return a String representation of the deadline with the format of outputFormatter.
+ */
+ public String deadlineToString() {
+ return this.deadline.format(OUTPUT_DATE_TIME_FORMAT).toString();
+ }
+ @Override
+ public void markAsDone() {
+ this.isDone = true;
+ }
+ /**
+ * Returns a String representation of the task.
+ * This representation includes the status icon, description, and deadline in the format of outputFormatter.
+ *
+ * @return a String representation of the task.
+ */
+ @Override
+ public String toString() {
+ if (this.recurrence == null && this.collaborativeLink != null) {
+ return "[" + getStatusIcon() + "] " + getDescription()
+ + " (by: " + deadlineToString() + ") " + "Link: " + this.collaborativeLink.toString() + " "
+ + getTagsToString();
+ } else if (this.recurrence != null && this.collaborativeLink == null) {
+ return "[" + getStatusIcon() + "] " + getDescription()
+ + " (by: " + deadlineToString() + ") " + "Recurrence: " + this.recurrence.toString() + " "
+ + getTagsToString();
+ } else if (this.recurrence == null && this.collaborativeLink == null) {
+ return "[" + getStatusIcon() + "] " + getDescription()
+ + " (by: " + deadlineToString() + ") " + getTagsToString();
+ } else {
+ return "[" + getStatusIcon() + "] " + getDescription()
+ + " (by: " + deadlineToString() + ") " + "Link: " + this.collaborativeLink.toString() + " "
+ + "Recurrence: " + this.recurrence.toString() + " " + getTagsToString();
+ }
+ }
+
+ /**
+ * Returns a boolean value indicating if the task is equal to
+ * another object by determining if descriptions, deadline, and isDone parameters
+ * are equal.
+ *
+ * @param o an object that is compared to the task to determine if both are equal
+ * @return true or false if the task is equal or not equal to the object respectively.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof Todo) {
+ Todo task = (Todo) o;
+ boolean isEqualDeadlines;
+ if (this.recurrence == null && this.collaborativeLink != null) {
+ isEqualDeadlines = this.description.equals(task.description)
+ && this.deadline.equals(task.deadline) && this.isDone == task.isDone
+ && this.collaborativeLink.equals(task.collaborativeLink) && task.recurrence == null
+ && this.tags.equals(task.tags);
+ } else if (this.recurrence != null && this.collaborativeLink == null) {
+ isEqualDeadlines = this.description.equals(task.description)
+ && this.deadline.equals(task.deadline) && this.isDone == task.isDone
+ && this.recurrence.equals(task.recurrence) && task.collaborativeLink == null
+ && this.tags.equals(task.tags);
+ } else if (this.recurrence == null && this.collaborativeLink == null) {
+ System.out.println("masuk else if");
+ System.out.println(this);
+ System.out.println(task);
+ isEqualDeadlines = this.description.equals(task.description)
+ && this.deadline.equals(task.deadline) && this.isDone == task.isDone
+ && task.recurrence == null && task.collaborativeLink == null
+ && this.tags.equals(task.tags);
+ System.out.println(isEqualDeadlines);
+ } else {
+ isEqualDeadlines = this.description.equals(task.description)
+ && this.deadline.equals(task.deadline) && this.isDone == task.isDone
+ && this.recurrence.equals(task.recurrence)
+ && this.collaborativeLink.equals(task.collaborativeLink)
+ && this.tags.equals(task.tags);
+ }
+ return isEqualDeadlines;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if both todos of the same description have at least one other identity field that is the same.
+ * This defines a weaker notion of equality between two todos.
+ */
+ public boolean isSameTodo(Todo otherTodo) {
+ if (otherTodo == this) {
+ return true;
+ }
+
+ return otherTodo != null
+ && otherTodo.getDescription().equals(getDescription())
+ && (otherTodo.getDescriptionDateTime().equals(getDescriptionDateTime()));
+ }
+
+ /**
+ * Returns the string representation of the task in a format to be inputted into a text file for data storage.
+ *
+ * @return the string representation of the task to be saved in a text file.
+ */
+ @Override
+ public String saveFormat() {
+ if (isDone) {
+ return "D | 1 | " + this.getDescription() + " | "
+ + getInputDate()
+ + " | " + getTagsToString();
+ } else {
+ return "D | 0 | " + this.getDescription() + " | " + getInputDate()
+ + " | " + getTagsToString();
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public String getDateTime() {
+ return deadlineToString();
+ }
+
+ public String getInputDate() {
+ return this.deadline.format(INPUT_DATE_TIME_FORMAT).toString();
+ }
+
+ @Override
+ public LocalDateTime getLocalDateTime() {
+ return this.deadline;
+ }
+
+ @Override
+ public boolean isTodo() {
+ return true;
+ }
+
+ @Override
+ public boolean isEvent() {
+ return false;
+ }
+
+ public String getDescriptionDateTime() {
+ return this.description + " (by: " + getDateTime() + ")";
+ }
+
+ @Override
+ public LocalDateTime getDeadline() {
+ return this.deadline;
+ }
+
+ @Override
+ public LocalDateTime getStart() {
+ return this.deadline;
+ }
+
+ @Override
+ public LocalDateTime getEnd() {
+ return null;
+ }
+
+ @Override
+ public String getType() {
+ return "Todo";
+ }
+
+ @Override
+ public Optional getLink() {
+ return Optional.ofNullable(this.collaborativeLink);
+ }
+
+ public CollaborativeLink getCollaborativeLink() {
+ return this.collaborativeLink;
+ }
+
+ @Override
+ public Recurrence getRecurrence() {
+ return this.recurrence;
+ }
+
+ public boolean hasRecurrence() {
+ return getRecurrence() != null;
+ }
+}
diff --git a/src/main/java/seedu/address/model/task/UniqueTaskList.java b/src/main/java/seedu/address/model/task/UniqueTaskList.java
new file mode 100644
index 00000000000..4efdc2d1085
--- /dev/null
+++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java
@@ -0,0 +1,158 @@
+package seedu.address.model.task;
+
+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.Person;
+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 UniqueTaskList 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(Task toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameTask);
+ }
+
+ /**
+ * Adds a person to the list.
+ * The person must not already exist in the list.
+ */
+ public void add(Task 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 setTask(Task target, Task editedPerson) {
+ requireAllNonNull(target, editedPerson);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new PersonNotFoundException();
+ }
+
+ if (!target.isSameTask(editedPerson) && contains(editedPerson)) {
+ throw new DuplicatePersonException();
+ }
+
+ internalList.set(index, editedPerson);
+ }
+
+ /**
+ * Replaces the person {@code target} in the list with an identical task marked as done.
+ * {@code target} must exist in the list.
+ *
+ * @returns AddCommand if task is recurring.
+ */
+ public void markAsDone(Task target) {
+ requireAllNonNull(target);
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new PersonNotFoundException();
+ }
+ target.markAsDone();
+ internalList.set(index, target);
+ }
+
+ /**
+ * Removes the equivalent person from the list.
+ * The person must exist in the list.
+ */
+ public void remove(Task toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new PersonNotFoundException();
+ }
+ }
+
+ public void setTasks(UniqueTaskList 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 setTasks(List tasks) {
+ requireAllNonNull(tasks);
+ if (!tasksAreUnique(tasks)) {
+ throw new DuplicatePersonException();
+ }
+
+ internalList.setAll(tasks);
+ }
+
+
+ /**
+ * 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 UniqueTaskList // instanceof handles nulls
+ && internalList.equals(((UniqueTaskList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code persons} contains only unique persons.
+ */
+ private boolean tasksAreUnique(List tasks) {
+ for (int i = 0; i < tasks.size() - 1; i++) {
+ for (int j = i + 1; j < tasks.size(); j++) {
+ if (tasks.get(i).isSameTask(tasks.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ public boolean isEmpty() {
+ return internalList.isEmpty();
+ }
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..6d197dccb29 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -6,12 +6,18 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
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;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -39,6 +45,30 @@ public static Person[] getSamplePersons() {
getTagSet("colleagues"))
};
}
+ public static Task[] getSampleTasks() {
+ return new Task[] {
+ new Todo("Finish assignment", "19-11-2020 2359", getTagSet("CS2100")),
+ new Todo("Finish tutorial worksheet", "16-11-2020 2359", getTagSet("CS2100")),
+ new Todo("Complete next iteration", "22-11-2020 2359", getTagSet("CS2103T")),
+ new Event("Attend group meeting", "20-11-2020 1000", "20-11-2020 1200",
+ new MeetingLink("Group meeting", "https://www.facebook.com", "20-11-2020 1000"),
+ getTagSet("CS2103T")),
+ new Event("Attend lecture", "17-11-2020 1200", "17-11-2020 1300", getTagSet("CS2100")),
+ new Event("Meet friends for lunch", "17-11-2020 1300", "17-11-2020 1400", getTagSet("Friends")),
+ new Todo("Finish assignment", "19-12-2020 2359", getTagSet("CS2105")),
+ new Event("Attend group meeting", "20-12-2020 1000", "20-12-2020 1200",
+ new MeetingLink("Friends meeting", "https://www.example.com", "20-12-2020 1000"),
+ getTagSet("CS2103T"))
+ };
+ }
+
+ public static ReadOnlyTaskList getSampleTaskList() {
+ TaskList sampleTl = new TaskList();
+ for (Task sampleTask: getSampleTasks()) {
+ sampleTl.addTask(sampleTask);
+ }
+ return sampleTl;
+ }
public static ReadOnlyAddressBook getSampleAddressBook() {
AddressBook sampleAb = new AddressBook();
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEvent.java b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java
new file mode 100644
index 00000000000..42238ec0d70
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java
@@ -0,0 +1,128 @@
+package seedu.address.storage;
+
+import java.time.LocalDateTime;
+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.tag.Tag;
+import seedu.address.model.task.Event;
+import seedu.address.model.task.MeetingLink;
+import seedu.address.model.task.Recurrence;
+import seedu.address.model.task.Task;
+
+public class JsonAdaptedEvent extends JsonAdaptedTask {
+ private final LocalDateTime start;
+ private final LocalDateTime end;
+ private final String linkDesc;
+ private final String linkUrl;
+ private final String linkTime;
+ private final Recurrence recurrence;
+ private final List tagged = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedEvent} with the given Event details.
+ */
+ @JsonCreator
+ public JsonAdaptedEvent(@JsonProperty("description") String description, @JsonProperty("isDone") Boolean isDone,
+ @JsonProperty("start") LocalDateTime start, @JsonProperty("end") LocalDateTime end,
+ @JsonProperty("linkDesc") String linkDesc, @JsonProperty("linkUrl") String url,
+ @JsonProperty("linkTime") String linkTime,
+ @JsonProperty("recurrence") JsonAdaptedRecurrence recurrence,
+ @JsonProperty("tagged") List tagged) {
+ super(description, isDone);
+ this.start = start;
+ this.end = end;
+ this.linkDesc = linkDesc;
+ this.linkUrl = url;
+ this.linkTime = linkTime;
+ if (recurrence != null) {
+ this.recurrence = recurrence.toModelType();
+ } else {
+ this.recurrence = null;
+ }
+ if (tagged != null) {
+ this.tagged.addAll(tagged);
+ }
+ }
+
+ /**
+ * Converts a given {@code Event} into this class for Jackson use.
+ */
+ public JsonAdaptedEvent(Task source) {
+ super(source);
+ start = source.getStart();
+ end = source.getEnd();
+ if (source.getLink().isPresent()) {
+ linkDesc = source.getLink().get().getDescription().split(" ", 2)[0];
+ linkUrl = source.getLink().get().getUrl();
+ linkTime = ((Event) source).getMeetingLink().saveTimeFormat();
+ } else {
+ linkDesc = null;
+ linkUrl = null;
+ linkTime = null;
+ }
+ recurrence = source.getRecurrence();
+ tagged.addAll(source.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted person object into the model's {@code Event} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted event.
+ */
+ @Override
+ public Task toModelType() throws IllegalValueException {
+ final Set eventTags = new HashSet<>();
+ for (JsonAdaptedTag tag : tagged) {
+ eventTags.add(tag.toModelType());
+ }
+ if (description == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "description"));
+ }
+ final String modelDescription = description;
+ if (isDone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "isDone"));
+ }
+ final boolean modelIsDone = isDone;
+
+ if (start == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "start date and time"));
+ }
+
+ final LocalDateTime modelStart = start;
+
+ if (end == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "end date and time"));
+ }
+
+ final LocalDateTime modelEnd = end;
+ final Recurrence modelRecurrence = recurrence;
+ final Set modelTags = new HashSet<>(eventTags);
+
+ if (linkUrl == null || linkDesc == null || linkTime == null) {
+ if (modelRecurrence == null) {
+ return new Event(modelIsDone, modelDescription, modelStart, modelEnd, modelTags);
+ } else {
+ return new Event(modelIsDone, modelDescription, modelStart, modelEnd, modelRecurrence, modelTags);
+ }
+ } else {
+ if (modelRecurrence == null) {
+ return new Event(modelIsDone, modelDescription, modelStart,
+ modelEnd, new MeetingLink(linkDesc, linkUrl, linkTime), modelTags);
+ } else {
+ return new Event(modelIsDone, modelDescription,
+ modelStart, modelEnd, modelRecurrence, new MeetingLink(linkDesc, linkUrl, linkTime), modelTags);
+ }
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedRecurrence.java b/src/main/java/seedu/address/storage/JsonAdaptedRecurrence.java
new file mode 100644
index 00000000000..56c66d704d6
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedRecurrence.java
@@ -0,0 +1,52 @@
+package seedu.address.storage;
+
+import java.time.temporal.ChronoUnit;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.model.task.Recurrence;
+
+public class JsonAdaptedRecurrence {
+ private final Integer value;
+ private final ChronoUnit unit;
+
+ /**
+ * Constructs a {code JsonAdaptedRecurrence} with the given {@code recurrence details}.
+ */
+ @JsonCreator
+ public JsonAdaptedRecurrence(@JsonProperty("value") Integer value, @JsonProperty("chronoUnit") ChronoUnit unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+
+ /**
+ * Converts a given {@code Recurrence} into this class for Jackson use.
+ */
+ public JsonAdaptedRecurrence(Recurrence source) {
+ value = source.getValue();
+ unit = source.getChronoUnit();
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted tag object into the model's {@code Recurrence} object.
+ *
+ */
+ public Recurrence toModelType() {
+ final Integer modelValue = value;
+ final String modelUnit;
+
+ if (unit.equals(ChronoUnit.DAYS)) {
+ modelUnit = "day";
+ } else if (unit.equals(ChronoUnit.WEEKS)) {
+ modelUnit = "week";
+ } else if (unit.equals(ChronoUnit.MONTHS)) {
+ modelUnit = "month";
+ } else if (unit.equals(ChronoUnit.YEARS)) {
+ modelUnit = "year";
+ } else {
+ modelUnit = "";
+ }
+ return new Recurrence(modelValue, modelUnit);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java
new file mode 100644
index 00000000000..965af0517ec
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java
@@ -0,0 +1,54 @@
+package seedu.address.storage;
+
+import static com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY;
+import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.task.Task;
+
+/**
+ * Jackson-friendly version of {@link Task}.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = NAME, include = PROPERTY)
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = JsonAdaptedTodo.class, name = "JsonAdaptedTodo"),
+
+ @JsonSubTypes.Type(value = JsonAdaptedEvent.class, name = "JsonAdaptedEvent") }
+)
+public abstract class JsonAdaptedTask {
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!";
+
+ protected final String description;
+ protected final Boolean isDone;
+
+ /**
+ * Constructs a {@code JsonAdaptedTask} with the given Task details.
+ */
+ @JsonCreator
+ public JsonAdaptedTask(@JsonProperty("description") String description, @JsonProperty("isDone") Boolean isDone) {
+ this.description = description;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Converts a given {@code Task} into this class for Jackson use.
+ */
+ public JsonAdaptedTask(Task source) {
+ description = source.getDescription();
+ isDone = source.getStatus();
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted person object into the model's {@code Task} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted Task.
+ */
+ public abstract Task toModelType() throws IllegalValueException;
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTodo.java b/src/main/java/seedu/address/storage/JsonAdaptedTodo.java
new file mode 100644
index 00000000000..5118157e25b
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTodo.java
@@ -0,0 +1,116 @@
+package seedu.address.storage;
+
+import java.time.LocalDateTime;
+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.tag.Tag;
+import seedu.address.model.task.CollaborativeLink;
+import seedu.address.model.task.Recurrence;
+import seedu.address.model.task.Task;
+import seedu.address.model.task.Todo;
+
+/**
+ * Jackson-friendly version of {@link Todo}.
+ */
+public class JsonAdaptedTodo extends JsonAdaptedTask {
+
+ private final LocalDateTime deadline;
+ private final String linkDesc;
+ private final String linkUrl;
+ private final Recurrence recurrence;
+ private final Set tagged = new HashSet<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedTodo} with the given Todo details.
+ */
+ @JsonCreator
+ public JsonAdaptedTodo(@JsonProperty("description") String description, @JsonProperty("isDone") Boolean isDone,
+ @JsonProperty("deadline") LocalDateTime deadline, @JsonProperty("linkDesc") String linkDesc,
+ @JsonProperty("linkUrl") String url,
+ @JsonProperty("recurrence") JsonAdaptedRecurrence recurrence,
+ @JsonProperty("tagged") List tagged) {
+ super(description, isDone);
+ this.deadline = deadline;
+ this.linkDesc = linkDesc;
+ this.linkUrl = url;
+ if (recurrence != null) {
+ this.recurrence = recurrence.toModelType();
+ } else {
+ this.recurrence = null;
+ }
+ if (tagged != null) {
+ this.tagged.addAll(tagged);
+ }
+ }
+
+ /**
+ * Converts a given {@code Todo} into this class for Jackson use.
+ */
+ public JsonAdaptedTodo(Task source) {
+ super(source);
+ deadline = source.getDeadline();
+ if (source.getLink().isPresent()) {
+ linkDesc = source.getLink().get().getDescription();
+ linkUrl = source.getLink().get().getUrl();
+ } else {
+ linkDesc = null;
+ linkUrl = null;
+ }
+ recurrence = source.getRecurrence();
+ tagged.addAll(source.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted todo object into the model's {@code Todo} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted Todo.
+ */
+ @Override
+ public Task toModelType() throws IllegalValueException {
+ final Set personTags = new HashSet<>();
+ for (JsonAdaptedTag tag : tagged) {
+ personTags.add(tag.toModelType());
+ }
+ if (description == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "description"));
+ }
+ final String modelDescription = description;
+ if (isDone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "isDone"));
+ }
+ final boolean modelIsDone = isDone;
+
+ if (deadline == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "deadline"));
+ }
+
+ final LocalDateTime modelDeadline = deadline;
+ final Recurrence modelRecurrence = recurrence;
+ final Set modelTags = new HashSet<>(personTags);
+
+ if (linkUrl == null || linkDesc == null) {
+ if (modelRecurrence == null) {
+ return new Todo(modelIsDone, modelDescription, modelDeadline, modelTags);
+ } else {
+ return new Todo(modelIsDone, modelDescription, modelDeadline, modelRecurrence, modelTags);
+ }
+ } else {
+ if (modelRecurrence == null) {
+ return new Todo(modelIsDone, modelDescription, modelDeadline,
+ new CollaborativeLink(linkDesc, linkUrl), modelTags);
+ } else {
+ return new Todo(modelIsDone, modelDescription, modelDeadline,
+ modelRecurrence, new CollaborativeLink(linkDesc, linkUrl), modelTags);
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableTaskList.java b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java
new file mode 100644
index 00000000000..223d26b9b8c
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonSerializableTaskList.java
@@ -0,0 +1,58 @@
+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.ReadOnlyTaskList;
+import seedu.address.model.TaskList;
+import seedu.address.model.task.Task;
+
+/**
+ * An Immutable TaskList that is serializable to JSON format.
+ */
+@JsonRootName(value = "tasklist")
+public class JsonSerializableTaskList {
+ public static final String MESSAGE_DUPLICATE_PERSON = "Task list contains duplicate task(s).";
+ private final List tasks = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableTaskList} with the given tasks.
+ */
+ @JsonCreator
+ public JsonSerializableTaskList(@JsonProperty("tasks") List extends JsonAdaptedTask> tasks) {
+ this.tasks.addAll(tasks);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyTaskList} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableTaskList}.
+ */
+ public JsonSerializableTaskList(ReadOnlyTaskList source) {
+ tasks.addAll(source.getTaskList().stream().map(x -> x.isTodo()
+ ? new JsonAdaptedTodo(x) : new JsonAdaptedEvent(x)).collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Task List into the model's {@code TaskList} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public TaskList toModelType() throws IllegalValueException {
+ TaskList taskList = new TaskList();
+ for (JsonAdaptedTask jsonAdaptedTask : tasks) {
+ Task task = jsonAdaptedTask.toModelType();
+ if (taskList.hasTask(task)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
+ }
+ taskList.addTask(task);
+ }
+ return taskList;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonTaskListStorage.java b/src/main/java/seedu/address/storage/JsonTaskListStorage.java
new file mode 100644
index 00000000000..f206c20ab5b
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonTaskListStorage.java
@@ -0,0 +1,82 @@
+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.ReadOnlyTaskList;
+
+/**
+ * A class to access TaskList data stored as a json file on the hard disk.
+ */
+public class JsonTaskListStorage implements TaskListStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonTaskListStorage.class);
+
+ private Path filePath;
+
+ public JsonTaskListStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ @Override
+ public Path getTaskListFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readTaskList() throws DataConversionException, IOException {
+ return readTaskList(filePath);
+ }
+
+ /**
+ * Similar to {@link #readTaskList()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataConversionException if the file is not in the correct format.
+ */
+ @Override
+ public Optional readTaskList(Path filePath) throws DataConversionException, IOException {
+ requireNonNull(filePath);
+
+ Optional jsonTaskList = JsonUtil.readJsonFile(
+ filePath, JsonSerializableTaskList.class);
+ if (!jsonTaskList.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonTaskList.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataConversionException(ive);
+ }
+ }
+
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList) throws IOException {
+ saveTaskList(taskList, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveTaskList(ReadOnlyTaskList)}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException {
+ requireNonNull(taskList);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableTaskList(taskList), filePath);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index beda8bd9f11..0a62b25c80d 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -6,13 +6,14 @@
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, TaskListStorage {
@Override
Optional readUserPrefs() throws DataConversionException, IOException;
@@ -29,4 +30,10 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
@Override
void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ @Override
+ Optional readTaskList() throws DataConversionException, IOException;
+
+ @Override
+ void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException;
+
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 79868290974..fd94bc94537 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -8,6 +8,7 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTaskList;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
@@ -19,14 +20,17 @@ public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
+ private TaskListStorage taskListStorage;
/**
* Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage,
+ TaskListStorage taskListStorage) {
super();
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.taskListStorage = taskListStorage;
}
// ================ UserPrefs methods ==============================
@@ -53,7 +57,10 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
public Path getAddressBookFilePath() {
return addressBookStorage.getAddressBookFilePath();
}
-
+ @Override
+ public Path getTaskListFilePath() {
+ return taskListStorage.getTaskListFilePath();
+ }
@Override
public Optional readAddressBook() throws DataConversionException, IOException {
return readAddressBook(addressBookStorage.getAddressBookFilePath());
@@ -69,11 +76,27 @@ public Optional readAddressBook(Path filePath) throws DataC
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);
}
-
+ @Override
+ public Optional readTaskList() throws DataConversionException, IOException {
+ return readTaskList(taskListStorage.getTaskListFilePath());
+ }
+ @Override
+ public Optional readTaskList(Path filePath) throws DataConversionException, IOException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return taskListStorage.readTaskList(filePath);
+ }
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList) throws IOException {
+ saveTaskList(taskList, taskListStorage.getTaskListFilePath());
+ }
+ @Override
+ public void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ taskListStorage.saveTaskList(taskList, filePath);
+ }
}
diff --git a/src/main/java/seedu/address/storage/TaskListStorage.java b/src/main/java/seedu/address/storage/TaskListStorage.java
new file mode 100644
index 00000000000..14ecee8a17a
--- /dev/null
+++ b/src/main/java/seedu/address/storage/TaskListStorage.java
@@ -0,0 +1,42 @@
+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.ReadOnlyTaskList;
+
+public interface TaskListStorage {
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getTaskListFilePath();
+
+ /**
+ * Returns TaskList data as a {@link ReadOnlyTaskList}.
+ * 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 readTaskList() throws DataConversionException, IOException;
+
+ /**
+ * @see #getTaskListFilePath()
+ */
+ Optional readTaskList(Path filePath) throws DataConversionException, IOException;
+
+ /**
+ * Saves the given {@link ReadOnlyTaskList} to the storage.
+ * @param taskList cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveTaskList(ReadOnlyTaskList taskList) throws IOException;
+
+ /**
+ * @see #saveTaskList(ReadOnlyTaskList)
+ */
+ void saveTaskList(ReadOnlyTaskList taskList, Path filePath) throws IOException;
+
+}
+
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 9a665915949..7d61e4b3579 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2021s1-cs2103t-f12-4.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..beb9f26fe74 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -5,6 +5,7 @@
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.MenuItem;
+import javafx.scene.control.TabPane;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
@@ -32,8 +33,10 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private TaskListPanel taskListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private TaskListPanel dueSoonTaskListPanel;
@FXML
private StackPane commandBoxPlaceholder;
@@ -44,12 +47,21 @@ public class MainWindow extends UiPart {
@FXML
private StackPane personListPanelPlaceholder;
+ @FXML
+ private StackPane taskListPanelPlaceholder;
+
@FXML
private StackPane resultDisplayPlaceholder;
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private TabPane featuresPanelPlaceholder;
+
+ @FXML
+ private StackPane dueSoonTasksPanelPlaceholder;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -112,6 +124,11 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
void fillInnerParts() {
personListPanel = new PersonListPanel(logic.getFilteredPersonList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ taskListPanel = new TaskListPanel(logic.getFilteredTaskList());
+ taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot());
+
+ dueSoonTaskListPanel = new TaskListPanel(logic.getDueSoonTaskList());
+ dueSoonTasksPanelPlaceholder.getChildren().add(dueSoonTaskListPanel.getRoot());
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
@@ -163,6 +180,19 @@ private void handleExit() {
primaryStage.hide();
}
+ /**
+ * Navigate features tab to show the correct tab
+ * @param category the category the command belongs to
+ */
+ @FXML
+ private void handleTabNavigation(String category) {
+ if (category.equals("CONTACT")) {
+ featuresPanelPlaceholder.getSelectionModel().select(0);
+ } else if (category.equals("TASK")) {
+ featuresPanelPlaceholder.getSelectionModel().select(1);
+ }
+ }
+
public PersonListPanel getPersonListPanel() {
return personListPanel;
}
@@ -178,6 +208,8 @@ private CommandResult executeCommand(String commandText) throws CommandException
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
+ handleTabNavigation(commandResult.getCategory());
+
if (commandResult.isShowHelp()) {
handleHelp();
}
diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java
new file mode 100644
index 00000000000..0c0f1006af5
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskCard.java
@@ -0,0 +1,121 @@
+package seedu.address.ui;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import seedu.address.model.task.Recurrence;
+import seedu.address.model.task.Task;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class TaskCard extends UiPart {
+
+ private static final String FXML = "TaskListCard.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 Task task;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label type;
+ @FXML
+ private Label description;
+ @FXML
+ private Label id;
+ @FXML
+ private Label dateTime;
+ @FXML
+ private Label statusIcon;
+ @FXML
+ private VBox additionalInfo;
+ @FXML
+ private FlowPane tags;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public TaskCard(Task task, int displayedIndex) {
+ super(FXML);
+ this.task = task;
+ type.setText(task.getType());
+ id.setText(displayedIndex + ". ");
+ description.setText(task.getDescription());
+ dateTime.setText(task.getDateTime());
+ statusIcon.setText("Status: " + task.getStatusIcon());
+ Hyperlink meetingLink = new Hyperlink();
+ Label linkDescription = new Label();
+ VBox linkContainer = new VBox();
+ meetingLink.getStyleClass().add("meetingLink");
+ linkDescription.getStyleClass().add("linkDescription");
+ linkContainer.getStyleClass().add("linkContainer");
+
+ Recurrence recurrence = task.getRecurrence();
+ if (recurrence != null) {
+ String text = "Recurring task: " + recurrence.getValue() + " " + recurrence.getUnit();
+ Label recurring = new Label(text);
+ recurring.getStyleClass().add("recurring");
+ additionalInfo.getChildren().add(recurring);
+ }
+
+ if (task.getLink().isPresent()) {
+ meetingLink.setText(task.getLink().get().getUrl());
+ meetingLink.setOnAction(e -> {
+ if (Desktop.isDesktopSupported()) {
+ new Thread(() -> {
+ try {
+ Desktop.getDesktop().browse(new URI(meetingLink.getText()));
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ } catch (URISyntaxException e1) {
+ e1.printStackTrace();
+ }
+ }).start();
+ }
+ });
+ linkDescription.setText(task.getLink().get().getDescription());
+ linkContainer.getChildren().addAll(meetingLink, linkDescription);
+ additionalInfo.getChildren().add(linkContainer);
+ }
+
+ task.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
+ TaskCard card = (TaskCard) other;
+ return id.getText().equals(card.id.getText())
+ && task.equals(card.task);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java
new file mode 100644
index 00000000000..49ae4f16945
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TaskListPanel.java
@@ -0,0 +1,49 @@
+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.task.Task;
+
+/**
+ * Panel containing the list of persons.
+ */
+public class TaskListPanel extends UiPart {
+ private static final String FXML = "TaskListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
+
+ @FXML
+ private ListView taskListView;
+
+ /**
+ * Creates a {@code TaskListPanel} with the given {@code ObservableList}.
+ */
+ public TaskListPanel(ObservableList taskList) {
+ super(FXML);
+ taskListView.setItems(taskList);
+ taskListView.setCellFactory(listView -> new TaskListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
+ */
+ class TaskListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Task task, boolean empty) {
+ super.updateItem(task, empty);
+
+ if (empty || task == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new TaskCard(task, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index 882027e4537..05496f76d56 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -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/lifebook.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
deleted file mode 100644
index 29810cf1fd9..00000000000
Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ
diff --git a/src/main/resources/images/icon.png b/src/main/resources/images/icon.png
new file mode 100644
index 00000000000..8017c3a4a60
Binary files /dev/null and b/src/main/resources/images/icon.png differ
diff --git a/src/main/resources/images/lifebook.png b/src/main/resources/images/lifebook.png
new file mode 100644
index 00000000000..b4749cd5ae5
Binary files /dev/null and b/src/main/resources/images/lifebook.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 09f6d6fe9e4..7b312b3dc6f 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -3,7 +3,7 @@
-
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..9d2263479fb 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -1,6 +1,6 @@
.background {
- -fx-background-color: derive(#1d1d1d, 20%);
- background-color: #383838; /* Used in the default.html file */
+ -fx-background-color: derive(#29323C, 20%);
+ background-color: #29323C; /* Used in the default.html file */
}
.label {
@@ -29,20 +29,65 @@
-fx-font-family: "Segoe UI Semibold";
}
-.tab-pane {
- -fx-padding: 0 0 0 1;
+.tab-pane
+{
+ -fx-tab-min-width: 3em;
+ -fx-tab-min-height: 3em;
}
-.tab-pane .tab-header-area {
- -fx-padding: 0 0 0 0;
- -fx-min-height: 0;
- -fx-max-height: 0;
+.tab{
+ -fx-background-insets: 0.0;
+ -fx-background-radius: 15 15 0 0;
+ -fx-background-color: #29323C;
+ -fx-padding: 0 30 0 30;
+}
+.tab-pane .tab
+{
+ -fx-background-color: #29323C;
+
+}
+
+.tab-pane .tab:selected
+{
+ -fx-background-color: #485563;
+}
+
+.tab-pane .tab:focused
+{
+ -fx-focus-color: transparent;
+}
+
+.tab .tab-label {
+ -fx-alignment: CENTER;
+ -fx-text-fill: white;
+ -fx-font-size: 14px;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-background-color: transparent;
+}
+
+.tab:selected .tab-label {
+ -fx-alignment: CENTER;
+ -fx-text-fill: white;
+}
+
+.tab-pane *.tab-header-background {
+ -fx-background-color: #29323c;
+}
+
+.tab-pane:top *.tab-header-area {
+ -fx-background-insets: 0, 0 0 1 0;
+ -fx-padding: 0.416667em 0.166667em 0.0em 0.0em;
+}
+
+.tab:selected .focus-indicator {
+ -fx-focus-color: transparent;
+ -fx-border-color: transparent;
}
.table-view {
- -fx-base: #1d1d1d;
- -fx-control-inner-background: #1d1d1d;
- -fx-background-color: #1d1d1d;
+ -fx-base: #29323C;
+ -fx-control-inner-background: #29323C;
+ -fx-background-color: #29323C;
-fx-table-cell-border-color: transparent;
-fx-table-header-border-color: transparent;
-fx-padding: 5;
@@ -77,45 +122,58 @@
}
.split-pane:horizontal .split-pane-divider {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#29323C, 20%);
-fx-border-color: transparent transparent transparent #4d4d4d;
}
.split-pane {
-fx-border-radius: 1;
-fx-border-width: 1;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#29323C, 20%);
}
.list-view {
-fx-background-insets: 0;
-fx-padding: 0;
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #485563;
+ -fx-border-width: 0;
+}
+
+.list-view .scroll-bar:vertical {
+ -fx-background-color:#485563;
+}
+
+.list-view .increment-button ,.list-view .decrement-button {
+ -fx-background-color:transparent;
+ -fx-border-color:transparent;
+}
+
+.list-view .scroll-bar:vertical .thumb {
+ -fx-background-color:#6a8098;
+ -fx-background-insets: 4;
+ -fx-background-radius: 5;
}
.list-cell {
-fx-label-padding: 0 0 0 0;
-fx-graphic-text-gap : 0;
-fx-padding: 0 0 0 0;
+ -fx-border-width: 1;
+ -fx-border-color: #485563
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: #626e7b;
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
+ -fx-background-color: #626e7b;
}
.list-cell:filled:selected {
-fx-background-color: #424d5f;
}
-.list-cell:filled:selected #cardPane {
- -fx-border-color: #3e7b91;
- -fx-border-width: 1;
-}
-
.list-cell .label {
-fx-text-fill: white;
}
@@ -133,23 +191,23 @@
}
.stack-pane {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: derive(#29323C, 20%);
}
.pane-with-border {
- -fx-background-color: derive(#1d1d1d, 20%);
- -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-background-color: derive(#29323C, 20%);
+ -fx-border-color: derive(#29323C, 10%);
-fx-border-top-width: 1px;
}
.status-bar {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: #343f4b;
}
.result-display {
-fx-background-color: transparent;
- -fx-font-family: "Segoe UI Light";
- -fx-font-size: 13pt;
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 12pt;
-fx-text-fill: white;
}
@@ -165,8 +223,8 @@
}
.status-bar-with-border {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#29323C, 30%);
+ -fx-border-color: derive(#29323C, 25%);
-fx-border-width: 1px;
}
@@ -175,17 +233,17 @@
}
.grid-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
- -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-background-color: red;
+ -fx-border-color: red;
-fx-border-width: 1px;
}
.grid-pane .stack-pane {
- -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-color: derive(#29323C, 30%);
}
.context-menu {
- -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-color: #343f4b;
}
.context-menu .label {
@@ -193,20 +251,29 @@
}
.menu-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #343f4b;
+ -fx-selection-bar: #485563;
}
.menu-bar .label {
- -fx-font-size: 14pt;
+ -fx-font-size: 12pt;
-fx-font-family: "Segoe UI Light";
-fx-text-fill: white;
-fx-opacity: 0.9;
}
+.menu:focused {
+ -fx-background-color: #485563;
+}
+
.menu .left-container {
-fx-background-color: black;
}
+.menu-item:focused {
+ -fx-background-color: #485563;
+}
+
/*
* Metro style Push Button
* Author: Pedro Duque Vieira
@@ -217,7 +284,7 @@
-fx-border-color: #e2e2e2;
-fx-border-width: 2;
-fx-background-radius: 0;
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #29323C;
-fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
-fx-font-size: 11pt;
-fx-text-fill: #d8d8d8;
@@ -230,7 +297,7 @@
.button:pressed, .button:default:hover:pressed {
-fx-background-color: white;
- -fx-text-fill: #1d1d1d;
+ -fx-text-fill: #29323C;
}
.button:focused {
@@ -243,7 +310,7 @@
.button:disabled, .button:default:disabled {
-fx-opacity: 0.4;
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #29323C;
-fx-text-fill: white;
}
@@ -257,11 +324,11 @@
}
.dialog-pane {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #29323C;
}
.dialog-pane > *.button-bar > *.container {
- -fx-background-color: #1d1d1d;
+ -fx-background-color: #29323C;
}
.dialog-pane > *.label.content {
@@ -271,7 +338,7 @@
}
.dialog-pane:header *.header-panel {
- -fx-background-color: derive(#1d1d1d, 25%);
+ -fx-background-color: derive(#29323C, 25%);
}
.dialog-pane:header *.header-panel *.label {
@@ -282,12 +349,13 @@
}
.scroll-bar {
- -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-background-color: #485563;
}
.scroll-bar .thumb {
- -fx-background-color: derive(#1d1d1d, 50%);
- -fx-background-insets: 3;
+ -fx-background-color:#6a8098;
+ -fx-background-insets: 2.5;
+ -fx-background-radius: 5;
}
.scroll-bar .increment-button, .scroll-bar .decrement-button {
@@ -307,6 +375,10 @@
-fx-padding: 8 1 8 1;
}
+#root {
+ -fx-background-color: #29323C;
+}
+
#cardPane {
-fx-background-color: transparent;
-fx-border-width: 0;
@@ -317,24 +389,56 @@
-fx-text-fill: #F70D1A;
}
+#commandTextPanel {
+ -fx-background-color: #485563;
+ -fx-background-radius: 15;
+ -fx-padding: 5 10 5 10;
+}
+
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-color: transparent #485563 transparent #485563;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
- -fx-border-insets: 0;
- -fx-border-width: 1;
- -fx-font-family: "Segoe UI Light";
- -fx-font-size: 13pt;
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 12pt;
-fx-text-fill: white;
}
+#commandBoxPlaceholder {
+ -fx-background-color: #29323C;
+ -fx-border-width: 0;
+}
+
#filterField, #personListPanel, #personWebpage {
-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
}
+#placeHolder {
+ -fx-background-color: #485563;
+ -fx-background-radius: 15 15 15 15;
+ -fx-padding: 10;
+ -fx-border-width: 0;
+}
+
+#resultDisplayPlaceholder {
+ -fx-background-color: #29323C;
+}
+
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
- -fx-background-radius: 0;
+ -fx-background-color: transparent;
+ -fx-background-radius: 15;
+}
+
+.text-area .scroll-pane {
+ -fx-background-color: transparent;
+}
+.text-area .scroll-pane .viewport{
+ -fx-background-color: transparent;
+}
+.text-area .scroll-pane .content{
+ -fx-background-color: transparent;
+}
+.corner {
+ -fx-background-color: transparent;
}
#tags {
@@ -343,10 +447,65 @@
}
#tags .label {
- -fx-text-fill: white;
- -fx-background-color: #3e7b91;
- -fx-padding: 1 3 1 3;
- -fx-border-radius: 2;
- -fx-background-radius: 2;
+ -fx-text-fill: black;
+ -fx-background-color: #FFD540;
+ -fx-padding: 1 5 1 5;
+ -fx-border-radius: 15;
+ -fx-background-radius: 15;
-fx-font-size: 11;
+ -fx-font-family: "Segoe UI Semibold"
+}
+
+#featuresPanelPlaceholder {
+ -fx-background-color: #29323c;
+ -fx-padding : 10 10 5 10
+}
+
+#personList {
+ -fx-background-color: #485563;
+ -fx-background-radius: 0 15 15 15;
+ -fx-border-width: 0;
+}
+
+#taskListPanel {
+ -fx-background-color: #485563;
+ -fx-background-radius: 15 15 15 15;
+ -fx-border-width: 0;
+}
+
+#dueSoonTaskListPanel {
+ -fx-background-color: #485563;
+ -fx-background-radius: 15 15 15 15;
+ -fx-border-width: 0;
+ -fx-padding: 10;
+}
+
+.linkContainer {
+ -fx-background-color:#6F7B89;
+ -fx-padding: 5;
+ -fx-background-radius: 5;
+}
+
+.list-cell:filled:selected .linkContainer {
+ -fx-background-color: #4F5B6F;
+}
+
+.meetingLink {
+ -fx-text-fill: white;
+ -fx-padding: 0;
+}
+
+.linkDescription {
+ -fx-font-size: 10pt;
+ -fx-font-family: "Segoe UI"
+}
+
+.recurring {
+ -fx-text-fill: #FFD540 !important;
+ -fx-font-size: 10pt;
+ -fx-font-family: "Segoe UI Semibold"
+}
+
+#additionalInfo {
+ -fx-text-fill: #FFD540;
}
diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css
index bfe82a85964..1733db19d51 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: #485563;
}
.tag-selector {
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index fa0fb54d9f4..7bd51325939 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -8,32 +8,32 @@
-
-
-
-
-
-
-
-