: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`.
-
+
:bulb: **Tip:**
+- If a command fails its execution, it will not call `Model#commitHistory()`, so the address book state will not be saved into `allHistory`.
+- For general commands (help, exit, calendar, undo, redo), `Model#commitHistory()` will not be called as they do not cause any changes to addressBook.
-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.
+Step 4. The user now decides that adding the contact was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoHistory()`, which will shift the `currentSize` once to the left, pointing it to the previous history instance, and restores the address book and its displaySetting to that state. Pointer `maxSize` will not shift and will stay at the end point of the history.
![UndoRedoState3](images/UndoRedoState3.png)
-
-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.
+The `redo` command does the opposite — it calls `Model#redoHistory()`, which shifts the `currentSize` once to the right, pointing to the previously undone state, and restores the address book and its display setting to that state.
-
: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.
+
:bulb: **Tip:** If the `currentSize` is at the same point as `maxSize`, pointing to the latest history instance, then there are no undone AddressBook states to restore. The `redo` command uses `Model#isRedoable()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-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.
+Step 5.
+
+- If the user decides to `redo` the previous `undo` command, the pointer `currentSize` will again be shifted forward by one, pointing to the last history instance. The previously undone addressBook state is restored.
![UndoRedoState4](images/UndoRedoState4.png)
-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.
+- If the user instead executes another command such as `eclear`, this will call `Model#commitHistory()`. This will create a new history instance right after the current instance. Both `currentSize` and `maxSize` will be reset to point to this new instance. Any history instance following after will be ignored. Reason: It no longer makes sense to redo the `cadd n/David …` command. This is the behavior that most modern desktop applications follow.
![UndoRedoState5](images/UndoRedoState5.png)
+#### Activity diagram
+
The following activity diagram summarizes what happens when a user executes a new command:
-
+![ActivityDiagram](images/UndoRedoActivityDiagram.png)
-#### Design considerations:
+#### Design considerations
**Aspect: How undo & redo executes:**
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+* **Alternative 1 (current choice):** Saves the entire address book and its display setting.
+ * 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.
+ * Pros: Will use less memory (e.g. for `cdelete`, just save the contact being deleted).
+ * Cons: We must ensure that the implementation of each individual command are correct.
+
+### Calendar UI feature
+
+The calendar UI is implemented using the [CalendarFX](https://github.com/dlsc-software-consulting-gmbh/CalendarFX) framework. It can be opened using either the shortcut key `F2`, the file dropdown menu, or the command `calendar`. This feature is mainly facilitated by the `CalendarWindow` class. The `CalendarCommand` class handles the `calendar` command that the user may type and the `MainWindow` class handles the shortcut key and the dropdown menu option for showing the calendar.
+
+#### Design of CalendarWindow
+
+The `CalendarWindow` class extends from `UiPart`, just like the other UI components including the help window. The `CalendarWindow` class instance contains a table of events and the respective entries in the `Calendar` object. There are also final instances of `Calendar` and `CalendarView` objects in a `CalendarWindow` object. See the [CalendarFX manual](https://dlsc.com/wp-content/html/calendarfx/manual.html) for more information on `Calendar` and `CalendarView` classes.
+
+![Sequence diagram of CalendarWindow](images/CalendarSequenceDiagram.png)
+
+The sequence of how a calendar window is generated is shown in the UML sequence diagram above. The following are the textual descriptions of the diagram:
+
+1. The user intends to open the calendar. The `MainWindow` object captures this intention and calls the constructor of `CalendarWindow` and passes the list of events into it.
+2. The `Calendar` and `CalendarView` objects are created and stored. A _hashmap_ of `Event` objects to `Entry` objects is created. (Not shown)
+3. A time thread is created to constantly update the current time of the calendar every 10 seconds.
+4. The `Calendar` object is populated with the entries of events from Step 1. The _hashmap_ is also concurrently being updated with the events and the associated entries. (Not shown)
+5. The `CalendarView` object is updated to include the `Calendar` object, and also to change some configurations to simplify the user interface and prevent edits directly on the calendar.
+6. The `StackPane` (see [`CalendarWindow.fxml`](https://github.com/AY2122S1-CS2103T-W15-3/tp/blob/master/src/main/resources/view/CalendarWindow.fxml)) is updated to include the new `CalendarView` user interface. The `CalendarWindow` object is now created and returned to `MainWindow`.
+
+#### Design considerations
+
+The user may leave the calendar window open and type in a new command to add, delete, edit or clear the events. In that case, there is a need to constantly update the calendar to reflect the new changes the user has made. This section discusses the implementation of the update and how the updates are optimized.
+
+**Aspect: How the calendar is updated constantly:**
+
+* **Alternative 1 (current choice):** Only updates the event(s) changed by the event commands.
+ * Pros: More optimized and faster update for large number of events.
+ * Cons: Harder to implement and prone to errors.
+
+
+:information_source: **Note:** It is important to discuss the `EventChanger` class from `Model` since the implementation of the update feature depends heavily on this class.
+
+
+The `EventChanger` class contains references to up to 2 `Event` objects - an `oldEvent` and a `newEvent`. It also contains a boolean that is true if the user intends to clear all events. Creating the `EventChanger` object to be passed to the `CalendarWindow` to update the entries is simple, as it can be easily constructed using one of the _factory methods_: `addEventChanger`, `clearEventChanger`, `deleteEventChanger` and `editEventChanger`. See the class diagram below for a summary of the `EventChanger` class.
-_{more aspects and alternatives to be added}_
+![Class Diagram of EventChanger](images/EventChangerClassDiagram.png)
-### \[Proposed\] Data archiving
+Upon the execution of any command, the list of event changers is returned in the `CommandResult`. The list is usually empty, except for the 4 types of commands listed above. The `updateCalendar` method of `CalendarWindow` is then called, which will update the `Calendar` object to remove the `oldEvent` and add the `newEvent` entries. The `Calendar` is cleared if the `EventChanger` is the `clearEventChanger`. This is when the _hashmap_ becomes useful, since the `Entry` objects in the calendar are unique and having the same `Event` associated to the `Entry` does not make the `Entry` objects equal. This will allow deletion of the `Entry` from the calendar.
-_{Explain here how the data archiving feature will be implemented}_
+* **Alternative 2:** Create a new Calendar with all the events for each command executed.
+ * Pros: Easy to implement, less code is needed.
+ * Cons: Performance can be slowed down if there are many events, even if the user does not make any large change to the events.
+This alternative implementation will cause the `MainWindow` class to constantly query the event list in the `AddressBook` through the `Logic` manager every time a command is executed. The queried list is then used to create a new `Calendar` which will be used by the `CalendarWindow` object to display the calendar to the user. However, the performance cost is not feasible if there are many events stored in the `AddressBook`.
--------------------------------------------------------------------------------------------------------------------
@@ -255,73 +674,484 @@ _{Explain here how the data archiving feature will be implemented}_
### Product scope
-**Target user profile**:
+**Target user:** [School of Computing](https://www.comp.nus.edu.sg) (SoC) students who:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
-
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+* Need to manage large number of contacts and events
+* Prefer desktop apps over other types
+* Can type fast and prefer typing to mouse interactions
+* Are reasonably comfortable using *CLI* apps
+**Value proposition**: Manage contacts of peers, *Profs* and events such as classes and Co-Curricular Activities in
+a single system faster than a typical mouse/*GUI* driven app while maintaining user-friendliness
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+#### Contacts
+
| Priority | As a … | I want to … | So that I can… |
| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| `* * *` | hardworking student | add contact of my *TA*/*Profs* | save their contacts and ask them questions on my modules. |
+| `* * *` | careless student | edit the contact of my *TA*/*Profs* | correct mistakes while adding contacts or update my *TA* contact details |
+| `* * *` | senior SoC student | delete the contact of my *TA*/*Profs* | remove contact of my *TA* after I have completed the module |
+| `* * *` | SoC student | view the contact of my *TA*/*Profs* | |
+| `* * *` | year 4 SoC student with many contacts | search for contact of my *TA*/*Profs* | contact them when necessary |
+| `* * *` | CS2103T student | list all the _telegram handles_ of my CS2103T project mates | add them to the project group |
+| `* *` | year 4 SoC student with many contacts | sort the contacts of my *TA* | view the contacts based on the sorting settings |
+| `* *` | careless student | undo my last action(s) | recover contacts I accidentally deleted/changed |
+| `* *` | organized SoC student | categorize the contacts of students/*TA*/*Profs* by tags | view them separately |
+| `* *` | SoC student who frequently contacts a *TA* | mark a contact as favorite and pin them to the top | view them easily when I start the app |
+| `* *` | student with many contacts | add the profile picture of *TA*/*Profs* | more easily differentiate my contacts and remember their faces |
+| `*` | first time user | import existing contacts from my phone | easily access all my past contacts using SoConnect |
+| `*` | long term user | archive some contacts | still save old contacts without cluttering my current screen |
+
+#### Events
+
+| Priority | As a … | I want to … | So that I can… |
+| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
+| `* * *` | SoC student | add details of event | keep track of the CCA dates and time |
+| `* * *` | SoC student | delete details of event | remove events that have finished or are cancelled |
+| `* * *` | SoC student | edit details of event | update event details |
+| `* * *` | SoC student | view all CCA/events | have a rough overview of my schedule |
+| `* * *` | SoC student | search for an event based on event name | easily refer to the event details |
+| `* * *` | year 1 SoC student | list addresses of all lectures, tutorials and labs |
+| `* *` | SoC student | sort the events by time | prepare for upcoming events |
+| `* *` | SoC student with busy schedule | check if the new event clashes with any of my current events | better plan my timetable and avoid event clashes |
+| `* *` | SoC student with busy schedule | see a weekly calendar | easily visualize my schedule for the week |
+| `* *` | SoC student with many different events to manage | categorize my events with different tags like classes and CCAs | search related events |
+| `* *` | SoC student with an important event coming up | mark an event as favorite and pin them to the top | easily see the most important events first when I start the app |
+| `* *` | SoC student with many events and contacts to manage | link some contacts to some events | view the details of the participants/*TAs* in my events or lessons. |
+| `*` | SoC student who uses other calendars | import and export my events to other calendars like Google Calendar | synchronize my events across my calendars |
+| `*` | long term user | archive some events that have ended | still save details of past events without cluttering my main screen |
+| `*` | SoC student with many commitments | have a reminder of upcoming events | |
+
+#### Personalisation and Others
+
+| Priority | As a … | I want to … | So that I can… |
+| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
+| `* * *` | first time user | have a help message I can refer to | understand and start using basic features |
+| `*` | first time user | set up my background | personalize the screen to my liking |
### 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 `SoConnect Application System (SAS)` and the **Actor** is the `user`, unless specified otherwise)
+
+**1. Use case: UC1 - Add contact details**
+
+**Guarantees:** The contact will be stored in the system if and only if the user enters the correct inputs.
+
+***MSS***
+
+1. User chooses to add *contact information*.
+
+2. SAS stores the contact and notifies the user that the contact has been successfully added.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. SAS detects an error in the inputs.
+
+ * 1a1. SAS requests for correct inputs.
+
+ * 1a2. User enters new inputs.
+
+ Steps 1a1 to 1a2 are repeated until the inputs entered are correct.
+
+ Use case resumes from step 2.
+
+
+* 1b. SAS detects a duplicate contact with the same name.
+
+ * 1b1. SAS gives the user the option of adding the contact anyway.
+
+ * 1b2. User proceeds to add the contact.
+
+ Use case resumes from step 2.
+
+
+* *a. At any time. user chooses not to add the contact.
+
+ Use case ends.
+
+
+**2. Use case: UC2 - Edit events**
+
+**Preconditions:** There is at least one event in the event list.
+
+**Guarantees:** The event will be updated accordingly if and only if the user enters the correct inputs.
+
+***MSS***
+
+1. User wants to view the list of events.
+
+2. User decides on an event to edit.
+
+3. User edits the event.
+
+4. SAS updates the event accordingly and notifies user that the event has been successfully edited.
+
+ Use case ends.
+
-**Use case: Delete a person**
+**Extensions**
+
+* 3a. SAS detects an error in the inputs.
+
+ * 3a1. SAS requests for correct inputs.
+
+ * 3a2. User enters new inputs.
+
+ Steps 3a1 to 3a2 are repeated until inputs entered are correct.
+
+ Use case resumes from step 4.
+
+
+* *a. User chooses not to edit the event.
+
+ Use case ends.
+
+
+**3. Use case: UC3 - Delete contacts**
+
+**Preconditions:** There is at least one contact in the contact list.
+
+**Guarantees:** The contact list will be updated according to which contact(s) are deleted if and only if the user enters the correct inputs.
+
+***MSS***
+
+1. User wants to view the list of contacts.
+
+2. User decides on the contact(s) to delete.
+
+3. User deletes the contact(s).
+
+4. SAS deletes the specified contact(s), updates the contact list accordingly, and notifies user that the contact(s) has been successfully deleted.
+
+ Use case ends.
+
+
+**Extensions**
+
+* 3a. SAS detects an error in the inputs.
+
+ * 3a1. SAS requests for correct inputs.
+
+ * 3a2. User enters new inputs.
+
+ Steps 3a1 to 3a2 are repeated until inputs entered are correct.
+
+ Use case resumes from step 4.
+
+
+* *a. User chooses not to delete the contact(s).
+
+ Use case ends.
+
+
+**4. Use case: UC4 - Find contact details**
+
+**Guarantees:** A filtered list of contacts that match the user keywords will be shown, if and only if the user enters the correct inputs.
+
+***MSS***
+
+1. User decides on the keyword(s) to find.
+
+2. User enters specified keyword(s).
+
+3. SAS shows the matched contacts accordingly and notifies user of search success.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. SAS fails to find any matched contacts.
+
+ * 2a1. SAS outputs 0 matched contacts.
+
+ * 2a2. User enters new keyword(s).
+
+ Steps 2a1 to 2a2 are repeated until the user finds the contacts of interest.
+
+ Use case resumes from step 3.
+
+
+* *a. User chooses not to find contact(s).
+
+ Use case ends.
-**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+**5. Use case: UC5 - Sort events**
+
+**Preconditions:** List of events to be sorted is displayed.
+
+**Guarantees:** The displayed list of events is sorted lexicographically according to the specified _field_, if the given _field_ is valid.
+
+***MSS***
+
+1. User decides on a _field_ to sort by.
+
+2. User inputs the specific _field_.
+
+3. SAS sorts the list of events by the specified _field_ provided in step 2.
+
+4. SAS displays the sorted list of events.
+
+ Use case ends
+
+**Extensions**
+
+* 2a. SAS detects that the input is an invalid _field_.
+
+ * 2a1. SAS requests for a correct input.
+
+ * 2a2. User enters a new input.
+
+ Steps 2a1-2a2 are repeated until user enters a valid _field_.
+
+ Use case resumes from step 3.
+
+
+* 2b. SAS detects that the input contains more than one _field_.
+
+ * 2b1. SAS sorts the list by the first _field_ entered.
+
+ Use case resumes from step 4.
+
+
+* *a. User chooses not to sort the list.
Use case ends.
+
+
+**6. Use case: UC6 - Delete events**
+
+**Preconditions:** There is at least one event in the event list.
+
+**Guarantees:** The event list will be updated according to which event(s) are deleted if and only if the user enters the correct inputs.
+
+***MSS***
+
+1. User wants to view the list of events.
+
+2. User decides on the event(s) to delete.
+
+3. User deletes the event(s).
+
+4. SAS deletes the specified event(s), updates the event list accordingly, and notifies user that the event(s) has been successfully deleted.
+
+ Use case ends.
+
**Extensions**
-* 2a. The list is empty.
+* 3a. SAS detects an error in the inputs.
+
+ * 3a1. SAS requests for correct inputs.
+
+ * 3a2. User enters new inputs.
+
+ Steps 3a1 to 3a2 are repeated until inputs entered are correct.
+
+ Use case resumes from step 4.
+
+
+**7. Use case: UC7 - List event _fields_**
+
+**Preconditions:** There is at least one event in the event list.
+
+**Guarantees:** The displayed list only contains the _field_(s) of interest, if the given _field_(s) is / are valid.
+
+***MSS***
+
+1. User decides on a _field_(s) to be listed.
+
+2. User inputs the specific _field_(s).
+
+3. SAS displays the list of events with only the _field_(s) specified shown.
+
+ Use case ends
+
+**Extensions**
+
+* 2a. SAS detects that the input is an invalid _field_.
+
+ * 2a1. SAS requests for a correct input.
+
+ * 2a2. User enters a new input.
+
+ Steps 2a1-2a2 are repeated until user enters a valid _field_.
+
+ Use case resumes from step 3.
+
+
+* 2b. SAS detects that the user did not provide a _field_.
+
+ * 2b1. SAS displays the default list containing all the _fields_ of the events.
+
+ Use case ends.
+
+
+* *a. User chooses not to sort the list.
Use case ends.
-* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+**8. Use case: UC8 - Link a specific event to multiple contacts**
+
+**Preconditions:** There is at least one event and one contact.
+
+**Guarantees:** The chosen event is linked to those chosen contacts.
+
+***MSS***
- Use case resumes at step 2.
+1. User decides on which event and contacts to link.
-*{More to be added}*
+2. User inputs the event and contacts to link.
+
+3. SAS links the chosen event to every chosen contacts and notifies user that the event is successfully linked to those contacts.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. SAS detects an error in the inputs.
+
+ * 2a1. SAS requests for correct inputs.
+
+ * 2a2. User enters new inputs.
+
+ Steps 2a1 to 2a2 are repeated until the inputs entered are correct.
+
+ Use case resumes from step 3.
+
+* *a. User chooses not to link event to contacts.
+
+ Use case ends.
+
+**9. Use case: UC9 - Undo a command**
+
+**Guarantees:** SoConnect will be restored to its previous state in its history.
+
+***MSS***
+
+1. User decides to undo a command.
+
+2. SAS identifies the previous state of SoConnect and restore it. SAS notifies the user that the previous command is undone.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. SAS detects that SoConnect does not have a previous state in its history.
+
+ * 1a1. SAS notifies the user that SoConnect is already at its original state.
+
+ Use case ends.
+
+**10. Use case: UC10 - Redo a command**
+
+**Guarantees:** SoConnect will be restored to its previously undone state in its history.
+
+***MSS***
+
+1. User decides to undo the previous undo command.
+
+2. SAS redoes the command and restores the previously undone state of SoConnect from its history.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. SAS detects that SoConnect is already at its most recent state.
+
+ * 1a1. SAS notifies the user that there is no previous undo command to redo.
+
+ Use case ends.
+
+**11. Use case: UC11 - Mark contacts**
+
+**Guarantees:** The chosen contacts will be marked with a bookmark icon
and moved to the start of the contact list.
+
+***MSS***
+
+1. User decides on the contacts to mark.
+
+2. User inputs the indexes of the contacts to mark.
+
+3. SAS marks the chosen contacts and move them to the top of the list.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. SAS detects an error in the inputs.
+
+ * 2a1. SAS requests for correct inputs.
+
+ * 2a2. User enters new inputs.
+
+ Steps 2a1 to 2a2 are repeated until the inputs entered are correct.
+
+ Use case resumes from step 3.
+
+* 2b. SAS detects some contacts that have already been marked.
+
+ * 2b1. SAS informs the user the relevant contacts that have already been marked previously.
+
+ * 2b2. SAS proceeds to mark the other contacts and move them to the top of the list.
+
+ Use case ends.
+
+* *a. User chooses not to link event to contacts.
+
+ Use case ends.
### Non-Functional Requirements
1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
+2. Should be able to hold up to 1000 contacts 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.
-
-*{More to be added}*
+4. Should not depend on Internet connection.
+5. Only one user should be able to use the system at one time.
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Mainstream OS**: Windows, Linux, Unix, OS-X.
+
+
+* [**Main Success Scenario**](https://nus-cs2103-ay2122s1.github.io/website/schedule/week7/topics.html#requirements-specifying-requirements-use-cases-details) (MSS): Describes the most straightforward interaction for a given use case, which assumes that nothing goes wrong.
+
+
+* **Command Line Interface** (CLI): Text-based user interface.
+
+
+* **Graphical User Interface** (GUI): Graphic-based user interface.
+
+
+* **Interface**: (For user interface) The place at which independent and often unrelated systems meet and act on or communicate with each other/ a way for the user to interact with the system. (For other use of interfaces such as `Model`) See the [Java Tutorials](https://docs.oracle.com/javase/tutorial/java/concepts/interface.html) for more details on what an interface in Java means.
+
+
+* **Telegram handle**: Username of a [Telegram](https://telegram.org/) user.
+
+
+* **Professor** (Prof): A lecturer who teaches a module in NUS.
+
+
+* **Teaching Assistant** (TA): A student tutor employed to teach small group lessons in NUS.
+
+
+* **Contact information**: Name and email of the contact. Phone number, address, telegram handle, video conferencing meeting link, and tag(s) are optional.
+
+
+* **Field**: A particular detail of an event or contact. Examples of fields include the name of a contact, the description of an event or the tags of an event.
+
+
+* **Hashmap**: A data structure that stores a table of keys and their corresponding values. In the implementation of Calendar UI, a hashmap is used to store the `Event` objects as keys and the `Entry` object as values.
+
+
+* **Factory method**: A method to create an object without specifying the exact class, based on the creational design pattern. In the context of `EventChanger`, the factory methods create instances of subclasses of `EventChanger`, as seen in [`EventChanger.java`](https://github.com/AY2122S1-CS2103T-W15-3/tp/blob/master/src/main/java/seedu/address/model/event/EventChanger.java).
--------------------------------------------------------------------------------------------------------------------
@@ -330,7 +1160,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Given below are instructions to test the app manually.
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
-testers are expected to do more *exploratory* testing.
+testers are expected to do more exploratory testing.
@@ -340,7 +1170,8 @@ 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.
+ 2. Double-click the jar file
+ Expected: Shows the _GUI_ with a set of sample contacts. The window size may not be optimum.
1. Saving window preferences
@@ -349,29 +1180,143 @@ 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 … }_
+### Adding a contact
+
+1. Test case: `cadd n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
+ Expected: A new contact named John Doe with the specified detail will be added to the end of the contact list. Success message displayed.
+
+2. Test case: `cadd n/Jane Doe p/1298701`
+ Expected: No contact added. Error details shown in the status message.
+
+3. Test case: `cadd n/Alex Yeoh e/alexYeoh@gmail.com`
+ Expected: No contact added. Error details shown in the status message.
+
+4. Other invalid `cadd` commands: `cadd n/`, `cadd n/John Doe e/`, `cadd n/* e/test@gmail.com`
+ Expected: No contact added. Error details shown in the status message.
+
+### Finding a contact
+1. Test case: `cfind Alex`
+ Expected: A list of contacts with names that match`Alex` will be displayed. Success message displayed.
+
+2. Test case: `cfind alex`
+ Expected: Similar to previous.
+
+3. Test case: `cfind e/bernice`
+ Expected: A list of contacts with emails that match `bernice` will be displayed. Success message displayed.
+
+4. Test case: `cfind e/bernice a/geylang`
+ Expected: A list of contacts with emails that match `bernice` or with addresses that match `geylang` will be displayed. Success message displayed.
+
+5. Test case: `cfind a/`
+ Expected: No change in the displayed contact list. Error details displayed in the status message.
+
+6. Test case: `cfind`
+ Expected: No change in the displayed contact list. Error details displayed in the status message.
+
+7. Other invalid `cfind` commands: `cfind a/ e/`, `cfind a/alex e/`
+ Expected: No change in the displayed contact list. Error details displayed in the status message.
+
+
+### Deleting an event
+
+Deleting an event while all events are being shown
+
+Prerequisites: List all events using the `elist` command. Multiple events in the list.
+
+ 1. Test case: `edelete 1`
+ Expected: First event is deleted from the list. Details of the deleted event shown in the status message. Timestamp in the status bar is updated.
+
+ 2. Test case: `edelete 0`
+ Expected: No event is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 3. Other incorrect delete commands to try: `edelete`, `edelete x` (where x is larger than the list size), `edelete a`
+ Expected: Similar to previous.
-### Deleting a person
+ 4. Test case: `edelete 1-2`
+ Expected: First and second event are deleted from the list. Details of the deleted events shown in the status message. Timestamp in the status bar is updated.
-1. Deleting a person while all persons are being shown
+ 5. Test case: `edelete 2-1` (invalid range)
+ Expected: No event is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 6. Test case: `edelete 1-x` (where x is larger than the list size)
+ Expected: Similar to previous.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+### Listing all events
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+Listing all event with certain _fields_ shown.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+Prerequisites: At least one event in the list.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+ 1. Test case: `elist at/`
+ Expected: All events listed with only address displayed. All events listed shown in the status message.
+
+ 1. Test case: `elist`
+ Expected: All events listed with all _fields_ displayed. All events listed shown in the status message.
+
+ 1. Other incorrect `elist` commands to try: `elist 123`, `elist at/0000`, `elist xyz/` (where xyz is not a valid prefix)
+ Expected: No change in display. Error details displayed in the status message.
+
+### Linking an event to one or more contacts
+
+ Prerequisites: There is at least one event and one contact in their respective list.
+
+1. Test case: `elink 1 c/1`
+ Expected: The event at index `1` is linked to the contact at index `1`. Success details shown in the status message.
+
+2. Test case: `elink 2 c/1 c/2 c/3`
+ Expected: The event at index `2` is linked to each of the contacts at indexes `1`, `2` and `3`.
-1. _{ more test cases … }_
+3. Test case: `elink x c/1` (where x is an index larger than the event list size)
+ Expected: No change in display. Error details displayed in the status message.
+
+3. Test case: `elink 1 c/x` (where x is an index larger than the contact list size)
+ Expected: No change in display. Error details displayed in the status message.
+
+4. Other incorrect `elink` commands to try: `elink`, `elink 1`, `elink c/1`
+ Expected: No change in display. Error details displayed in the status message.
+
+### Undoing/Redoing a command
+
+1. Test case: `cadd n/Amy e/amy@mail.com p/1987911` followed by `undo`
+ Expected: A new contact named Amy is added after the first command is then removed after the `undo` command. Success messages displayed after each command.
+
+2. Test case: `cfind Alex` followed by `undo 12`
+ Expected: A filtered list with contact name Alex Yeoh will be displayed after the first command. A full contact list will be displayed after `undo`command. Success messages displayed after each command.
+
+3. Test case: `cdelete 1` followed by `undo` and `redo`
+ Expected: First contact in the contact list is removed from the displayed contact list after the first command. The contact is restored after the `undo` command, and is removed again after the `redo` command. Success messages displayed after each command.
+
+4. Test case: `undo` without any previous command
+ Expected: No change in display. Error details displayed in the status message.
+
+5. Test case: `redo` without any previous `undo` command
+ Expected: No change in display. Error details displayed in the status message.
+
+### GUI
+
+1. Test case: For any event with a linked contact, click on the linked contact (labeled in yellow)
+ Expected: Only linked contact(s) of the chosen event will be displayed in the contact list. Clicking on the same linked contact again will display the full contact list.
+
+2. Test case: Click on any field in the contact and event list (except for tag)
+ Expected: The chosen field will be copied to clipboard. Success details displayed in the status message.
+
+3. Test case: Click on any zoom or telegram link (underlined in blue)
+ Expected: The chosen link is opened in browser. Success details displayed in the status message.
+
### Saving data
-1. Dealing with missing/corrupted data files
+Dealing with missing/corrupted data files
+
+Prerequisite: There should be a valid `soconnect.json` file saved in the data folder.
+
+
+:bulb: **Tip:** If there is no json file, start `soconnect.jar`, perform some changes to the default list of contacts or events (see [User Guide](https://ay2122s1-cs2103t-w15-3.github.io/tp/UserGuide.html)). A folder `data` with a file `soconnect.json` should be created.
+
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Test case: Edit the json file in order to corrupt it. For example, add an additional `{` to create some syntax error. Then, start `soconnect.jar`.
+ Expected: The event and contact list is empty.
-1. _{ more test cases … }_
+ 2. Test case: Delete the json file and start `soconnect.jar`.
+ Expected: The default event and contact list is loaded (see [User Guide](https://ay2122s1-cs2103t-w15-3.github.io/tp/UserGuide.html#quick-start) for an example).
+
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..4a3d40975a2 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,192 +1,1140 @@
---
layout: page
-title: User Guide
+title: SoConnect 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.
+
+
+Made by [School of Computing](https://www.comp.nus.edu.sg/) (SoC) students, for SoC students, SoConnect is a **desktop app for SoC students to manage contacts
+of Professors and Teaching Assistants, and keep track of noteworthy events.**
+With SoConnect, you can **save
+your contacts and events in one location**, and even **link events to multiple contacts**, or **bookmark
+your favorite contact**.
+
+SoConnect is **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, managing your contacts will be
+**very easy** with SoConnect.
+
+This user guide will help you to familiarise yourself with your SoConnect quickly and teach you the
+full range of features it offers.
+
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## How to use SoConnect User Guide
-1. Ensure you have Java `11` or above installed in your Computer.
+* You can click on the titles in the Table of Contents to jump to the section that you are interested in.
+* You will find these icons in this user guide:
+ * **:bulb: Tip** provides additional information that might be useful to you.
+ * **:information_source: Note** provides supplementary information that helps you to understand this User Guide.
+ * **:exclamation: Caution** cautions you against certain actions that will lead to undesirable consequences.
+* You can find explanations of _italicized_ words in the [Glossary](#glossary).
+* You can refer to the [Command Summary](#command-summary) for a complete overview of all SoConnect features and _commands_.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+### How to read _commands_ in SoConnect
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+You will see _commands_ throughout this User Guide and each of them has its own _Command Format_.
-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:
+This is one example of a _command_:
- * **`list`** : Lists all contacts.
+![Command Example](images/demo-screenshots/commandSyntax.png)
- * **`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.
+There are only 3 different parts of inputs within each _command_:
+1. **_COMMAND_ WORD**
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ You need to specify the _command_ word to tell SoConnect what action you want to do.
- * **`clear`** : Deletes all contacts.
+ For example, specifying `cedit` in the _command_ above will tell SoConnect to edit a contact for you.
- * **`exit`** : Exits the app.
+2. **_PARAMETER_**
-1. Refer to the [Features](#features) below for details of each command.
+ The _parameter_ is the supplementary information that you need to provide for the action you want to do.
---------------------------------------------------------------------------------------------------------------------
+ Using the same example, specifying `2` tells SoConnect to edit the contact at **index 2** (the second contact in the list) to the name `Betsy Crower`.
-## Features
+3. **_PREFIX_**
+
+ The *prefix* separates the different types of *parameters*.
+
+ Each _prefix_ always ends with a `/`. See the [list of _prefixes_](#list-of-prefixes) for all the _prefixes_ that you can use in SoConnect.
+
+ For example, if you use `n/`, SoConnect will recognise that the information following this _prefix_ should be a **name**.
+
+You may view the entire list of _commands_ that you can type in SoConnect [here](#features).
-**:information_source: Notes about the command format:**
+**:information_source: About the _Command_ format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+* Words in `UPPER_CASE` are the _parameters_ that you should provide.
+ e.g. in `cadd n/NAME`, `NAME` is a _parameter_ that can be used as `cadd n/John Doe`.
-* Items in square brackets are optional.
+* _Fields_ in **square brackets** are **optional**.
e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
-* 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.
+* _Fields_ 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.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+* Each pair of _prefixes_ and _parameters_ can be in any order.
+ e.g. if the _command_ specifies `n/NAME [p/PHONE_NUMBER]`, `[p/PHONE_NUMBER] n/NAME` is also acceptable.
-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
+* If a _parameter_ is expected only once in the _command_, but you specify it multiple times, **only the last occurrence** of the _parameter_ will be taken (unless otherwise stated).
e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* Extraneous _parameters_ for _commands_ that do not take in _parameters_ (such as `help` and `exit`) will be **ignored**.
+ e.g. if the _command_ specifies `help 123`, it will be interpreted as `help`.
+
+* Date and time must follow **dd-MM-yyyy HH:mm** format (day-month-year hours:minutes).
+ e.g. if the date and time is 1 May 2021 6.30pm, you should specify it as `01-05-2021 18:30`
+
+
+
+### What happens if my _command_ is invalid?
+
+After you enter a _command_ in SoConnect, a success message will be displayed on the message box of SoConnect.
+
+
:bulb: **Tip:**
+
+Unsure of where to find the **message box**? Click [here](#overview-of-soconnect) to check out the
+overview of different components in SoConnect.
+
+
+For example, after entering [`clist`](#listing-all-contacts-clist), SoConnect will tell you that it has successfully listed all contacts.
+
+![Success Message](images/demo-screenshots/successMessage.png)
+
+
+
+However, if the _command_ you entered does not fulfill the _Command Format_ specified in
+[Features](#features), an **error message** will be shown on the message box instead.
+You can then correct your mistakes in the _command_ and try again.
+
+For example, if you enter [`cview`](#viewing-a-contact-cview) **without** specifying which contact to view,
+SoConnect will tell you that the _Command Format_ is invalid.
+
+![Error Message](images/demo-screenshots/invalidCommand.png)
+
+--------------------------------------------------------------------------------------------------------------------
+## Quick start
+
+1. Ensure you have [Java 11](https://www.oracle.com/java/technologies/downloads/) or above installed on your computer.
+
+
:bulb: **Tip:**
+
+ [Here](https://www.java.com/en/download/help/version_manual.html) is how you can check the Java Version installed on your computer.
+
+
+2. Download the latest `soconnect.jar` from [here](https://github.com/AY2122S1-CS2103T-W15-3/tp/releases).
+
+3. Copy the file to the folder you want to use as the _home folder_ for your SoConnect.
+
+
+![_Home Folder_](images/demo-screenshots/homeFolder.png)
+
+
+4. Double-click the file to start SoConnect. This is how SoConnect looks like:
+
+ ![Overview](images/demo-screenshots/overview.png)
+
+
:information_source: **Note:**
+ SoConnect comes with some sample contacts and events upon installation.
+
+
:bulb: **Tip:**
+ If SoConnect does not start by double-clicking, you can check this
+ [alternative](#how-to-start-soconnect-using-terminal) way of starting SoConnect.
+
+
+5. Type the _command_ in the _command_ box and **press Enter** to execute it.
+
+
:bulb: **Tip:**
+ Unsure of where to find the **_command_ box**? Click [here](#overview-of-soconnect) to check out the
+ overview of different components in SoConnect.
+
+
+ For example, typing `help` and **pressing Enter** will open the [help window](#help-window-help).
+ Here are some example _commands_ you can try:
+
+ * `elist`: [Lists all events](#listing-all-events-elist).
+
+ * `cadd n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`:
+ [Adds a contact](#adding-a-contact-cadd) named `John Doe` to SoConnect.
+
+ * `cdelete 3` : [Deletes the third contact](#deleting-a-contact-cdelete) shown in SoConnect.
+
+ * `eclear` : [Clears all entries of events](#clearing-all-events-eclear) from SoConnect.
+
+ * `exit` : [Exits](#exiting-soconnect-exit) SoConnect.
+
+6. Refer to the [Features](#features) below for details of each _command_.
+
+_See also: [What happens if my command is invalid](#what-happens-if-my-command-is-invalid)_
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Overview of SoConnect
+
+This is what you will see when you open SoConnect!
+![Labelled SoConnect](images/demo-screenshots/labelledSoconnectOverview.png)
+
+1. **Menu Bar**
+
+ The Menu Bar consists of 2 tabs: `File` and `Help`.
+
+ Upon clicking `File`, you can choose to:
+ 1. **Exit** SoConnect
+ 2. Open **Calendar** of SoConnect
+
+ ![File Tab](images/demo-screenshots/fileTab.png)
+
+
:bulb: **Tip:**
+ Click the following links to see how to [exit SoConnect](#exiting-soconnect-exit) and [open the calendar](#calendar-window-calendar) using _commands_.
+
+
+ Upon clicking `Help`, you can choose to open the **Help Window** of SoConnect.
+ ![Help Tab](images/demo-screenshots/helpTab.png)
+
+
:bulb: **Tip:**
+ Check out how to open [the help window](#help-window-help) using _commands_.
+
+
+2. **_Command_ Box**
+
+ This is where you can type all your _commands_ to use the [features of SoConnect](#features).
+
+3. **Message Box**
+
+ This is where the message from SoConnect is displayed after you have executed one _command_.
+
+ _See Also: [What happens if my command is invalid?](#what-happens-if-my-command-is-invalid)_
+
+5. **Contact List**
+
+ Displays information of the contacts you saved in SoConnect.
+
+ Refer to [Icons in Contact List](#icons-in-contact-list) to learn about the different _fields_ that each icon represents in Contact List.
+
+6. **Event list**
+
+ Displays information of the events you saved in SoConnect.
+
+ Refer to [Icons in Event List](#icons-in-event-list) to learn about the different _fields_ that each icon represents in Event List.
+
+
+
+### Icons in Contact List
+
+![Contact Card](images/demo-screenshots/contactCard.png)
+You will always find these 2 information of each contact displayed in SoConnect Contact List:
+1. Contact Index
+2. Contact Name
+
+Here are the icons you might see under each contact:
+
+Icon | _Field_
+--------|------------------
+**![Bookmark Icon](images/demo-screenshots/bookmarkIcon.png)** | Bookmark*
+**![Email Icon](images/demo-screenshots/emailIcon.png)** | Email Address
+**![Phone Number Icon](images/demo-screenshots/phoneNumberIcon.png)** | Phone Number
+**![Address Icon](images/demo-screenshots/addressIcon.png)** | Address
+**![Telegram Icon](images/demo-screenshots/telegramIcon.png)** | Telegram Handle
+**![Website Icon](images/demo-screenshots/websiteIcon.png)** | Links/ Websites
+**![Event Icon](images/demo-screenshots/eventIcon.png)** | Linked Events
+**![Tag Icon](images/demo-screenshots/tagIcon.png)** | Tag
+
+*The bookmark icon is only visible if the contact has been marked.
+_See also: [Marking a contact](#marking-a-contact-cmark)_
+
+
+### Icons in Event List
+
+![Event Card](images/demo-screenshots/eventCard.png)
+You will always find these 2 information of each event displayed in SoConnect Event List:
+1. Event Index
+2. Event Name
+
+Here are the icons you might see under each event:
+
+Icon | _Field_
+--------|------------------
+**![Bookmark Icon](images/demo-screenshots/bookmarkIcon.png)** | Bookmark*
+**![Start Time Icon](images/demo-screenshots/startTimeIcon.png)** | Start Time
+**![End Time Icon](images/demo-screenshots/endTimeIcon.png)** | End Time
+**![Website Icon](images/demo-screenshots/websiteIcon.png)** | Links/ Websites
+**![Description Icon](images/demo-screenshots/descriptionIcon.png)** | Description
+**![Contact Icon](images/demo-screenshots/contactsIcon.png)** | Linked Contacts
+**![Tag Icon](images/demo-screenshots/tagIcon.png)** | Tag
+
+*The bookmark icon is only visible if the event has been marked.
+_See also: [Marking an event](#marking-an-event-emark)_
+
+--------------------------------------------------------------------------------------------------------------------
+
+## Features
+
+There are three main sections to SoConnect Features:
+[Contact Management](#contact-management),
+[Event Management](#event-management), and
+[General](#general).
+
+For each feature, you are provided with:
+* Function and Description of the feature
+* _Command Format_ of the feature
+* Examples of some usages of the feature (All examples use the **initial sample** of contacts and events)
+
+--------------------------------------------------------------------------------------------------------------------
+
+### Contact Management
+
+This section details all the features and _commands_ available in SoConnect that can help you manage your contacts:
+
+* [Adding a contact](#adding-a-contact-cadd)
+* [Clearing all contacts](#clearing-all-contacts-cclear)
+* [Deleting a contact](#deleting-a-contact-cdelete)
+* [Editing a contact](#editing-a-contact-cedit)
+* [Finding contacts](#finding-contacts-cfind)
+* [Listing all contacts](#listing-all-contacts-clist)
+
+* [Marking a contact](#marking-a-contact-cmark)
+* [Unmarking a contact](#removing-mark-of-a-contact-cunmark)
+
+* [Viewing a contact](#viewing-a-contact-cview)
+
+
+#### Adding a contact: `cadd`
+
+Adds a contact to SoConnect.
+
+**Format:** `cadd n/NAME e/EMAIL [p/PHONE_NUMBER] [a/ADDRESS] [th/TELEGRAM_HANDLE] [z/ZOOM] [t/TAG]…`
+
+
:bulb: **Tip:**
+
+* A contact can have any number of tags (including 0)
+* You **cannot** add a contact with the **same name** as an existing contact.
+
+
+**Examples:**
+
+Input | Expected Output
+------|------------------
+`cadd n/Alex Doe e/e0123456@u.nus.edu a/COM1 #99-99 th/johnDoe99 t/Professor` | You should see this message in the message box: `New contact added: Alex Doe; Email: e0123456@u.nus.edu; Address: COM1 #99-99; Telegram: johnDoe99; Tags: [Professor]`
You should also see `Alex Doe` added **at the end** of your contact list: ![New Contact 1](images/demo-screenshots/caddEx1.png)
+`cadd n/ Jon Cheng t/TA e/e7654321@u.nus.edu a/COM1-0201 p/87654321 t/Senior th/jonnyjohnny z/https://nus-sg.zoom.us/j/0123456789?pwd=ABCDEFGHIJKLMNOPDJFHISDFSDH` | You should see this message in the message box: `New contact added: Jon Cheng; Email: e7654321@u.nus.edu; Phone: 87654321; Address: COM1-0201; Zoom Link: https://nus-sg.zoom.us/j/0123456789?pwd=ABCDEFGHIJKLMNOPDJFHISDFSDH; Telegram: jonnyjohnny; Tags: [Senior][TA]`
You should also see `Jon Cheng` added **at the end** of your contact list: ![New Contact 2](images/demo-screenshots/caddEx2.png)
+
+*Index of the newly added contact will be one more than the previous number of contacts.
+
+
+#### Clearing all contacts: `cclear`
+
+Clears **all** entries of contacts in SoConnect.
+
+**Format:** `cclear`
+
+
:bulb: **Tip:**
+
+This **will not change the events** saved in SoConnect.
+
+
+
+#### Deleting a contact: `cdelete`
+
+Deletes the specified contact(s) from SoConnect.
+
+**Format:** `cdelete INDEX1[-INDEX2]`
+
+* Deletes the contact(s):
+ * at the specified `INDEX1` or
+ * between the specified range from `INDEX1` to `INDEX2` inclusively (if you provide `INDEX2`).
+* `INDEX1` and `INDEX2` refer to the index number shown in the displayed contact list.
+
+
:bulb: **Tip:**
+
+* `INDEX1` and `INDEX2` **must be a positive integer**. e.g. 1, 2, 3, …
+* `INDEX1` and `INDEX2` must **not be greater** than the **number of contacts** in the contact list.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+[`clist`](#listing-all-contacts-clist) followed by `cdelete 2` | Deletes the second contact in the contact list.
You should see these messages in the message box:
1. After `clist`: `Listed all contacts`
2. After `cdelete 2`: `Deleted Contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
+[`cfind Bernice`](#finding-contacts-cfind) followed by `cdelete 1` | Deletes the first contact in the **results of the `cfind` _command_**.
You should see these messages in the message box:
1. After `cfind Bernice`: `1 contacts listed!`
2. After `cdelete 1`: `Deleted Contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
+`cdelete 3-5` | Deletes contacts from indexes 3 to 5 (inclusive) from the **currently displayed** contact list.
You should see these messages in the message box:
`Deleted Contact: Charlotte Oliveiro; Email: charlotte@example.com Deleted Contact: David Li; Email: lidavid@comp.nus.edu.sg; Address: COM1-B1-0931; Telegram: lidavid; Tags: [professor][CS2103T] Deleted Contact: Irfan Ibrahim; Email: irfan@example.com; Address: Blk 47 Tampines Street 20, #17-35; Telegram: irfanx; Tags: [classmates]`
+
+
+#### Editing a contact: `cedit`
+
+Edits an **existing** contact in SoConnect.
+
+**Format:** `cedit INDEX [n/NAME] [e/EMAIL] [p/PHONE] [a/ADDRESS] [th/TELEGRAM_HANDLE] [z/ZOOM] [dt/TAG_DELETED]… [t/TAG_ADDED]… `
+* Edits the contact at the specified `INDEX`.
+* `INDEX` refers to the index number shown in the displayed contact list.
+* You must provide **at least one** of the optional _fields_.
+* The input values you provide will be used to update the existing values.
+* You can use `t/` to add a tag.
+* You can remove a specific tag by typing `dt/` followed by the name of the tag that you wish to remove.
+* You can remove all existing tags of a contact by typing `dt/*`.
+* When editing tags, the tags to be deleted will be removed first, before new tags are added.
+
+
:bulb: **Tip:**
+
+* `INDEX` **must be a positive integer**. e.g. 1, 2, 3, …
+* `INDEX` must **not be greater** than the **number of contacts** in the contact list.
+* You **cannot** edit a contact to the **same name** as an existing contact.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`cedit 2 p/91234567 e/agentX@thehightable.com` | Edits the **phone number** and **email address** of the second contact in the **currently displayed** contact list to be `91234567` and `agentX@thehightable.com` respectively.
You should see this message in the message box:
`Edited Contact: Bernice Yu; Email: agentX@thehightable.com; Phone: 91234567; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
You should also the following changes: ![Edit Contact 1](images/demo-screenshots/ceditEx1.png)
+`cedit 1 n/Betsy Crower dt/*` | Edits the **name** of the first contact in the **currently displayed** contact list to be `Betsy Crower` and **clears all existing tags**.
You should see this message in your message box: `Edited Contact: Betsy Crower; Email: alexyeoh@example.com; Phone: 87438807; Address: Blk 30 Geylang Street 29, #06-40; Telegram: yeoh_alex`
You should also see the following changes: ![Edit Contact 2](images/demo-screenshots/ceditEx2.png)
+
+
+#### Finding contacts: `cfind`
+
+Finds all contacts that contain any of the given keywords that you specify.
+
+**Format:** `cfind [KEYWORD]… [e/KEYWORD…] [p/KEYWORD…] [a/KEYWORD…] [th/KEYWORD…] [z/KEYWORD…] [t/KEYWORD…]`
+
+
:bulb: **Tip:**
+
+There are **two** types of contact searches you can do in SoConnect:
+1. If you **do not specify any optional _fields_** in front of the keywords you specify, e.g. `cfind KEYWORD1 KEYWORD2`,
+
+ SoConnect will only search the names of the contacts using the keywords you provide.
+
+2. If you specified optional _field(s)_ before a keyword, e.g. `cfind e/KEYWORD1 p/KEYWORD2`,
+
+ SoConnect will search the emails and phone numbers of the contacts using `KEYWORD1` and `KEYWORD2` respectively.
-### Viewing help : `help`
+* You must provide **at least one keyword**.
+* You can provide multiple keywords without specifying any optional _fields_ e.g. `cfind John David`.
+* You can only **specify each optional _field_ once**.
+* You can the keywords **in any order**. e.g. Both `Hans Bo` and `Bo Hans` will return the same result.
+* Partial words can be matched e.g. `Han` will match `Hans`.
+* The contact(s) matching at least one keyword you provide will be returned.
+ e.g. `Hans Bo` will return `Hans Gruber` and `Bo Yang`.
-Shows a message explaning how to access the help page.
+
:bulb: **Tip:**
-![help message](images/helpMessage.png)
+The search by `cfind` is case-insensitive. e.g. `hans` will match `Hans`.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`cfind alex david` | Returns contacts of `Alex Yeoh` and `David Li`.
You should see this message in the message box:
`2 contacts listed!`
You should also see only these **2 contacts**: ![Find Contact 1](images/demo-screenshots/cfindEx1.png)
+`cfind p/926 e/nus.edu` | Returns contacts with phone numbers that contain `926` and with emails that contain `nus.edu`.
You should see this message in the message box:
`2 contacts listed!`
You should also see only these **2 contacts**: ![Find Contact 2](images/demo-screenshots/cfindEx2.png)
-Format: `help`
+#### Listing all contacts: `clist`
-### Adding a person: `add`
+Shows **all contacts** in the SoConnect, with all available details by default.
-Adds a person to the address book.
+**Format:** `clist [e/] [p/] [a/] [th/] [z/] [t/]`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+* Names of contacts are always shown.
+* If you do not provide any optional _prefixes_, e.g `clist`, all available details of each contact will be shown.
+* If you provide optional _prefixes_, it will only show the names and the _fields_ corresponding to specified _prefixes_ for each contact.
+* You can provide more than one optional _prefix_.
+* You can specify the optional _prefix_ **in any order**. e.g. both `clist e/ p/` and `clist p/ e/` will show only the names, email addresses and phone numbers of each contact.
+* _Fields_ of a contact that have no value will not appear e.g. if a contact does not have a zoom link, typing `clist z/` will not display the zoom link of this contact.
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+
+**Do not** add extraneous values after each optional _prefix_ you specify.
-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`
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`clist` | Shows **all available details** of each contact in SoConnect.
You should see this message in the message box:
`Listed all contacts`
+`clist e/ p/` | Shows **only the names, email addresses and phone numbers** (if available) of each contact in SoConnect.
You should see this message in the message box:
`Listed all contacts`
You should also see the contacts list displays **only the specified _fields_**: ![List Contact 1](images/demo-screenshots/clistEx.png)
+
+
+
+#### Marking a contact: `cmark`
-### Listing all persons : `list`
+Marks the specified contact(s).
-Shows a list of all persons in the address book.
+**Format:** `cmark INDEX [INDEX]...`
-Format: `list`
+* Marks the contact at `INDEX` and **pins it at the top** of the contact list.
+* You can specify more than one `INDEX`, e.g `cmark 1 2`, the order in which the marked contacts appear will be in reverse order to the order you specify their corresponding indexes.
+* `INDEX` refers to the index number shown in the **currently displayed** contact list.
-### Editing a person : `edit`
+
:bulb: **Tip:**
-Edits an existing person in the address book.
+* `INDEX` **must be a positive integer**, e.g. 1, 2, 3,…
+* `INDEX` must **not be greater** than the **number of contacts** in the contact list.
+
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+**Examples:**
-* 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.
+Input | Expected Output
+--------|------------------
+`cmark 2` | Marks the second contact of the **currently displayed** contact list in SoConnect.
You should see this message in the message box:
`Marked Contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
You should see `Bernice Yu` **pinned to the top** of your contact list with the **Bookmark Icon**: ![Mark Contact](images/demo-screenshots/cmarkEx1.png)
+`cmark 4 5` | Marks the fourth and fifth contacts of the **currently displayed** contact list in SoConnect.
You should see this message in the message box:
`Marked contact: Irfan Ibrahim; Email: irfan@example.com; Address: Blk 47 Tampines Street 20, #17-35; Telegram: irfanx; Tags: [classmates] Marked contact: David Li; Email: lidavid@comp.nus.edu.sg; Address: COM1-B1-0931; Telegram: lidavid; Tags: [professor][CS2103T]`
You should see `Irfan Ibrahim` and `David Li` **pinned to the top** of your contact list in that order with the **Bookmark Icon**: ![Mark Contact](images/demo-screenshots/cmarkEx2.png)
+
+_See also: [Unmarking a contact](#removing-mark-of-a-contact-cunmark)_
+
+
+#### Removing mark of a contact: `cunmark`
+
+Unmarks the specified contact(s).
+
+**Format:** `cunmark INDEX [INDEX]...`
+
+* Unmarks the contact at `INDEX1`.
+* You may unmark **more than one contact at a time** by specifying multiple indexes, eg `cunmark 1 2`, the indexes in which the contacts appear will be **in the order in which** you specify them
+* `INDEX` refers to the index number shown in the displayed contact list.
+
+
:bulb: **Tip:**
+
+* `INDEX` **must be a positive integer**, e.g. 1, 2, 3,…
+* `INDEX` must **not be greater** than the **number of contacts** in the contact list.
+* You must check that the contact indexed at `INDEX` is **initially marked**.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+[`cmark 2 3`](#marking-a-contact-cmark) followed by `cunmark 1` | Unmarks the first contact of the **currently displayed** contact list in SoConnect.
You should see these messages in the message box:
1. After `cmark 2 3`: `Marked contact: Charlotte Oliveiro; Email: charlotte@example.com Marked contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
2. After `cunmark 1`: `Unmarked contact: Charlotte Oliveiro; Email: charlotte@example.com`
You should see `Charlotte Oliveiro` moved to the **bottom of your marked contact list** without the **Bookmark Icon**: ![Unmark Contact](images/demo-screenshots/cunmarkEx1.png)
+[`cmark 1 2 3`](#marking-a-contact-cmark) followed by `cunmark 1 2` | Unmarks the first and second contact of the **currently displayed** contact list in SoConnect.
You should see these messages in the message box:
1. After `cmark 1 2 3`: `Marked contact: Charlotte Oliveiro; Email: charlotte@example.com Marked contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends] Marked contact: Alex Yeoh; Email: alexyeoh@example.com; Phone: 87438807; Address: Blk 30 Geylang Street 29, #06-40; Telegram: yeoh_alex; Tags: [friends]`
2. After `cunmark 1 2`: `Unmarked contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends] Unmarked contact: Charlotte Oliveiro; Email: charlotte@example.com`
You should see `Bernice Yu` and `Charlotte Oliveiro` moved to the **bottom of your marked contact list** without the **Bookmark Icon**: ![Unmark Contact](images/demo-screenshots/cunmarkEx2.png)
+
+_See also: [Marking a contact](#marking-a-contact-cmark)_
+
+
+
+#### Viewing a contact: `cview`
+
+Views a contact with all details fully shown.
+
+**Format:** `cview INDEX`
+
+* displays only the contact at the specified `INDEX`.
+* All truncated details of the contact you want to view will be expanded fully.
+* `INDEX` refers to the index number shown in the displayed contact list.
+
+
+
+**:bulb: Tip:**
+* `INDEX` **must be a positive integer**. e.g. 1, 2, 3, …
+* `INDEX` must **not be greater** than the **number of contacts** in the contact list.
+
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`cview 2` | Shows all details of the second contact of the **currently displayed** in SoConnect **fully**.
You should see this message in the message box:
`Viewing Contact: Bernice Yu; Email: berniceyu@example.com; Phone: 99272758; Address: Blk 30 Lorong 3 Serangoon Gardens, #07-18; Zoom Link: nus-sg.zoom.us/j/08382435376?pwd=Oow3u9N098nh8nsdLp0; Telegram: bbernicee; Tags: [TA][friends]`
You should see this change of your **viewed** contact: ![View Contact](images/demo-screenshots/cviewEx.png)
+
+
+### Event Management
+
+This section details all the features and _commands_ available in SoConnect that can help you with managing your events:
+* [Adding an event](#adding-an-event-eadd)
+* [Clearing all event](#clearing-all-events-eclear)
+* [Deleting an event](#deleting-an-event-edelete)
+* [Editing an event](#editing-an-event-eedit)
+* [Finding events](#finding-events-efind)
+* [Linking an event to contacts](#linking-an-event-to-contacts-elink)
+* [Listing all events](#listing-all-events-elist)
+* [Marking an event](#marking-an-event-emark)
+* [Removing mark of an event](#removing-mark-of-an-event-eunmark)
+* [Sorting events](#sorting-events-esort)
+* [Unlinking an event from contacts](#unlinking-an-event-from-contacts-eunlink)
+* [Viewing an event](#viewing-an-event-eview)
+
+
+
+#### Adding an event: `eadd`
+
+Adds an event to SoConnect.
+
+**Format:** `eadd n/NAME at/START_TIME [end/END_TIME] [d/DESCRIPTION] [a/ADDRESS] [z/ZOOM] [t/TAG]…`
+
+
:bulb: **Tip:**
+
+* An event can have any number of tags (including 0)
+* You **cannot** add an event with the **same name** as an existing event.
+
+
+
:information_source: **Note:**
+
+* Start time and End Time should be of format **dd-MM-yyyy HH:m** (date-MONTH-year HOUR:minute in 24-hour format).
+* End Time should be **chronologically after** the Start Time.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`eadd n/Summer Party at/12-12-2021 15:12 a/123, Clementi Rd, 1234665 t/fun` | You should see this message in the message box:
`New event added: Summer Party; Start: 12-12-2021 15:12; Address: 123, Clementi Rd, 1234665; Tags: [fun]`
You should also see `Summer Party` **at the end** of your event list: ![New Event](images/demo-screenshots/eaddEx.png)
+
+*Index of the newly added event will depend on your previous number of events.
+
+
+
+#### Clearing all events: `eclear`
+
+Clears all entries of events from SoConnect.
+
+**Format:** `eclear`
+
+
:bulb: **Tip:**
+This will not change the contacts saved in SoConnect.
+
+
+
+#### Deleting an event: `edelete`
+
+Deletes the specified event(s) from SoConnect.
+
+**Format:** `edelete INDEX1[-INDEX2]`
+
+* Deletes the event(s) at:
+ * the specified `INDEX1` or
+ * between the specified range from `INDEX1` to `INDEX2` inclusively (if you provide `INDEX2`).
+* `INDEX1` and `INDEX2` refer to the index numbers shown in the displayed event list.
+
+
:bulb: **Tip:**
+
+* `INDEX1` and `INDEX2` **must be a positive integer**. e.g. 1, 2, 3, …
+* `INDEX1` and `INDEX2` must **not be greater** than the **number of events** in the event list.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+[`elist`](#listing-all-events-elist) followed by `edelete 2` | Deletes the second event from SoConnect.
You should see these messages in the message box:
1. After `elist`: `Listed all events`
2. After `edelete 2`: `Deleted Event: Basketball training; Start: 01-11-2021 20:00; End: 01-11-2021 21:00; Description: Meeting every week; Address: NUS Sport Centre; Tags: [CCA][Recurring]`
+[`efind Class`](#finding-events-efind) followed by `edelete 1` | Deletes the first event from the **results of the `efind` _command_**.
You should see these messages in the message box:
1. After `efind class`: `1 events listed!`
2. After `edelete 1`: `Deleted Event: Dance class; Start: 13-11-2021 20:00; End: 13-11-2021 22:00; Description: Dancing is my passion. I like pole dancing.; Address: NUS UTown; Tags: [CCA][Recurring]`
+`edelete 1-2` | Deletes events from index 1 to 2 from the **currently displayed** event list.
You should see these messages in the message box:
`Deleted Event: CS2103T project meeting; Start: 10-10-2021 21:00; End: 10-10-2021 22:00; Zoom Link: nus-sg.zoom.us/j/21342513543; Tags: [Recurring][CS2103T]`
`Deleted Event: Basketball training; Start: 01-11-2021 20:00; End: 01-11-2021 21:00; Description: Meeting every week; Address: NUS Sport Centre; Tags: [CCA][Recurring]`
+
+
+#### Editing an event: `eedit`
+
+Edits an existing event in SoConnect.
+
+**Format:** `eedit INDEX [n/NAME] [at/START_TIME] [end/END_TIME] [d/DESCRIPTION] [a/ADDRESS] [z/ZOOM] [dt/TAG_DELETED]… [t/TAG_ADDED]…`
+
+
:bulb: **Tip:**
+
+You must provide **at least one** of the optional _fields_.
+
+
+* Edits the event at the specified `INDEX`.
+* `INDEX` refers to the index number shown in the displayed event list.
* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* You can use `t/` to add a tag.
+* You can remove a specific tag by typing `dt/` followed by the name of the tag that you wish to remove.
+* You can remove all existing tags of an event by typing `dt/*`.
+* When editing tags, the tags to be deleted will be removed first, before new tags are added.
-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.
+
:bulb: **Tip:**
-### Locating persons by name: `find`
+* `INDEX` **must be a positive integer**. e.g. 1, 2, 3, …
+* Start time and End Time should be of format **dd-MM-yyyy HH:mm** (date-MONTH-year HOUR:minute in 24-hour format).
+* End Time should be **chronologically after** the Start Time.
+* You **cannot** edit an event to the **same name** as an existing event.
+
-Finds persons whose names contain any of the given keywords.
+**Examples:**
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Input | Expected Output
+--------|------------------
+`eedit 2 n/CS2103T Exam dt/CCA t/Hard` | Changes the name of the second event in the **currently displayed** event list to `CS2103T Exam`, deletes the tag `CCA` and adds the tag `Hard`.
You should see this message in the message box:
`Edited Event: CS2103T Exam; Start: 01-11-2021 20:00; End: 01-11-2021 21:00; Description: Meeting every week; Address: NUS Sport Centre; Tags: [Recurring][Hard]`
You should also see these changes: ![Edit Event](images/demo-screenshots/eeditEx.png)
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
-### Deleting a person : `delete`
+#### Finding Events: `efind`
-Deletes the specified person from the address book.
+Finds all events that contain any of the given keywords based on your search type.
-Format: `delete INDEX`
+**Format:** `efind [KEYWORD]… [at/KEYWORD…] [end/KEYWORD…] [d/KEYWORD…] [a/KEYWORD…] [z/KEYWORD…] [t/KEYWORD…]`
+
:bulb: **Tip:**
-* 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, …
+There are two types of event searches you can do in SoConnect:
+1. If you **do not specify any optional _fields_ before your keyword(s)**, e.g. `efind KEYWORD1 KEYWORD2`,
-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.
+ You will only search the names of the events based on the keyword(s) provided.
-### Clearing all entries : `clear`
+2. If you specify any _prefix_ before your keyword(s), e.g. `efind a/KEYWORD1 d/KEYWORD2`,
-Clears all entries from the address book.
+ You will search the addresses and descriptions of the events based on `KEYWORD1` and `KEYWORD2` respectively.
+
-Format: `clear`
+* You need to provide **at least one keyword**.
+* You can provide multiple keywords without specifying any _prefixes_.
+* You can only **specify each optional _field_ once**.
+* Partial words can be matched e.g. `Exa` will match `CS2103T Exam`.
+* Events matching at least one keyword will be returned.
+ e.g. `Exam Hard` will return `Hard Exam`, `CS1101S Exams`.
-### Exiting the program : `exit`
+
:bulb: **Tip:**
-Exits the program.
+The search by `efind` is case-insensitive. e.g. `exams` will match `Exams`.
+
-Format: `exit`
+**Examples:**
-### Saving the data
+Input | Expected Output
+--------|------------------
+`efind meet` | Displays events with names that contain `meet`.
You should see this message in the message box:
`1 events listed!`
You should also see only this **one event**: ![Find Event 1](images/demo-screenshots/efindEx1.png)
+`efind t/CS2103T Intern` | Displays events with tags that contain `CS2103T` and `Intern`.
You should see this message in the message box:
`2 events listed!`
You should also see only these **two events**: ![Find Event 2](images/demo-screenshots/efindEx2.png)
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+
-### Editing the data file
+#### Linking an event to contacts: `elink`
+
+Links the specified event to one or more contacts.
+
+**Format:** `elink EVENT_INDEX c/CONTACT_INDEX [c/CONTACT_INDEX]…`
+
+* Links the event at `EVENT_INDEX` to the contact(s) at `CONTACT_INDEX`.
+* `EVENT_INDEX` refers to the index number shown in the displayed event list.
+* `CONTACT_INDEX` refers to the index number shown in the displayed contact list.
+
+
:bulb: **Tip:**
+
+`EVENT_INDEX` and `CONTACT_INDEX` **must be a positive integer**, e.g. 1, 2, 3,…
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`elink 2 c/1 c/2` | Links the second event in the **currently displayed** event list to the contacts with index **1 and 2** in the **currently displayed** contact list.
You should see this in your SoConnect: ![Link](images/demo-screenshots/elinkEx.png)
+
+_See Also: [Unlinking an event from contacts](#unlinking-an-event-from-contacts-eunlink)_
+
+
+#### Listing all events: `elist`
+
+Displays all events in SoConnect, with all details by default.
+
+**Format:** `elist [at/] [end/] [d/] [a/] [z/] [t/]`
+
+* Event names are always displayed.
+* When no optional _prefixes_ are provided, e.g. `elist` , all available details of each event will be displayed.
+* When optional _prefixes_ are provided, only the names and the corresponding specified _fields_ for each event will be shown.
+* You can provide more than one optional _prefixes_.
+* The order of the _prefixes_ does not matter. e.g. both `elist d/ at/` and `elist at/ d/` will only show the names, descriptions and starting times of each event.
+* _Fields_ of an event that have no value will not be shown.
+
+
:bulb: **Tip:**
+
+**Do not** add extraneous values after each optional _prefix_ you specify.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`elist` | Shows **all events** in SoConnect with **all available details** for each event.
You should see this message in the message box:
`Listed all events`
+`elist d/ at/` | Shows **all events** in SoConnect with **only their names, start date and time and description** (if available). You should see this message in the message box:
`Listed all events`
You should also see the event list displays all events with **only the specified _fields_**: ![List Events](images/demo-screenshots/elistEx.png)
+
+
+#### Marking an event: `emark`
+
+Marks the specified event(s).
+
+**Format:** `emark INDEX [INDEX]…`
+
+* Marks the event at `INDEX` and **pins it to the top** of the event list.
+* You can specify more than one `INDEX`, e.g. `emark 1 2`, the order in which the marked events appear will be in reverse order to the order you specify their corresponding indexes.
+* `INDEX` refers to the index number shown in the **currently displayed** event list.
+
+
:bulb: **Tip:**
+
+`INDEX` **must be a positive integer**, e.g. 1, 2, 3,…
+`INDEX` must **not be greater** than the **number of events** in the event list.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`emark 2` | Marks the second event of **currently displayed** event list in SoConnect.
_The expected display is similar to [marking a contact](#marking-a-contact-cmark)_
+
+_See Also: [Removing mark of an event](#removing-mark-of-an-event-eunmark)_
+
+
+#### Removing mark of an event: `eunmark`
+
+Unmarks the specified event(s).
+
+**Format:** `eunmark INDEX [INDEX]...`
+
+* Unmarks the event at `INDEX`.
+* You may unmark **more than one event** by specifying multiple indexes, eg `eunmark 1 2`, the indexes in which the events appear will be **in the order in which** you specify them.
+* `INDEX` refers to the index number shown in the displayed event list.
+
+
:bulb: **Tip:**
+
+* `INDEX` **must be a positive integer**, e.g. 1, 2, 3,…
+* `INDEX` must **not be greater** than the **number of events** in the event list.
+* You must ensure that the event indexed at `INDEX` is **initially marked**.
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`eunmark 2` | Unmarks the second event of **currently displayed** event list in SoConnect.
_The expected display is similar to [Removing mark of a contact](#removing-mark-of-a-contact-cunmark)_
+`eunmark 2 4` | Unmarks the second and fourth event in SoConnect.
+
+_See Also: [Marking an event](#marking-an-event-emark)_
+
+
+#### Sorting events: `esort`
+
+Sorts all events by start time and displays all upcoming or ongoing events.
+
+**Format:** `esort`
+
+**Examples:**
+![Sort example](images/demo-screenshots/sortResult.png)
+
+
:information_source: **Note:**
+
+Events that have ended **will not be shown**
+
+
+
+#### Unlinking an event from contacts: `eunlink`
+
+**Format:** `eunlink EVENT_INDEX c/CONTACT_INDEX [c/CONTACT_INDEX]…`
+
+Unlinks the specified event and the specified contact(s).
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`eunlink 2 c/1` | Unlinks the second event in the **currently displayed** event list from the first contact in the **currently displayed** contact list.
You should see this in your SoConnect: ![unlink contact](images/demo-screenshots/eunlinkEx1.png)
+`eunlink 2 c/*` | Unlinks the second event in the **currently displayed** event list from **all of its linked contacts**.
You should see this in your SoConnect: ![unlink all contacts](images/demo-screenshots/eunlinkEx2.png)
+
+_See Also: [Linking an event to contacts](#linking-an-event-to-contacts-elink)_
+
+
+#### Viewing an event: `eview`
+
+Views an event with all details fully shown.
+
+**Format:** `eview INDEX`
+
+* Views the event at the specified `INDEX`.
+* `INDEX` refers to the index number shown in the displayed event list.
+* All truncated details of the event will be shown fully.
+
+
:bulb: **Tip:**
+
+`INDEX` **must be a positive integer**. e.g. 1, 2, 3, …
+
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+`eview 1` | Shows all details of the first event in the **currently displayed** event list **fully**.
_The expected display is similar to [Viewing a contact](#viewing-a-contact-cview)_
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+
+### General
+
+This section details all the other features and _commands_ available in SoConnect that can enhance your SoConnect experience:
+* [Calendar Window](#calendar-window-calendar)
+* [Exiting SoConnect](#exiting-soconnect-exit)
+* [Help Window](#help-window-help)
+* [Redo a _command_](#redo-a-command-redo)
+* [Undo a _command_](#undo-a-command-undo)
+
+#### Calendar Window: `calendar`
+
+Shows a calendar of all the events.
+
+**Format:** `calendar`
+
+* You can view the calendar in 4 different views:
+ * Daily
+ * Weekly
+ * Monthly
+ * Yearly
+ ![calendar](images/demo-screenshots/Calendar.png)
+
+
+
:bulb: **Tip:**
+
+* Alternatively, you can view the calendar using the top menu bar via `File -> Calendar` or press `F2`.
+* If an event does not have a specified end date and time, the calendar will treat the duration of the event as **one hour**.
+
+
+
:information_source: **Note:**
+
+[Undo](#undo-a-command-undo) and [redo](#redo-a-command-redo) will not change the state of the calendar.
+You should close the calendar window before performing any undo or redo operations.
+
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run.
+Any changes made in the calendar window will not be saved.
+**Do not attempt to add new events using the calendar window.**
+Doing so might result in a crash and your data may be lost.
+
+
+
+
+#### Exiting SoConnect: `exit`
+
+Exits and closes SoConnect.
+
+**Format:** `exit`
+
+
:bulb: **Tip:**
+
+Alternatively, you can exit SoConnect using the top menu bar via `File -> Exit`.
+
+
+
+#### Help Window: `help`
+
+Displays a summary of all _commands_ in SoConnect User Guide.
+
+**Format:** `help`
+
+![help message](images/demo-screenshots/helpWindow.png)
+
+
:bulb: **Tip:**
+
+Alternatively, you can view the help window using the top menu bar via `Help -> Help` or press `F1`.
+
+
+
+#### Redo a _command_: `redo`
+
+Restores SoConnect to a previously undone state from its history.
+
+**Format:** `redo`
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+[`edelete 1`](#deleting-an-event-edelete) followed by [`undo`](#undo-a-command-undo) then `redo` | First **restores the deleted event** in the event list.
Then `redo` will **delete the same event again**.
+
+_See Also: [Undo a command](#undo-a-command-undo)_
+
+
+#### Undo a _command_: `undo`
+
+Restores SoConnect to its previous state from its history.
+
+**Format:** `undo`
+
+**Examples:**
+
+Input | Expected Output
+--------|------------------
+[`cadd n/John Doe e/john@gmail.com`](#adding-a-contact-cadd) followed by `undo` | **Removes the added** contact from the contact list.
+
+_See Also: [Redo a command](#redo-a-command-redo)_
+
+
:information_source: **Note:**
+
+[Undo](#undo-a-command-undo) and [redo](#redo-a-command-redo) will only work for _commands_ listed in the
+[Contact Management](#contact-management) and [Event Management](#event-management) features section.
+
+_Commands_ listed in the [General](#general) section are not undoable.
-### Archiving data files `[coming in v2.0]`
+--------------------------------------------------------------------------------------------------------------------
+
+## SoConnect Saved Data
+
+### Saving the data
+
+SoConnect data is saved in the _hard disk_ automatically after any _command_ that changes the data.
+There is no need to save manually.
+
+### Editing the data file
-_Details coming soon ..._
+SoConnect data are saved as a _JSON file_ `[JAR file location]/data/soconnect.json`.
+Advanced users are welcome to update data directly by editing that data file.
+
+
:exclamation: **Caution:**
+If your changes to the data file make its format invalid,
+SoConnect will discard all data and start with an empty data file at the next run.
+
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## Others
+
+### FAQ
**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**: Install SoConnect in the other computer and copy over the contents from your previous SoConnect _JSON file_ to the
+empty data file SoConnect creates on the other Computer.
+
+
+#### Copying Details and Opening Hyperlinks
+
+![clickable links](images/demo-screenshots/clickableLinkExample.png)
+
+**Q**: How do I copy the email address of a contact?
+**A**: You can copy any specific details of a contact or an event just by clicking on that detail!
+As shown on the image above, clicking on the `email` of `Charlotte Oliveiro` will copy her Email Address.
+
+**Q**: Can SoConnect automatically open a hyperlink on my browser?
+**A**: You can open any hyperlinks that you have included in a contact or in an event. This includes
+telegram handles and Zoom meeting links.
+
+
:bulb: **Tip:**
+
+Clickable hyperlinks are underlined in blue
+
+
+Referring back to the same image, if you click on the zoom link saved under `Charlotte Oliveiro`,
+SoConnect will help you open the link on your browser automatically.
+
+
+#### Linked Contacts and Events
+
+**Q**: How do I view the contacts linked to a particular event?
+**A**: Click on the particular event card in the panel containing events.
+Then click on the yellow boxes which are links to the contacts.
+The linked contacts will be displayed on the contact panel on the left.
+![View links of event](images/demo-screenshots/ClickLinksEvent.png)
+
+**Q**: How do I view the events linked to a particular contact?
+**A**: Click on the particular contact card in the panel containing contacts.
+Then click on the yellow boxes which are links to the events.
+The linked events will be displayed on the event panel on the right.
+![View links of contact](images/demo-screenshots/ClickLinksContact.png)
+
+**Q**: What is the purpose of using links?
+**A**: Links are a form of **relationship between the contacts and the events** saved in SoConnect.
+Typically, we link an event to a contact if the contact of a **participant** of the event.
+For instance, you can link your *professor* to the lecture.
+
+
+### How to start SoConnect using Terminal
+
+1. Open the terminal (For [MacOS](https://support.apple.com/en-sg/guide/terminal/welcome/mac) or Linux)
+ or Command Prompt (For [Windows](https://www.howtogeek.com/235101/10-ways-to-open-the-command-prompt-in-windows-10/)).
+
+2. Navigate to the folder containing `soconnect.jar`.
+ See the tutorial for [Windows](https://www.howtogeek.com/659411/how-to-change-directories-in-command-prompt-on-windows-10/),
+ [MacOS or Linux](https://www.macworld.com/article/221277/command-line-navigating-files-folders-mac-terminal.html)
+ (Linux uses the same _command_ for navigating folders).
+
+3. Enter the following _command_: `java -jar soconnect.jar`. The SoConnect window should open.
+
+
:bulb: **Tip:**
+
+* If you are still unable to open the file,
+ [check your Java version](https://www.java.com/en/download/help/version_manual.html)
+ again and make sure it is version 11.
+* [Download Java 11](https://www.oracle.com/java/technologies/downloads/) if you have not done so.
+
+
--------------------------------------------------------------------------------------------------------------------
+## List of _Prefixes_
+
+**Contact Management**
+
+_Prefix_ | _Parameter_ Type
+--------|------------------
+**`a/`** | Address
+**`dt/`** | Tag to be deleted
+**`e/`** | Email Address
+**`n/`** | Name
+**`p/`** | Phone Number
+**`t/`** | Tag
+**`th/`** | Telegram Handle
+**`z/`** | Links/ Websites
+
+**Event Management**
+
+_Prefix_ | _Parameter_ Type
+--------|------------------
+**`a/`** | Address
+**`at/`** | Start Date and Time
+**`c/`** | Contact index (for linking)
+**`d/`** | Description
+**`dt/`** | Tag to be deleted
+**`end/`** | End Date and Time
+**`n/`** | Name
+**`t/`** | Tag
+**`z/`** | Links/ Websites
-## Command summary
+
+## _Command_ Summary
+
+**Contact Management**
+
+Action | Format, Examples
+--------|------------------
+**[Add](#adding-a-contact-cadd)** | `cadd n/NAME e/EMAIL [p/PHONE_NUMBER] [a/ADDRESS] [th/TELEGRAM_HANDLE] [z/ZOOM] [t/TAG]…`
e.g., `cadd n/Alex Doe e/e0123456@u.nus.edu a/COM1 #99-99 th/johnDoe99 t/Professor`
+**[Clear](#clearing-all-contacts-cclear)** | `cclear`
+**[Delete](#deleting-a-contact-cdelete)** | `cdelete INDEX1[-INDEX2]`
e.g. `cdelete 3`
e.g. `cdelete 1-5`
+**[Edit](#editing-a-contact-cedit)** | `cedit INDEX [n/NAME] [e/EMAIL] [p/PHONE] [a/ADDRESS] [th/TELEGRAM_HANDLE] [z/ZOOM] [dt/TAG_DELETED]… [t/TAG_ADDED]…`
e.g.`cedit 2 p/91234567 e/agentX@thehightable.com`
e.g. `cedit 1 n/Betsy Crower dt/*`
+**[Find](#finding-contacts-cfind)** | `cfind [KEYWORD]… [e/KEYWORD…] [p/KEYWORD…] [a/KEYWORD…] [th/KEYWORD…] [z/KEYWORD…] [t/KEYWORD…]`
e.g. `cfind alex david`
+**[List](#listing-all-contacts-clist)** | `clist [e/] [p/] [a/] [th/] [z/] [t/]`
e.g. `clist`
e.g. `clist e/ p/`
+**[Mark](#marking-a-contact-cmark)** | `cmark INDEX`
e.g. `cmark 2`
+**[Remove mark](#removing-mark-of-a-contact-cunmark)** | `cunmark INDEX [INDEX]…`
e.g. `cunmark 2`
e.g. `cunmark 2 3`
+**[View](#viewing-a-contact-cview)** | `cview INDEX`
e.g. `cview 2`
+
+**Event Management**
+
+Action | Format, Examples
+--------|------------------
+**[Add](#adding-an-event-eadd)** | `eadd n/NAME at/START_TIME [end/END_TIME] [d/DESCRIPTION] [a/ADDRESS] [z/ZOOM] [t/TAG]… `
e.g., `eadd n/Summer Party at/12-12-2021 15:12 a/123, Clementi Rd, 1234665 t/fun`
+**[Clear](#clearing-all-events-eclear)** | `eclear`
+**[Delete](#deleting-an-event-edelete)** | `edelete INDEX1[-INDEX2]`
e.g., `edelete 3`
e.g., `edelete 1-5`
+**[Edit](#editing-an-event-eedit)** | `eedit INDEX [n/NAME] [at/START_TIME] [end/END_TIME] [d/DESCRIPTION] [a/ADDRESS] [z/ZOOM] [dt/TAG_DELETED]… [t/TAG_ADDED]…`
e.g.,`eedit 2 n/CS2103T Exam dt/CCA t/Hard`
e.g., `eedit 3 dt/*`
+**[Find](#finding-events-efind)** | `efind [KEYWORDS]… [at/KEYWORD…] [end/KEYWORD…] [d/KEYWORD…] [a/KEYWORD…] [z/KEYWORD…] [t/KEYWORD…]`
e.g., `efind meet`
e.g., `efind t/CS2103T Intern`
+**[Link](#linking-an-event-to-contacts-elink)** | `elink EVENT_INDEX c/CONTACT_INDEX [c/CONTACT_INDEX]...`
`elink 2 c/1 c/2`
+**[List](#listing-all-events-elist)** | `elist [at/] [end/] [d/] [a/] [z/] [t/]`
e.g., `elist`
e.g., `elist d/ at/`
+**[Mark](#marking-an-event-emark)** | `emark INDEX`
e.g. `emark 2`
+**[Remove mark](#removing-mark-of-an-event-eunmark)** | `eunmark INDEX [INDEX]…`
e.g. `eunmark 2`
e.g. `eunmark 2 4`
+**[Sort](#sorting-events-esort)** | `esort`
+**[Unlink](#unlinking-an-event-from-contacts-eunlink)** | `eunlink EVENT_INDEX c/CONTACT_INDEX [c/CONTACT_INDEX]...`
e.g., `eunlink 2 c/1`
e.g., `eunlink 3 c/*`
+**[View](#viewing-an-event-eview)** | `eview INDEX`
e.g. `eview 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`
-**Help** | `help`
+**[Calendar](#calendar-window-calendar)** | `calendar`
+**[Exit](#exiting-soconnect-exit)** | `exit`
+**[Help](#help-window-help)** | `help`
+**[Redo](#redo-a-command-redo)** | `redo`
+**[Undo](#undo-a-command-undo)** | `undo`
+
+________________________________________________________________________________________________________________
+
+## Glossary
+
+Word | Explanation
+--------|------------------
+**Command Line Interface (CLI)** | Text-based application where users interact with the application by **typing in texts/ commands**.
+**Command** | A string of words and characters you type to perform an action, each command has its own **_Command format_**.
+**Command Format** | The structure that a _command_ must abide by for it to be executed.
+**Parameter(s)** | Information supplied by the user to the application when executing certain _commands_.
+**Prefix** | A tag, consisting of a slash - "/", with one or a few characters to denote information about a certain **_field_**.
+**Field(s)** | The information type within each contact or event
For example, **Name** and **Address** of a contact are _fields_ of a contact.
+**Graphical User Interface (GUI)** | How the application appears to the user on his/her screen.
+**Hard Disk** | Device in a computer that is specialized in storing data permanently.
+**Home Folder** | Folder which the application file is saved on the computer.
+**JavaScript Object Notation (JSON) File** | The file that is used by the application to load and save data of the application in a human-readable format.
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..6b7b6a30a2a 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "SoConnect"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2122S1-CS2103T-W15-3/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..b6858eebe25 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "SoConnect";
font-size: 32px;
}
}
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..a470d45582f 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR
Participant ":Model" as model MODEL_COLOR
Participant ":Storage" as storage STORAGE_COLOR
-user -[USER_COLOR]> ui : "delete 1"
+user -[USER_COLOR]> ui : "edelete 1"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("edelete 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteEvent(e)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 5731f9cbaa1..109520731e9 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -4,18 +4,14 @@ 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
+AddressBook *-right-> "1" UniqueContactList
+AddressBook *-left-> "1" UniqueEventList
+AddressBook *-down-> "1" UniqueTagList
-UniqueTagList *-right-> "*" Tag
-UniquePersonList -right-> Person
+UniqueTagList *--> "*" Tag
+UniqueContactList --> Contact
+UniqueEventList --> Event
-Person -up-> "*" Tag
-
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
+Contact --> "*" Tag
+Event --> "*" Tag
@enduml
diff --git a/docs/diagrams/CMarkActivityDiagram.puml b/docs/diagrams/CMarkActivityDiagram.puml
new file mode 100644
index 00000000000..5e4c836ed3b
--- /dev/null
+++ b/docs/diagrams/CMarkActivityDiagram.puml
@@ -0,0 +1,32 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+start
+:User enters cmark command in the command box;
+:CMarkCommandParser parses the command;
+if () then ([arguments are valid])
+
+ :Generate list of indexes;
+
+ if() then ([indexes not greater than length of contact list])
+ : get contact corresponding to the index
+ in the contact list;
+ : create a new marked contact;
+ : replace the original contact with the marked contact;
+ : rearrange contacts in order;
+
+ else ([else])
+ :throw CommandException with
+ invalid contact displayed;
+ endif
+
+else ([else])
+:throw ParseException with invalid command
+format message and command usage;
+endif
+
+:returns feedback to user;
+
+stop
+
+@enduml
diff --git a/docs/diagrams/CMarkSequenceDiagram.puml b/docs/diagrams/CMarkSequenceDiagram.puml
new file mode 100644
index 00000000000..d76c5a2ee42
--- /dev/null
+++ b/docs/diagrams/CMarkSequenceDiagram.puml
@@ -0,0 +1,90 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":CMarkCommandParser" as CMarkCommandParser LOGIC_COLOR
+participant "c:CMarkCommand" as CMarkCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Contact" as Contact MODEL_COLOR
+end box
+
+[-> LogicManager : execute("cmark 1 2")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("cmark 1 2")
+activate AddressBookParser
+
+create CMarkCommandParser
+AddressBookParser -> CMarkCommandParser
+activate CMarkCommandParser
+
+CMarkCommandParser --> AddressBookParser
+deactivate CMarkCommandParser
+
+AddressBookParser -> CMarkCommandParser : parse("1 2")
+activate CMarkCommandParser
+
+create CMarkCommand
+CMarkCommandParser -> CMarkCommand
+activate CMarkCommand
+
+CMarkCommand --> CMarkCommandParser : c
+deactivate CMarkCommand
+
+CMarkCommandParser --> AddressBookParser : c
+deactivate CMarkCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+CMarkCommandParser -[hidden]-> AddressBookParser
+destroy CMarkCommandParser
+
+AddressBookParser --> LogicManager : c
+deactivate AddressBookParser
+
+LogicManager -> CMarkCommand : execute()
+activate CMarkCommand
+
+CMarkCommand -> Model : getFilteredContactList()
+activate Model
+Model --> CMarkCommand
+deactivate Model
+
+loop 2 times
+ CMarkCommand -> Contact : markContact()
+ activate Contact
+
+ Contact --> CMarkCommand
+ deactivate Contact
+
+ CMarkCommand -> Model : setContact()
+ activate Model
+
+ Model --> CMarkCommand
+ deactivate Model
+
+end
+
+CMarkCommand -> Model : rearrangeContactsInOrder()
+activate Model
+
+Model --> CMarkCommand
+deactivate
+
+create CommandResult
+CMarkCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> CMarkCommand
+deactivate CommandResult
+
+CMarkCommand --> LogicManager : result
+deactivate CMarkCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/CUnmarkActivityDiagram.puml b/docs/diagrams/CUnmarkActivityDiagram.puml
new file mode 100644
index 00000000000..4b588687761
--- /dev/null
+++ b/docs/diagrams/CUnmarkActivityDiagram.puml
@@ -0,0 +1,37 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+start
+:User enters cunmark command in the command box;
+:CUnmarkCommandParser parses the command;
+if () then ([arguments are valid])
+
+ :Generate list of indexes;
+
+ if() then ([indexes not greater than length of contact list])
+ : get contact corresponding to the index
+ in the contact list;
+ if() then ([contact is marked])
+ : create a new unmarked contact;
+ : replace the original contact with the unmarked contact;
+
+ else ([else])
+ endif
+
+ : rearrange contacts in order;
+
+ else ([else]))
+ :throw CommandException with
+ invalid contact displayed;
+ endif
+
+else ([else])
+:throw ParseException with invalid command
+format message and command usage;
+endif
+
+:returns feedback to user;
+
+stop
+
+@enduml
diff --git a/docs/diagrams/CUnmarkSequenceDiagram.puml b/docs/diagrams/CUnmarkSequenceDiagram.puml
new file mode 100644
index 00000000000..8f08bb1c136
--- /dev/null
+++ b/docs/diagrams/CUnmarkSequenceDiagram.puml
@@ -0,0 +1,85 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":CUnmarkCommandParser" as CUnmarkCommandParser LOGIC_COLOR
+participant "c:CUnmarkCommand" as CUnmarkCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Contact" as Contact MODEL_COLOR
+end box
+
+[-> LogicManager : execute("cunmark 4 5")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("cunmark 4 5")
+activate AddressBookParser
+
+create CUnmarkCommandParser
+AddressBookParser -> CUnmarkCommandParser
+activate CUnmarkCommandParser
+
+CUnmarkCommandParser --> AddressBookParser
+deactivate CUnmarkCommandParser
+
+AddressBookParser -> CUnmarkCommandParser : parse("4 5")
+activate CUnmarkCommandParser
+
+create CUnmarkCommand
+CUnmarkCommandParser -> CUnmarkCommand
+activate CUnmarkCommand
+
+CUnmarkCommand --> CUnmarkCommandParser : c
+deactivate CUnmarkCommand
+
+CUnmarkCommandParser --> AddressBookParser : c
+deactivate CUnmarkCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+CUnmarkCommandParser -[hidden]-> AddressBookParser
+destroy CUnmarkCommandParser
+
+AddressBookParser --> LogicManager : c
+deactivate AddressBookParser
+
+LogicManager -> CUnmarkCommand : execute()
+activate CUnmarkCommand
+
+CUnmarkCommand -> Model : getFilteredContactList()
+activate Model
+Model --> CUnmarkCommand
+deactivate Model
+
+loop 2 times
+ CUnmarkCommand -> Contact : getIsMarked()
+ activate Contact
+
+ Contact --> CUnmarkCommand : isMarked
+ deactivate Contact
+
+alt isMarked
+ CUnmarkCommand -> Contact : unmarkContact()
+ activate Contact
+
+ Contact --> CUnmarkCommand
+ deactivate Contact
+
+ CUnmarkCommand -> Model : setContact()
+ activate Model
+
+ Model --> CUnmarkCommand
+ deactivate Model
+end
+end
+
+CUnmarkCommand -> Model : rearrangeContactsInOrder()
+activate Model
+
+Model --> CUnmarkCommand
+deactivate
+
+@enduml
diff --git a/docs/diagrams/CalendarSequenceDiagram.puml b/docs/diagrams/CalendarSequenceDiagram.puml
new file mode 100644
index 00000000000..dd37b4bae8f
--- /dev/null
+++ b/docs/diagrams/CalendarSequenceDiagram.puml
@@ -0,0 +1,63 @@
+@startuml
+!include style.puml
+
+actor User
+
+box UI UI_COLOR_T1
+participant ":CalendarWindow" as CalendarWindow UI_COLOR
+participant ":CalendarView" as CalendarView UI_COLOR_T4
+participant ":Calendar" as Calendar UI_COLOR_T4
+participant ":CalendarSource" as CalendarSource UI_COLOR_T4
+participant "calendarui:StackPane" as CalendarUI UI_COLOR_T4
+end box
+
+create CalendarWindow
+User -> CalendarWindow : CalendarWindow(events)
+activate CalendarWindow
+
+create CalendarView
+CalendarWindow -> CalendarView : new
+activate CalendarView
+CalendarView --> CalendarWindow
+deactivate CalendarView
+
+create Calendar
+CalendarWindow -> Calendar : new Calendar("Events")
+activate Calendar
+Calendar --> CalendarWindow
+deactivate Calendar
+
+CalendarWindow -> CalendarWindow : createTimeThread()
+note left: parallel thread to\nupdate calendar\nevery 10 seconds
+
+CalendarWindow -> CalendarWindow : createCalendar(events)
+
+activate CalendarWindow
+
+loop all events
+CalendarWindow -> CalendarWindow : createEntry(event)
+activate CalendarWindow
+CalendarWindow -> Calendar : addEntry
+deactivate CalendarWindow
+end
+
+deactivate CalendarWindow
+
+CalendarWindow -> CalendarWindow : updateCalendarView()
+activate CalendarWindow
+
+create CalendarSource
+CalendarWindow -> CalendarSource : new
+activate CalendarSource
+return source
+deactivate CalendarSource
+
+CalendarWindow -> CalendarSource : add(calendar)
+
+CalendarWindow -> CalendarUI : add calendarView
+deactivate CalendarWindow
+
+CalendarWindow --> User : calendar window
+deactivate CalendarWindow
+
+@enduml
diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml
index 6a6b23a006f..64a784eee2f 100644
--- a/docs/diagrams/CommitActivityDiagram.puml
+++ b/docs/diagrams/CommitActivityDiagram.puml
@@ -4,12 +4,29 @@ start
'Since the beta syntax does not support placing the condition outside the
'diamond we place it as the true branch instead.
+if () then ([UndoCommand])
+ if () then ([model is undoable])
+ :Restore previous
+ history instance;
+ else ([else])
+ :throw ModelHistoryException;
+ endif
-if () then ([command commits AddressBook])
- :Purge redundant states;
- :Save AddressBook to
- addressBookStateList;
else ([else])
+ if () then ([RedoCommand])
+ if() then ([model is redoable])
+ :Restore previously
+ undone history instance;
+ else ([else])
+ : throw ModelHistoryException;
+ endif
+ else ([else])
+ if () then ([Undoable Command])
+ : Commit current history instance;
+ else ([else])
+ endif
+ endif
endif
+
stop
@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
deleted file mode 100644
index 1dc2311b245..00000000000
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ /dev/null
@@ -1,69 +0,0 @@
-@startuml
-!include style.puml
-
-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 ":CommandResult" as CommandResult LOGIC_COLOR
-end box
-
-box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
-end box
-
-[-> LogicManager : execute("delete 1")
-activate LogicManager
-
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
-
-create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
-activate DeleteCommandParser
-
-DeleteCommandParser --> AddressBookParser
-deactivate DeleteCommandParser
-
-AddressBookParser -> DeleteCommandParser : parse("1")
-activate DeleteCommandParser
-
-create DeleteCommand
-DeleteCommandParser -> DeleteCommand
-activate DeleteCommand
-
-DeleteCommand --> DeleteCommandParser : d
-deactivate DeleteCommand
-
-DeleteCommandParser --> AddressBookParser : d
-deactivate DeleteCommandParser
-'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
-destroy DeleteCommandParser
-
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
-
-LogicManager -> DeleteCommand : execute()
-activate DeleteCommand
-
-DeleteCommand -> Model : deletePerson(1)
-activate Model
-
-Model --> DeleteCommand
-deactivate Model
-
-create CommandResult
-DeleteCommand -> CommandResult
-activate CommandResult
-
-CommandResult --> DeleteCommand
-deactivate CommandResult
-
-DeleteCommand --> LogicManager : result
-deactivate DeleteCommand
-
-[<--LogicManager
-deactivate LogicManager
-@enduml
diff --git a/docs/diagrams/EDeleteActivityDiagram.puml b/docs/diagrams/EDeleteActivityDiagram.puml
new file mode 100644
index 00000000000..b13615bf78a
--- /dev/null
+++ b/docs/diagrams/EDeleteActivityDiagram.puml
@@ -0,0 +1,37 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User enters edelete command in the command box;
+:EDeleteCommandParser parses the command;
+if () then ([arguments are valid] )
+ if () then ( [argument is\na single INDEX])
+ :Creates a RANGE with\nSTART INDEX = END INDEX = INPUT INDEX;
+
+ else ([argument is a RANGE])
+
+ endif
+:Creates EDeleteCommand object for execution;
+
+ repeat
+ :Checks if the event\nat START INDEX is\nlinked to any contacts;
+ if () then ( [else])
+
+ else ([has linked\ncontact(s)])
+ :Unlinks the event from the contact(s);
+
+ endif
+ :Deletes the event at START INDEX;
+ :END INDEX = END INDEX - 1;
+ repeat while () is ( [END INDEX \n>= START INDEX])
+ -> [else] ;
+
+else ( [else])
+:Throws ParseException with invalid command\nformat message and command usage;
+endif
+
+:Returns feedback to user;
+
+stop
+
+@enduml
diff --git a/docs/diagrams/EDeleteSequenceDiagram.puml b/docs/diagrams/EDeleteSequenceDiagram.puml
new file mode 100644
index 00000000000..2f54d18000e
--- /dev/null
+++ b/docs/diagrams/EDeleteSequenceDiagram.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 ":EDeleteCommandParser" as EDeleteCommandParser LOGIC_COLOR
+participant "eD:EDeleteCommand" as EDeleteCommand 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("edelete 1-3")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("edelete 1-3")
+activate AddressBookParser
+
+create EDeleteCommandParser
+AddressBookParser -> EDeleteCommandParser
+activate EDeleteCommandParser
+
+EDeleteCommandParser --> AddressBookParser
+deactivate EDeleteCommandParser
+
+AddressBookParser -> EDeleteCommandParser : parse("1-3")
+activate EDeleteCommandParser
+
+create EDeleteCommand
+EDeleteCommandParser -> EDeleteCommand
+activate EDeleteCommand
+
+EDeleteCommand --> EDeleteCommandParser : eD
+deactivate EDeleteCommand
+
+EDeleteCommandParser --> AddressBookParser : eD
+deactivate EDeleteCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+EDeleteCommandParser -[hidden]-> AddressBookParser
+destroy EDeleteCommandParser
+
+AddressBookParser --> LogicManager : eD
+deactivate AddressBookParser
+
+LogicManager -> EDeleteCommand : execute()
+activate EDeleteCommand
+
+loop 3 times
+ EDeleteCommand -> Model : deleteEvent(1)
+ activate Model
+
+ Model --> EDeleteCommand
+ deactivate Model
+end
+
+create CommandResult
+EDeleteCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EDeleteCommand
+deactivate CommandResult
+
+EDeleteCommand --> LogicManager : result
+deactivate EDeleteCommand
+
+[<--LogicManager
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/ELinkActivityDiagram.puml b/docs/diagrams/ELinkActivityDiagram.puml
new file mode 100644
index 00000000000..77f8bb549a6
--- /dev/null
+++ b/docs/diagrams/ELinkActivityDiagram.puml
@@ -0,0 +1,29 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User enters elink command in the command box;
+:ELinkCommandParser parses the command;
+if () then ([arguments are valid] )
+:Creates ElinkCommand object for execution;
+
+ repeat
+ :Checks if each of the contacts is linked to the event;
+ if () then ( [already linked])
+
+ else ([else])
+ :Links the event and contact;
+ endif
+ repeat while () is ( [else])
+ -> [linked to all contacts] ;
+
+else ( [else])
+:throws ParseException with invalid command
+format message and command usage;
+endif
+
+:returns feedback to user;
+
+stop
+
+@enduml
diff --git a/docs/diagrams/ELinkSequenceDiagram.puml b/docs/diagrams/ELinkSequenceDiagram.puml
new file mode 100644
index 00000000000..b8d0362a255
--- /dev/null
+++ b/docs/diagrams/ELinkSequenceDiagram.puml
@@ -0,0 +1,71 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ELinkCommandParser" as ELinkCommandParser LOGIC_COLOR
+participant "e:ELinkCommand" as ELinkCommand 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("elink 1 c/1 c/2 c/3")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("elink 1 c/1 c/2 c/3")
+activate AddressBookParser
+
+create ELinkCommandParser
+AddressBookParser -> ELinkCommandParser
+activate ELinkCommandParser
+
+ELinkCommandParser --> AddressBookParser
+deactivate ELinkCommandParser
+
+AddressBookParser -> ELinkCommandParser : parse("1 c/1 c/2 c/3")
+activate ELinkCommandParser
+
+create ELinkCommand
+ELinkCommandParser -> ELinkCommand
+activate ELinkCommand
+
+ELinkCommand --> ELinkCommandParser : e
+deactivate ELinkCommand
+
+ELinkCommandParser --> AddressBookParser : e
+deactivate ELinkCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ELinkCommandParser -[hidden]-> AddressBookParser
+destroy ELinkCommandParser
+
+AddressBookParser --> LogicManager : e
+deactivate AddressBookParser
+
+LogicManager -> ELinkCommand : execute()
+activate ELinkCommand
+
+loop 3 times
+ ELinkCommand -> Model : linkEventAndContact(contact, event)
+ activate Model
+
+ Model --> ELinkCommand
+ deactivate Model
+end
+
+create CommandResult
+ELinkCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ELinkCommand
+deactivate CommandResult
+
+ELinkCommand --> LogicManager : result
+deactivate ELinkCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EListActivityDiagram.puml b/docs/diagrams/EListActivityDiagram.puml
new file mode 100644
index 00000000000..1d2baa93740
--- /dev/null
+++ b/docs/diagrams/EListActivityDiagram.puml
@@ -0,0 +1,26 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User enters elist command in the command box;
+:EListCommandParser parses the command;
+if () then ([valid arguments])
+
+ if () then ([prefix present])
+ :set corresponding fields
+ to be displayed;
+
+ else ([no prefix present])
+ :set all fields to display;
+ endif
+
+else ([invalid arguments])
+:throw ParseException with invalid command
+format message and command usage;
+endif
+
+:returns feedback to user;
+
+stop
+
+@enduml
diff --git a/docs/diagrams/EListSequenceDiagram.puml b/docs/diagrams/EListSequenceDiagram.puml
new file mode 100644
index 00000000000..043f9e1b225
--- /dev/null
+++ b/docs/diagrams/EListSequenceDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EListCommand" as eListCommand LOGIC_COLOR
+participant ":EListCommandParser" as eListCommandParser LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Event" as Event MODEL_COLOR
+end box
+
+[-> LogicManager : execute(elist at/ end/)
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand(elist at/ end/)
+activate AddressBookParser
+
+create eListCommandParser
+AddressBookParser -> eListCommandParser
+activate eListCommandParser
+
+eListCommandParser -> eListCommandParser : parse(at/ end/)
+
+eListCommandParser -> Event : setAllDisplayToFalse() \n setWillDisplayStartDateTime(true) \n setWillDisplayEndDateTime(true)
+
+create eListCommand
+eListCommandParser -> eListCommand
+activate eListCommand
+
+eListCommandParser --> AddressBookParser
+deactivate eListCommandParser
+
+AddressBookParser --> LogicManager : eListCommand
+deactivate AddressBookParser
+
+LogicManager -> eListCommand : execute()
+
+eListCommand -> Model : updateFilteredEventList(PREDICATE_HIDE_ALL_EVENTS) \n updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
+activate Model
+
+Model --> eListCommand
+deactivate Model
+
+eListCommand --> LogicManager : result
+deactivate eListCommand
+eListCommand -[hidden]-> LogicManager : result
+destroy eListCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EventChangerClassDiagram.puml b/docs/diagrams/EventChangerClassDiagram.puml
new file mode 100644
index 00000000000..784dcf30ec8
--- /dev/null
+++ b/docs/diagrams/EventChangerClassDiagram.puml
@@ -0,0 +1,38 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+show members
+hide empty members
+
+class EventChanger {
+ {field} - isClear:boolean
+ {static} + addEventChanger(Event)
+ {static} + deleteEventChanger(Event)
+ {static} + editEventChanger(Event, Event)
+ {static} + clearEventChanger()
+}
+
+EventChanger -> "0..2" Event
+
+class CalendarWindow UI_COLOR
+CalendarWindow -right[dashed]-> EventChanger
+
+abstract class "{abstract}\nCommand" as Command LOGIC_COLOR
+class EDeleteCommand LOGIC_COLOR
+class EAddCommand LOGIC_COLOR
+class EEditCommand LOGIC_COLOR
+class EClearCommand LOGIC_COLOR
+
+EDeleteCommand --down[LOGIC_COLOR]|> Command
+EAddCommand --down[LOGIC_COLOR]|> Command
+EEditCommand --down[LOGIC_COLOR]|> Command
+EClearCommand --down[LOGIC_COLOR]|> Command
+EventChanger <-[LOGIC_COLOR,dashed]- EDeleteCommand
+EventChanger <-[LOGIC_COLOR,dashed]- EClearCommand
+EventChanger <-[LOGIC_COLOR,dashed]- EEditCommand
+EventChanger <-[LOGIC_COLOR,dashed]- EAddCommand
+
+
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 6d14b17b361..ea65addb816 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -38,7 +38,7 @@ LogicManager --> Storage
Storage --[hidden] Model
Command .[hidden]up.> Storage
Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+note right of XYZCommand: XYZCommand = CAddCommand, \nEFindCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 1122257bd9a..1d434612007 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -15,14 +15,32 @@ Class ModelManager
Class UserPrefs
Class ReadOnlyUserPrefs
+Package Contact <
> {
+Class UniqueContactList
+Class Contact
+Class Email
+Class Phone
+Class TelegramHandle
+}
-Class UniquePersonList
-Class Person
+Package Common <> {
Class Address
-Class Email
Class Name
-Class Phone
+Class ZoomLink
+}
+
+Package Event <> {
+Class UniqueEventList
+Class Event
+Class EndDateTime
+Class DateAndTime
+Class Description
+Class StartDateTime
+}
+
+Package Tag <> {
Class Tag
+}
}
@@ -38,17 +56,35 @@ ModelManager -left-> "1" AddressBook
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+AddressBook *--> "1" UniqueContactList
+UniqueContactList --> "~* all" Contact
+Contact *--> "1" Name
+Contact *--> "0..1" Phone
+Contact *--> "1" Email
+Contact *--> "0..1" Address
+Contact *--> "0..1" ZoomLink
+Contact *--> "0..1" TelegramHandle
+Contact *--> "*" Tag
+
+StartDateTime .down.|> DateAndTime
+EndDateTime .down.|> DateAndTime
+
+AddressBook *--> "1" UniqueEventList
+UniqueEventList --> "~* all" Event
+Event *--> "1" Name
+Event *--> "0..1" Address
+Event *--> "0..1" ZoomLink
+Event *--> "0..1" Description
+Event *--> "1" StartDateTime
+Event *--> "0..1" EndDateTime
+Event *--> "*" Tag
+
+Tag -down[hidden]- Common
+Address -up[hidden]- Name
+Address -up[hidden]- ZoomLink
+ZoomLink -up[hidden]- Name
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+ModelManager -->"~* filtered" Contact
+ModelManager -->"~* filtered" Event
-ModelManager -->"~* filtered" Person
@enduml
diff --git a/docs/diagrams/ModelHistory.puml b/docs/diagrams/ModelHistory.puml
new file mode 100644
index 00000000000..e7f166b0caf
--- /dev/null
+++ b/docs/diagrams/ModelHistory.puml
@@ -0,0 +1,21 @@
+@startuml
+!include style.puml
+
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR_T4
+skinparam classBackgroundColor MODEL_COLOR
+
+package Model {
+ Class ModelManager
+ Class ModelHistory
+ Class HistoryInstance
+ Class AddressBook
+ class ModelDisplaySetting
+}
+
+ModelManager -> ModelHistory
+ModelHistory -down-> HistoryInstance
+HistoryInstance -down-> AddressBook
+HistoryInstance -down-> ModelDisplaySetting
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 85ac3ea2dee..c76582be55d 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -19,6 +19,7 @@ Interface AddressBookStorage <>
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
+Class JsonAdaptedEvent
Class JsonAdaptedTag
}
@@ -38,6 +39,8 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
+JsonSerializableAddressBook --> "*" JsonAdaptedEvent
JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonAdaptedEvent --> "*" JsonAdaptedTag
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index ecae4876432..1e88a949631 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -3,22 +3,26 @@
skinparam arrowThickness 1.1
skinparam arrowColor UI_COLOR_T4
skinparam classBackgroundColor UI_COLOR
+'skinparam linetype ortho
package UI <>{
Interface Ui <>
Class "{abstract}\nUiPart" as UiPart
Class UiManager
Class MainWindow
-Class HelpWindow
-Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
-Class StatusBarFooter
-Class CommandBox
+Class CalendarWindow
+Class ContactListPanel
+Class EventListPanel
+Class ContactCard
+Class EventCard
+package OtherUiComponents <> {
+Class HiddenComponent #FFFFFF
+}
}
package Model <> {
-Class HiddenModel #FFFFFF
+Class Event
+Class Contact
}
package Logic <> {
@@ -30,31 +34,30 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
-MainWindow *-down-> "1" CommandBox
-MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
-MainWindow *-down-> "1" StatusBarFooter
-MainWindow --> "0..1" HelpWindow
+Ui -[hidden]---> OtherUiComponents
+MainWindow *--> OtherUiComponents
+MainWindow *--> "1" ContactListPanel
+MainWindow *--> "1" EventListPanel
+MainWindow *--> "0..1" CalendarWindow
-PersonListPanel -down-> "*" PersonCard
+ContactListPanel -down-> "*" ContactCard
+EventListPanel -down-> "*" EventCard
MainWindow -left-|> UiPart
-ResultDisplay --|> UiPart
-CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
-StatusBarFooter --|> UiPart
-HelpWindow --|> UiPart
+ContactListPanel --|> UiPart
+EventListPanel --|> UiPart
+ContactCard --|> UiPart
+EventCard --|> UiPart
+CalendarWindow --|> UiPart
+OtherUiComponents --|> UiPart
-PersonCard ..> Model
+ContactCard ..> Contact
+EventCard ..> Event
+CalendarWindow --> Event : calendar of >
UiManager -right-> Logic
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
-HelpWindow -[hidden]left- CommandBox
-CommandBox -[hidden]left- ResultDisplay
-ResultDisplay -[hidden]left- StatusBarFooter
-
-MainWindow -[hidden]-|> UiPart
+'MainWindow -[hidden]-|> UiPart
+ContactListPanel -[hidden]left- EventListPanel
@enduml
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
index 96e30744d24..397c0c33ace 100644
--- a/docs/diagrams/UndoRedoState0.puml
+++ b/docs/diagrams/UndoRedoState0.puml
@@ -3,18 +3,23 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title Initial state
+title 1. Initial state
-package States {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__"
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
+State3 -[hidden]right-> State4
hide State2
hide State3
+hide State4
-class Pointer as "Current State" #FFFFF
-Pointer -up-> State1
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State1
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State1
@end
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 01fcb9b2b96..897c107c91f 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -3,20 +3,24 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title After command "delete 5"
+title 2. After command "edelete 5"
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__"
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
+State3 -[hidden]right-> State4
hide State3
+hide State4
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State2
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State2
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State2
@end
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index bccc230a5d1..459a5867fc6 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -3,18 +3,23 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title After command "add n/David"
+title 3. After command "cadd n/David"
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__"
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
+State3 -[hidden]right-> State4
-class Pointer as "Current State" #FFFFF
+hide State4
-Pointer -up-> State3
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State3
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State3
@end
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
index ea29c9483e4..587a256fe20 100644
--- a/docs/diagrams/UndoRedoState3.puml
+++ b/docs/diagrams/UndoRedoState3.puml
@@ -3,18 +3,23 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title After command "undo"
+title 4. After command "undo"
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__"
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
+State3 -[hidden]right-> State4
-class Pointer as "Current State" #FFFFF
+hide State4
-Pointer -up-> State2
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State2
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State3
@end
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
index 1b784cece80..6774b984bf4 100644
--- a/docs/diagrams/UndoRedoState4.puml
+++ b/docs/diagrams/UndoRedoState4.puml
@@ -3,18 +3,22 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title After command "list"
+title 5a. After command "redo"
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__"
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
+State3 -[hidden]right-> State4
+hide State4
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State2
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State3
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State3
@end
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
index 88927be32bc..502404c0723 100644
--- a/docs/diagrams/UndoRedoState5.puml
+++ b/docs/diagrams/UndoRedoState5.puml
@@ -3,19 +3,23 @@
skinparam ClassFontColor #000000
skinparam ClassBorderColor #000000
-title After command "clear"
+title 5b. After command "eclear"
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab3:AddressBook__"
+package ModelHistory {
+ class State1 as "__hi0:HistoryInstance__"
+ class State2 as "__hi1:HistoryInstance__"
+ class State3 as "__hi2:HistoryInstance__" #807f7d
+ class State4 as "__hi3:HistoryInstance__"
}
State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
+State2 -[hidden]right-> State4
+State4 -[hidden]right-> State3
-class Pointer as "Current State" #FFFFF
+class Pointer1 as "Current Size" #FFF
+Pointer1 -up-> State4
+class Pointer2 as "Max Size" #FFF
+Pointer2 -up-> State4
-Pointer -up-> State3
-note right on link: State ab2 deleted.
+note right on link: State hi2 cannot be accessed.
@end
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
index 410aab4e412..0591cfb0d99 100644
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ b/docs/diagrams/UndoSequenceDiagram.puml
@@ -1,4 +1,5 @@
@startuml
+
!include style.puml
box Logic LOGIC_COLOR_T1
@@ -9,8 +10,9 @@ end box
box Model MODEL_COLOR_T1
participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
+participant ":ModelHistory" as ModelHistory MODEL_COLOR
end box
+
[-> LogicManager : execute(undo)
activate LogicManager
@@ -30,15 +32,14 @@ deactivate AddressBookParser
LogicManager -> UndoCommand : execute()
activate UndoCommand
-UndoCommand -> Model : undoAddressBook()
+UndoCommand -> Model : undoHistory()
activate Model
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
+Model -> ModelHistory : undo()
+activate ModelHistory
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
+ModelHistory --> Model :
+deactivate ModelHistory
Model --> UndoCommand
deactivate Model
@@ -50,4 +51,5 @@ destroy UndoCommand
[<--LogicManager
deactivate LogicManager
+
@enduml
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..0b802536023 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -41,6 +41,8 @@ skinparam Class {
FontColor #FFFFFF
BorderThickness 1
BorderColor #FFFFFF
+ AttributeFontColor #FFFFFF
+ AttributeIconSize 0
StereotypeFontColor #FFFFFF
FontName Arial
}
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..5a7402a2fe6 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 1ec62caa2a5..e0696734a2c 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/CMarkActivityDiagram.png b/docs/images/CMarkActivityDiagram.png
new file mode 100644
index 00000000000..fcffb4d4aac
Binary files /dev/null and b/docs/images/CMarkActivityDiagram.png differ
diff --git a/docs/images/CMarkSequenceDiagram.png b/docs/images/CMarkSequenceDiagram.png
new file mode 100644
index 00000000000..d96e96ecfa0
Binary files /dev/null and b/docs/images/CMarkSequenceDiagram.png differ
diff --git a/docs/images/CUnmarkActivityDiagram.png b/docs/images/CUnmarkActivityDiagram.png
new file mode 100644
index 00000000000..551e45c790e
Binary files /dev/null and b/docs/images/CUnmarkActivityDiagram.png differ
diff --git a/docs/images/CUnmarkSequenceDiagram.png b/docs/images/CUnmarkSequenceDiagram.png
new file mode 100644
index 00000000000..51e4e17adb8
Binary files /dev/null and b/docs/images/CUnmarkSequenceDiagram.png differ
diff --git a/docs/images/CalendarSequenceDiagram.png b/docs/images/CalendarSequenceDiagram.png
new file mode 100644
index 00000000000..77556f5d4cc
Binary files /dev/null and b/docs/images/CalendarSequenceDiagram.png differ
diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png
deleted file mode 100644
index c08c13f5c8b..00000000000
Binary files a/docs/images/CommitActivityDiagram.png and /dev/null 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/EDeleteActivityDiagram.png b/docs/images/EDeleteActivityDiagram.png
new file mode 100644
index 00000000000..4f31dd10218
Binary files /dev/null and b/docs/images/EDeleteActivityDiagram.png differ
diff --git a/docs/images/EDeleteSequenceDiagram.png b/docs/images/EDeleteSequenceDiagram.png
new file mode 100644
index 00000000000..dc584468943
Binary files /dev/null and b/docs/images/EDeleteSequenceDiagram.png differ
diff --git a/docs/images/ELinkActivityDiagram.png b/docs/images/ELinkActivityDiagram.png
new file mode 100644
index 00000000000..0ceb9dd6c2a
Binary files /dev/null and b/docs/images/ELinkActivityDiagram.png differ
diff --git a/docs/images/ELinkSequenceDiagram.png b/docs/images/ELinkSequenceDiagram.png
new file mode 100644
index 00000000000..5226ed6d7f4
Binary files /dev/null and b/docs/images/ELinkSequenceDiagram.png differ
diff --git a/docs/images/EListActivityDiagram.png b/docs/images/EListActivityDiagram.png
new file mode 100644
index 00000000000..4eaaf4565f9
Binary files /dev/null and b/docs/images/EListActivityDiagram.png differ
diff --git a/docs/images/EListSequenceDiagram.png b/docs/images/EListSequenceDiagram.png
new file mode 100644
index 00000000000..c6b4c855378
Binary files /dev/null and b/docs/images/EListSequenceDiagram.png differ
diff --git a/docs/images/EventChangerClassDiagram.png b/docs/images/EventChangerClassDiagram.png
new file mode 100644
index 00000000000..fc1f676794e
Binary files /dev/null and b/docs/images/EventChangerClassDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index c3028aa1cda..bccf7cf608f 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 39d7aec4b33..4c7311e2dc1 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ModelHistory.png b/docs/images/ModelHistory.png
new file mode 100644
index 00000000000..18062c20d75
Binary files /dev/null and b/docs/images/ModelHistory.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 82c66f8f16e..c872879697d 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 91488fd1a0f..75168984408 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 4bb8b2ce591..26a9ddbf17a 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png
new file mode 100644
index 00000000000..534b79bfe24
Binary files /dev/null and b/docs/images/UndoRedoActivityDiagram.png differ
diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png
index 8f7538cd884..eadb61571d4 100644
Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ
diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png
index df9908d0948..239c3d629db 100644
Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ
diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png
index 36519c1015b..b4097379c00 100644
Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ
diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png
index 19959d01712..c82ca28d193 100644
Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ
diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png
index 4c623e4f2c5..53548380884 100644
Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ
diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png
index 84ad2afa6bd..8174a6119d3 100644
Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
index 6addcd3a8d9..bc20308750b 100644
Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ
diff --git a/docs/images/chunweii.png b/docs/images/chunweii.png
new file mode 100644
index 00000000000..3b7799a9374
Binary files /dev/null and b/docs/images/chunweii.png differ
diff --git a/docs/images/demo-screenshots/BookmarkContact.png b/docs/images/demo-screenshots/BookmarkContact.png
new file mode 100644
index 00000000000..32990480ca0
Binary files /dev/null and b/docs/images/demo-screenshots/BookmarkContact.png differ
diff --git a/docs/images/demo-screenshots/BookmarkEvents.png b/docs/images/demo-screenshots/BookmarkEvents.png
new file mode 100644
index 00000000000..a36162ba268
Binary files /dev/null and b/docs/images/demo-screenshots/BookmarkEvents.png differ
diff --git a/docs/images/demo-screenshots/Calendar.png b/docs/images/demo-screenshots/Calendar.png
new file mode 100644
index 00000000000..16224e38f3b
Binary files /dev/null and b/docs/images/demo-screenshots/Calendar.png differ
diff --git a/docs/images/demo-screenshots/ClickLinksContact.png b/docs/images/demo-screenshots/ClickLinksContact.png
new file mode 100644
index 00000000000..9d1cadeed3b
Binary files /dev/null and b/docs/images/demo-screenshots/ClickLinksContact.png differ
diff --git a/docs/images/demo-screenshots/ClickLinksEvent.png b/docs/images/demo-screenshots/ClickLinksEvent.png
new file mode 100644
index 00000000000..f773bfc6af1
Binary files /dev/null and b/docs/images/demo-screenshots/ClickLinksEvent.png differ
diff --git a/docs/images/demo-screenshots/UnmarkContacts.png b/docs/images/demo-screenshots/UnmarkContacts.png
new file mode 100644
index 00000000000..e4d8ca26fe0
Binary files /dev/null and b/docs/images/demo-screenshots/UnmarkContacts.png differ
diff --git a/docs/images/demo-screenshots/UnmarkEvents.png b/docs/images/demo-screenshots/UnmarkEvents.png
new file mode 100644
index 00000000000..9bbbcb9dd08
Binary files /dev/null and b/docs/images/demo-screenshots/UnmarkEvents.png differ
diff --git a/docs/images/demo-screenshots/addressIcon.png b/docs/images/demo-screenshots/addressIcon.png
new file mode 100644
index 00000000000..292b1c5e5b8
Binary files /dev/null and b/docs/images/demo-screenshots/addressIcon.png differ
diff --git a/docs/images/demo-screenshots/bookmarkIcon.png b/docs/images/demo-screenshots/bookmarkIcon.png
new file mode 100644
index 00000000000..7e736ef607e
Binary files /dev/null and b/docs/images/demo-screenshots/bookmarkIcon.png differ
diff --git a/docs/images/demo-screenshots/caddEx1.png b/docs/images/demo-screenshots/caddEx1.png
new file mode 100644
index 00000000000..7e58f88fcdc
Binary files /dev/null and b/docs/images/demo-screenshots/caddEx1.png differ
diff --git a/docs/images/demo-screenshots/caddEx2.png b/docs/images/demo-screenshots/caddEx2.png
new file mode 100644
index 00000000000..2a6908be2bc
Binary files /dev/null and b/docs/images/demo-screenshots/caddEx2.png differ
diff --git a/docs/images/demo-screenshots/ceditEx1.png b/docs/images/demo-screenshots/ceditEx1.png
new file mode 100644
index 00000000000..956e0bf9ee4
Binary files /dev/null and b/docs/images/demo-screenshots/ceditEx1.png differ
diff --git a/docs/images/demo-screenshots/ceditEx2.png b/docs/images/demo-screenshots/ceditEx2.png
new file mode 100644
index 00000000000..45346ddc462
Binary files /dev/null and b/docs/images/demo-screenshots/ceditEx2.png differ
diff --git a/docs/images/demo-screenshots/cfindEx1.png b/docs/images/demo-screenshots/cfindEx1.png
new file mode 100644
index 00000000000..7ddd86e15f3
Binary files /dev/null and b/docs/images/demo-screenshots/cfindEx1.png differ
diff --git a/docs/images/demo-screenshots/cfindEx2.png b/docs/images/demo-screenshots/cfindEx2.png
new file mode 100644
index 00000000000..0f27e6f4626
Binary files /dev/null and b/docs/images/demo-screenshots/cfindEx2.png differ
diff --git a/docs/images/demo-screenshots/clickableLinkExample.png b/docs/images/demo-screenshots/clickableLinkExample.png
new file mode 100644
index 00000000000..ae646253fd1
Binary files /dev/null and b/docs/images/demo-screenshots/clickableLinkExample.png differ
diff --git a/docs/images/demo-screenshots/clistEx.png b/docs/images/demo-screenshots/clistEx.png
new file mode 100644
index 00000000000..8d8fb5e3582
Binary files /dev/null and b/docs/images/demo-screenshots/clistEx.png differ
diff --git a/docs/images/demo-screenshots/cmarkEx.png b/docs/images/demo-screenshots/cmarkEx.png
new file mode 100644
index 00000000000..d1715be3dfa
Binary files /dev/null and b/docs/images/demo-screenshots/cmarkEx.png differ
diff --git a/docs/images/demo-screenshots/cmarkEx1.png b/docs/images/demo-screenshots/cmarkEx1.png
new file mode 100644
index 00000000000..2d302846ca9
Binary files /dev/null and b/docs/images/demo-screenshots/cmarkEx1.png differ
diff --git a/docs/images/demo-screenshots/cmarkEx2.png b/docs/images/demo-screenshots/cmarkEx2.png
new file mode 100644
index 00000000000..ab56f22dae3
Binary files /dev/null and b/docs/images/demo-screenshots/cmarkEx2.png differ
diff --git a/docs/images/demo-screenshots/commandSyntax.png b/docs/images/demo-screenshots/commandSyntax.png
new file mode 100644
index 00000000000..7b6fdba37d0
Binary files /dev/null and b/docs/images/demo-screenshots/commandSyntax.png differ
diff --git a/docs/images/demo-screenshots/contactCard.png b/docs/images/demo-screenshots/contactCard.png
new file mode 100644
index 00000000000..57826c233e5
Binary files /dev/null and b/docs/images/demo-screenshots/contactCard.png differ
diff --git a/docs/images/demo-screenshots/contactsIcon.png b/docs/images/demo-screenshots/contactsIcon.png
new file mode 100644
index 00000000000..82d8e0fbf64
Binary files /dev/null and b/docs/images/demo-screenshots/contactsIcon.png differ
diff --git a/docs/images/demo-screenshots/cunmarkEx1.png b/docs/images/demo-screenshots/cunmarkEx1.png
new file mode 100644
index 00000000000..29f29a08978
Binary files /dev/null and b/docs/images/demo-screenshots/cunmarkEx1.png differ
diff --git a/docs/images/demo-screenshots/cunmarkEx2.png b/docs/images/demo-screenshots/cunmarkEx2.png
new file mode 100644
index 00000000000..a1ba4739463
Binary files /dev/null and b/docs/images/demo-screenshots/cunmarkEx2.png differ
diff --git a/docs/images/demo-screenshots/cviewEx.png b/docs/images/demo-screenshots/cviewEx.png
new file mode 100644
index 00000000000..9b13c4df67e
Binary files /dev/null and b/docs/images/demo-screenshots/cviewEx.png differ
diff --git a/docs/images/demo-screenshots/descriptionIcon.png b/docs/images/demo-screenshots/descriptionIcon.png
new file mode 100644
index 00000000000..bc7bf0ea3e3
Binary files /dev/null and b/docs/images/demo-screenshots/descriptionIcon.png differ
diff --git a/docs/images/demo-screenshots/eaddEx.png b/docs/images/demo-screenshots/eaddEx.png
new file mode 100644
index 00000000000..44de8a2a70d
Binary files /dev/null and b/docs/images/demo-screenshots/eaddEx.png differ
diff --git a/docs/images/demo-screenshots/eeditEx.png b/docs/images/demo-screenshots/eeditEx.png
new file mode 100644
index 00000000000..fb7dc6fef9d
Binary files /dev/null and b/docs/images/demo-screenshots/eeditEx.png differ
diff --git a/docs/images/demo-screenshots/efindEx1.png b/docs/images/demo-screenshots/efindEx1.png
new file mode 100644
index 00000000000..ea4cd674100
Binary files /dev/null and b/docs/images/demo-screenshots/efindEx1.png differ
diff --git a/docs/images/demo-screenshots/efindEx2.png b/docs/images/demo-screenshots/efindEx2.png
new file mode 100644
index 00000000000..ab80fe8d041
Binary files /dev/null and b/docs/images/demo-screenshots/efindEx2.png differ
diff --git a/docs/images/demo-screenshots/elinkEx.png b/docs/images/demo-screenshots/elinkEx.png
new file mode 100644
index 00000000000..8a5ae6fe6ba
Binary files /dev/null and b/docs/images/demo-screenshots/elinkEx.png differ
diff --git a/docs/images/demo-screenshots/elistEx.png b/docs/images/demo-screenshots/elistEx.png
new file mode 100644
index 00000000000..c45f4372a24
Binary files /dev/null and b/docs/images/demo-screenshots/elistEx.png differ
diff --git a/docs/images/demo-screenshots/emailIcon.png b/docs/images/demo-screenshots/emailIcon.png
new file mode 100644
index 00000000000..bb21667f584
Binary files /dev/null and b/docs/images/demo-screenshots/emailIcon.png differ
diff --git a/docs/images/demo-screenshots/endTimeIcon.png b/docs/images/demo-screenshots/endTimeIcon.png
new file mode 100644
index 00000000000..ead7561a603
Binary files /dev/null and b/docs/images/demo-screenshots/endTimeIcon.png differ
diff --git a/docs/images/demo-screenshots/eunlinkEx1.png b/docs/images/demo-screenshots/eunlinkEx1.png
new file mode 100644
index 00000000000..f83f27552d8
Binary files /dev/null and b/docs/images/demo-screenshots/eunlinkEx1.png differ
diff --git a/docs/images/demo-screenshots/eunlinkEx2.png b/docs/images/demo-screenshots/eunlinkEx2.png
new file mode 100644
index 00000000000..a15c121e6d8
Binary files /dev/null and b/docs/images/demo-screenshots/eunlinkEx2.png differ
diff --git a/docs/images/demo-screenshots/eventCard.png b/docs/images/demo-screenshots/eventCard.png
new file mode 100644
index 00000000000..9155f386e1c
Binary files /dev/null and b/docs/images/demo-screenshots/eventCard.png differ
diff --git a/docs/images/demo-screenshots/eventIcon.png b/docs/images/demo-screenshots/eventIcon.png
new file mode 100644
index 00000000000..3b21f42c304
Binary files /dev/null and b/docs/images/demo-screenshots/eventIcon.png differ
diff --git a/docs/images/demo-screenshots/fileTab.png b/docs/images/demo-screenshots/fileTab.png
new file mode 100644
index 00000000000..7f563afbf0d
Binary files /dev/null and b/docs/images/demo-screenshots/fileTab.png differ
diff --git a/docs/images/demo-screenshots/helpTab.png b/docs/images/demo-screenshots/helpTab.png
new file mode 100644
index 00000000000..a66cc15a98f
Binary files /dev/null and b/docs/images/demo-screenshots/helpTab.png differ
diff --git a/docs/images/demo-screenshots/helpWindow.png b/docs/images/demo-screenshots/helpWindow.png
new file mode 100644
index 00000000000..8e5c8e7e709
Binary files /dev/null and b/docs/images/demo-screenshots/helpWindow.png differ
diff --git a/docs/images/demo-screenshots/homeFolder.png b/docs/images/demo-screenshots/homeFolder.png
new file mode 100644
index 00000000000..a8f04575d5e
Binary files /dev/null and b/docs/images/demo-screenshots/homeFolder.png differ
diff --git a/docs/images/demo-screenshots/invalidCommand.png b/docs/images/demo-screenshots/invalidCommand.png
new file mode 100644
index 00000000000..c32b28b5c72
Binary files /dev/null and b/docs/images/demo-screenshots/invalidCommand.png differ
diff --git a/docs/images/demo-screenshots/labelledSoconnectOverview.png b/docs/images/demo-screenshots/labelledSoconnectOverview.png
new file mode 100644
index 00000000000..aaa1d01d460
Binary files /dev/null and b/docs/images/demo-screenshots/labelledSoconnectOverview.png differ
diff --git a/docs/images/demo-screenshots/overview.png b/docs/images/demo-screenshots/overview.png
new file mode 100644
index 00000000000..e019a45e2b8
Binary files /dev/null and b/docs/images/demo-screenshots/overview.png differ
diff --git a/docs/images/demo-screenshots/phoneNumberIcon.png b/docs/images/demo-screenshots/phoneNumberIcon.png
new file mode 100644
index 00000000000..a6554546e1d
Binary files /dev/null and b/docs/images/demo-screenshots/phoneNumberIcon.png differ
diff --git a/docs/images/demo-screenshots/sortResult.png b/docs/images/demo-screenshots/sortResult.png
new file mode 100644
index 00000000000..e3dd1dc3aff
Binary files /dev/null and b/docs/images/demo-screenshots/sortResult.png differ
diff --git a/docs/images/demo-screenshots/startTimeIcon.png b/docs/images/demo-screenshots/startTimeIcon.png
new file mode 100644
index 00000000000..b55937e6ac8
Binary files /dev/null and b/docs/images/demo-screenshots/startTimeIcon.png differ
diff --git a/docs/images/demo-screenshots/successMessage.png b/docs/images/demo-screenshots/successMessage.png
new file mode 100644
index 00000000000..8cb4043d2c1
Binary files /dev/null and b/docs/images/demo-screenshots/successMessage.png differ
diff --git a/docs/images/demo-screenshots/tagIcon.png b/docs/images/demo-screenshots/tagIcon.png
new file mode 100644
index 00000000000..8faedbbe37f
Binary files /dev/null and b/docs/images/demo-screenshots/tagIcon.png differ
diff --git a/docs/images/demo-screenshots/telegramIcon.png b/docs/images/demo-screenshots/telegramIcon.png
new file mode 100644
index 00000000000..2b2059f25aa
Binary files /dev/null and b/docs/images/demo-screenshots/telegramIcon.png differ
diff --git a/docs/images/demo-screenshots/websiteIcon.png b/docs/images/demo-screenshots/websiteIcon.png
new file mode 100644
index 00000000000..53771fdf8ad
Binary files /dev/null and b/docs/images/demo-screenshots/websiteIcon.png differ
diff --git a/docs/images/gordon25.png b/docs/images/gordon25.png
new file mode 100644
index 00000000000..f3a09b311eb
Binary files /dev/null and b/docs/images/gordon25.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/janjanchen.png b/docs/images/janjanchen.png
new file mode 100644
index 00000000000..95abf543029
Binary files /dev/null and b/docs/images/janjanchen.png differ
diff --git a/docs/images/pcgiang.png b/docs/images/pcgiang.png
new file mode 100644
index 00000000000..f60a4469cc8
Binary files /dev/null and b/docs/images/pcgiang.png differ
diff --git a/docs/images/xiangjunn.png b/docs/images/xiangjunn.png
new file mode 100644
index 00000000000..10d170b867a
Binary files /dev/null and b/docs/images/xiangjunn.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..0288f34f15c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,20 @@
---
layout: page
-title: AddressBook Level-3
+title: SoConnect
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![Java CI](https://github.com/AY2122S1-CS2103T-W15-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2122S1-CS2103T-W15-3/tp/actions/workflows/gradle.yml)
+[![codecov](https://codecov.io/gh/AY2122S1-CS2103T-W15-3/tp/branch/master/graph/badge.svg?token=40MOICZDNE)](https://codecov.io/gh/AY2122S1-CS2103T-W15-3/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-* 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.
+**SoConnect is a desktop application for managing your contacts and events.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+
+* If you are interested in using SoConnect, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing SoConnect, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5), [CalendarFX](https://github.com/dlsc-software-consulting-gmbh/CalendarFX)
diff --git a/docs/team/chunweii.md b/docs/team/chunweii.md
new file mode 100644
index 00000000000..50a808be3d5
--- /dev/null
+++ b/docs/team/chunweii.md
@@ -0,0 +1,53 @@
+---
+layout: page
+title: Chun Wei's Project Portfolio Page
+---
+
+### Project: SoConnect
+
+SoConnect is a **desktop app for SoC students to manage contacts of Professors and Teaching Assistants,
+as well as to keep track of noteworthy events, optimized for use via a _Command Line Interface (CLI)_** while still having
+the benefits of a _Graphical User Interface (GUI)_. This project is part of the [CS2103T](https://nus-cs2103-ay2122s1.github.io/website/) team project requirements for AY2021/2022 Semester 1.
+
+The non-exhaustive list given below are my contributions to the project. All features come with their respective unit tests.
+
+* **New Feature**: Changed fields to `Contact` model (Previously known as `Person`).
+ * What it does: allows the user to save details about the telegram handle and zoom meeting link of the saved contact, and allowing the user to omit certain details about the contact.
+ * Justification: Given that the users are mainly School of Computing (SoC) students in NUS, the product would be significantly more relevant if it allows students to save details like telegram handles and Zoom meeting links of their friends, professors and tutors. Telegram and Zoom are widely used applications in NUS. Also, since students may not be able to obtain every single detail about a contact they want to save, for example, many tutors do not share their addresses and phone numbers. By making certain fields optional, our product becomes more suitable for SoC students to use.
+ * Highlights: This enhancement affects how certain commands such as add or edit will work, since these commands do not accept optional fields, except for tags, in the original Address Book 3 implementation.
+
+* **New Feature**: Added the [calendar user interface](../DeveloperGuide.html#calendar-ui-feature).
+ * What it does: Allows the user to view their saved events in a clean visual calendar user interface. An added feature is that while the calendar is open, the calendar will automatically update itself if the user make changes to the list of events.
+ * Justification: This feature improves the product significantly because a user may find it difficult to plan their appointments by just looking at the list of events in the right event list panel.
+ * Highlights: This enhancement requires the understanding of a third-party GUI framework. In addition, the implementation of the auto-update for the calendar is non-trivial, since a poor implementation can introduce coupling and turn the code ugly.
+ * Credits: [CalendarFX](https://github.com/dlsc-software-consulting-gmbh/CalendarFX). Inspiration is from team projects from other teams in the past and [present](https://ay2122s1-cs2103t-f13-3.github.io/tp/).
+
+* **New Feature**: Sorting and displaying upcoming events
+ * What it does: Allows the user to sort their events based on start time and display events that are ongoing or occuring soon.
+ * Justification: Having an unsorted list of events will make it difficult for the user to find out what is their next activity for the day or the week.
+ * Highlights: This is more than just a simple sorting by date and time, since there is also a need to filter out old events. Furthermore, it can interfere with other commands that will alter the order of the list, such as `emark`.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=w15-3&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=chunweii&tabRepo=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+* **Project management**:
+ * Set up project dashboard and project milestones on GitHub.
+ * Oversaw the entire project as the team leader.
+ * Adopted the [long-lived feature branch workflow](https://github.com/nus-cs2103-AY2122S1/forum/issues/325#issuecomment-946409090) in [v1.2b](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/90) and the feature branch workflow in v1.3 onwards, to improve efficiency of the team contributions while maintaining protection of the master branch.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `calendar`([\#122](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/122)), and the instructions to use the user guide. ([\#236](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/236))
+ * Add instructions on how to open the jar file in terminal. [\#162](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/162)
+ * Developer Guide:
+ * Added implementation details of the [calendar user interface](../DeveloperGuide.html#calendar-ui-feature) and heavily edited the [List Events feature](../DeveloperGuide.html#list-events-feature).
+ * Updated the [UI design section](../DeveloperGuide.html#ui-component).
+ * Added use case 11 in [#217](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/217).
+
+* **Community**:
+ * Notable PRs reviewed (with non-trivial review comments): [\#147](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/147), [\#135](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/135), [\#94](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/94). See others [here](https://github.com/AY2122S1-CS2103T-W15-3/tp/pulls?page=1&q=is%3Apr+is%3Aclosed+reviewed-by%3Achunweii).
+ * Contributed ideas and code to improve code quality for undo/redo feature and bug fix: [\#219](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/219)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S1/forum/issues/190#issuecomment-913172698), [2](https://github.com/nus-cs2103-AY2122S1/forum/issues/267#issuecomment-925130845))
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2122S1-CS2103T-T12-4/tp/issues/159), [2](https://github.com/AY2122S1-CS2103T-T12-4/tp/issues/163))
+
+* **Tools**:
+ * Integrated a third party framework (CalendarFX) to the project ([\#42](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/122))
diff --git a/docs/team/gordon25.md b/docs/team/gordon25.md
new file mode 100644
index 00000000000..939c81a5ede
--- /dev/null
+++ b/docs/team/gordon25.md
@@ -0,0 +1,70 @@
+---
+layout: page
+title: Gordon Yit Hongyao's Project Portfolio Page
+---
+
+### Project: SoConnect
+
+SoConnect is a **desktop app for SoC students to manage contacts of Professors and Teaching Assistants, as well as to keep track of noteworthy events, optimized for use via a _Command Line Interface (CLI)_** while still having the benefits of a _Graphical User Interface (GUI)_.
+
+This project is part of the [CS2103T](https://nus-cs2103-ay2122s1.github.io/website/) team project requirements for AY2021/2022 Semester 1.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to mark contacts and events.
+ * What it does: allows the user to mark one or more contacts / events at a time. The marked contacts / events will appear at the top of the contact / event list.
+ * Justification: This feature improves the product significantly because it saves the user time from having to scroll through the list or using the `find` command to find contacts / events that they use frequently and the app should provide a convenient way for users to refer to them.
+ * Highlights: This enhancement affects existing commands commands to be added in future. It required an in-depth analysis of design alternatives.
+
+* **New Feature**: Added ability to unmark contacts and events.
+ * What it does: allows the users to unmark one or more marked contacts / events at a time. The newly unmarked contacts / events will appear after all marked contacts / events in the list.
+ * Justification: This feature improves the product significantly because it allows the user to keep the number of marked contacts / events small and unmark contacts / events they no longer reference frequently, saving them time from having to look through a long list of marked contacts / events.
+ * Highlights: This enhancement affects existing commands commands to be added in future. It required an in-depth analysis of design alternatives.
+
+
+
+* **New Feature**: Added ability to search contacts / events based on other fields
+ * What it does: allows the users to search for contacts / events based on other fields other than the the contact / event name.
+ * Justification: This feature improves the product significantly because it gives users more freedom in how they search for contacts / events. For example, they can for search for an event based on its location or description.
+ * Highlights: This enhancement affects existing commands commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
+
+* **New Feature**: Added ability to list only certain fields of events
+ * What it does: allows the users to only list certain types of fields of each event.
+ * Justification: This feature improves the product significantly because it allows user to focus on certain fields of events they are interested in instead of looking at the entire list.
+ * Highlights: This enhancement affects existing commands commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=w15-3&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=zoom&tabAuthor=janjanchen&tabRepo=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&zA=janjanchen&zR=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&zACS=199.78947368421052&zS=2021-09-17&zFS=w15-3&zU=2021-11-06&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false)
+
+* **Project management**:
+ * Managed issue tracker, created issues, linked them to the relevant milestone, tags and project dashboard and assigned them to the relevant teammate based on the breakdown of work discussed in the team meeting. Where possible, included task descriptions in the issue for easy reference.
+
+* **Enhancements to existing features**:
+ * Updated contact search features to allow users to find by other fields: [\#135](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/135)
+ * Modified storage class to store events: [\#53](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/53)
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `eedit`, `efind` and `edelete`: [\#29](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/29)
+ * Updated documentation for the features `cmark`, `cunmark`, added screenshots for `edelete`, `efind`, `elist`: [\#213](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/213)
+ * Developer Guide:
+ * Added implementation details for `elist` [\#132](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/132)
+ * Added implementation details for `cmark` and `cunmark` [\#217](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/217)
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#33](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/33), [\#145](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/145), [\#148](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/148)
+
+ * Reported bugs and suggestions for other teams in the class : [\#169](https://github.com/AY2122S1-CS2103T-T13-3/tp/issues/169), [\#178](https://github.com/AY2122S1-CS2103T-T13-3/tp/issues/178), [\#181](https://github.com/AY2122S1-CS2103T-T13-3/tp/issues/181)
+
+
+© 2021 GitHub, Inc.
+Terms
+Privacy
+Security
+Status
+Docs
+Contact GitHub
+Pricing
+API
+Training
+Blog
+About
diff --git a/docs/team/janjanchen.md b/docs/team/janjanchen.md
new file mode 100644
index 00000000000..6b212b66d3f
--- /dev/null
+++ b/docs/team/janjanchen.md
@@ -0,0 +1,65 @@
+---
+layout: page
+title: Janice Chen's Project Portfolio Page
+---
+
+### Project: SoConnect
+
+SoConnect is a **desktop app for SoC students to manage contacts of Professors and Teaching Assistants,
+as well as to keep track of noteworthy events, optimized for use via a _Command Line Interface (CLI)_** while still having
+the benefits of a _Graphical User Interface (GUI)_.
+
+This project is part of the [CS2103T](https://nus-cs2103-ay2122s1.github.io/website/) team project requirements for AY2021/2022 Semester 1.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the `Event` model, together with the ability to clear, delete and finding events.
+ * What it does: allows the user to save events that consists of details such as event date and time, description, address.
+ * Justification: This feature improves the product significantly because a user can save events next to the contact list using the same platform. This feature saves the trouble of the students from using multiple platforms to separately store the contacts and events.
+ * Highlights: This enhancement affects certain existing commands and how new commands should be implemented, since the model manager will need to take in both `Contact` and `Event`. `Event` and `Contact` also owns different fields, which adds more complexity in how the commands should work.
+
+* **New Feature**: Added the help window user interface
+ * What it does: allows the user to view the command summary of SoConnect in a different visual interface.
+ * Justification: This feature improves the product significantly because a user (especially new user) might find it challenging to remember all commands provided by SoConnect.
+ * Highlights: This enhancement requires the understanding of a third-party GUI framework.
+ * Credits: Text wrapping feature of help window is adapted from [@author:James_D](https://stackoverflow.com/questions/22732013/javafx-tablecolumn-text-wrapping)
+
+* **New Feature**: Viewing a specific contact or event
+ * What it does: allows the user to view a specific contact or event with all details fully shown
+ * Justification: This feature improves the product significantly because some information saved in the contact or event might be too long and the information will be truncated by default.
+ * Highlights: This enhancement requires new implementation of how to filter out all the other contacts/ events using index only, while changing the GUI framework setting to wrap text when it is in View Mode. Corresponding changes to the GUI setting is also needed to automatically disable View mode.
+
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=w15-3&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=zoom&tabAuthor=janjanchen&tabRepo=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&zA=janjanchen&zR=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&zACS=199.78947368421052&zS=2021-09-17&zFS=w15-3&zU=2021-11-06&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false)
+
+
+
+* **Project management**:
+ * Added documentation for `How to use SoConnect User Guide`, `Overview of SoConnect` , `List of Prefixes` and `Glossary` [\#139](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/139) and [\#236](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/236)
+ * Added screenshots for User Guide
+ * Changed logging and json file name [\#98](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/98)
+
+
+
+* **Enhancements to existing features**:
+ * Enable partial word search for events and contacts
+ * What it does: allows the user to search information on SoConnect without the need to specify the keyword fully
+ * Justification: This feature improves the product significantly because user does not need to know the full word they want to search for within the contact/ event list.
+
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `cadd`, `cedit`, `clist` and `cfind` [\#11](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/11)
+ * Added documentation for the features `cview` and `eview` [\#78](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/78)
+ * Added documentation for the features `help` [\#139](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/139)
+ * Did cosmetic tweaks to the document.
+ * Developer Guide:
+ * Added implementation details of the `edelete` feature and `Model`[\#127](https://github.com/AY2122S1-CS2103T-W15-3/tp/issues/127)
+
+
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#117](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/117)
+
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/pcgiang.md b/docs/team/pcgiang.md
new file mode 100644
index 00000000000..39a414b76f0
--- /dev/null
+++ b/docs/team/pcgiang.md
@@ -0,0 +1,51 @@
+---
+layout: page
+title: Pham Chau Giang's Project Portfolio Page
+---
+
+### Project: SoConnect
+
+SoConnect is a **desktop app for SoC students to manage contacts of Professors and Teaching Assistants,
+as well as to keep track of noteworthy events, optimized for use via a _Command Line Interface (CLI)_** while still having
+the benefits of a _Graphical User Interface (GUI)_.
+
+This project is part of the [CS2103T](https://nus-cs2103-ay2122s1.github.io/website/) team project requirements for AY2021/2022 Semester 1.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Worked together with Chun Wei to add the ability to undo/redo previous commands.
+ * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
+ * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
+ * Highlights: This enhancement affects existing commands and commands to be added in future. The implementation was challenging as it requires an in-depth analysis of how commands interact and are displayed in order to come up with the most suited implementation.
+
+
+* **New Feature**: Enabled fields to be copied.
+ * What it does: allows user to copy important information or open any links simply by clicking on a field label.
+ * Justification: This feature improves the product significantly because a user can conveniently get important information and use it elsewhere without having to manually copy the fields.
+ * Highlights: This enhancement requires understanding of GUI framework.
+
+
+* **New Feature**: Added a feature that allow colourful tags for contacts and events.
+ * What it does: displays different tag names with different colours such that the user can easily identify the different tags names.
+ * Justification: This feature improves the UI of the product significantly and aids the user in differentiating the different tags.
+ * Highlights: This enhancement requires understanding of GUI framework.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=pcgiang&tabRepo=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+
+* **Project management**:
+ * Set up project repo
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the feature `undo` and `redo` [\#133](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/133)
+ * Checked and fixed bugs in Managing Events Section [\#236](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/236)
+ * Developer Guide:
+ * Added implementation details of the `undo/redo` feature [\#217](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/217)
+ * Added manual test cases [\#217](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/217)
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#132](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/132), [\#165](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/165)
+
diff --git a/docs/team/xiangjunn.md b/docs/team/xiangjunn.md
new file mode 100644
index 00000000000..5e7c0b853e6
--- /dev/null
+++ b/docs/team/xiangjunn.md
@@ -0,0 +1,58 @@
+---
+layout: page
+title: Xiang Jun's Project Portfolio Page
+---
+
+### Project: SoConnect
+
+SoConnect is a **desktop app for SoC students to manage contacts of Professors and Teaching Assistants,
+as well as to keep track of noteworthy events, optimized for use via a _Command Line Interface (CLI)_** while still
+having the benefits of a _Graphical User Interface (GUI)_. This project is part of the
+[CS2103T](https://nus-cs2103-ay2122s1.github.io/website/) team project requirements for AY2021/2022 Semester 1.
+
+Given below are my notable contributions to the project.
+
+* **New Feature**: Added the ability to link an event to one or more contacts.
+ * What it does: allows the user to choose an event and link related contacts to it.
+ * Justification: An event is likely to have people that are hosting it. Therefore, the ability to view the contacts
+ of an event allows the user to easily contact the group of people that are hosting the event. This feature
+ connects contact and event together, making the product more cohesive.
+ * Highlights: This enhancement affects existing commands such as EDeleteCommand, CEditCommand and EClearCommand.
+ What these commands have in common is that they change the contacts/events in the contact/event list after
+ execution. This is problematic because these can result in change in the links as well. Therefore, the
+ implementation was challenging as it required changes to existing commands to take into account the links as well.
+
+* **New Feature**: Added the ability to delete a range of contacts.
+ * What it does: allows the user to delete either one contact or an inclusive range of consecutive contacts.
+ * Justification: It is troublesome for users to delete the contact one by one. Allowing them to delete a range of contacts
+ is more user-friendly.
+ * Highlights: This feature works well with the existing feature which can filter the contact list because user can delete the contacts from that filtered list with ease.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=xiangjunn&tabRepo=AY2122S1-CS2103T-W15-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+* **Project management**:
+ * Add branch protection rules to enforce certain requirements.
+ * Ensures that a branch has to pass CI before it can be merged into the `master` branch.
+ * Requires at least one reviewer's approval before pull request reviews can be merged.
+ * Ensures that a branch is up to date with `master` branch before it can be merged.
+ * Disallow push commits to `master` branch directly.
+ * Makes sure that team members are following the proper way of reviewing and merging pull requests.
+ * Facilitate closing of milestones and tagging the relevant commits with the correct version.
+
+* **Enhancements to existing features**:
+ * Improvements to the GUI (Pull requests [\#45](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/45), [\#149](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/149))
+ * Enhance CDeleteCommand to allow for deleting over a range of values. Creates a range class for this purpose. (Pull requests [\#87](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/87))
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `cdelete`, `cclear`, `eadd` and `elist` (Pull request [\#19](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/19))
+ * Added documentation for the feature `elink` (Pull request [\#131](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/131))
+ * Developer Guide:
+ * Added documentation for product scope and user stories (Pull request [\#33](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/33))
+ * Added implementation details of the `elink` feature, which include sequence diagram, activity diagram and design considerations.
+ * Added instruction for manual testing for `elink` feature.
+ * Added use cases for editing events and linking an event to one or more contacts
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#51](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/51), [\#142](https://github.com/AY2122S1-CS2103T-W15-3/tp/pull/142)
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2122S1-CS2103-T16-2/tp/issues/160), [2](https://github.com/AY2122S1-CS2103-T16-2/tp/issues/145), [3](https://github.com/AY2122S1-CS2103-T16-2/tp/issues/159))
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..8fe957b1eec 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+The `address` field in `Person` is actually an instance of the `seedu.address.model.common.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..a6ea2c089c2 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -48,7 +48,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing SoConnect ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -173,9 +173,10 @@ public void start(Stage primaryStage) {
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping SoConnect ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
+ storage.saveAddressBook(model.getAddressBook());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
}
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java
index 431e7185e76..1f9e613a141 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/address/commons/core/LogsCenter.java
@@ -18,7 +18,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "soconnect.log";
private static Level currentLogLevel = Level.INFO;
private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..f66605533e7 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -7,7 +7,18 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX = "The contact index provided is invalid";
+ public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid";
+ public static final String MESSAGE_START_MORE_THAN_END_INDEX = "The start index should not be more than end index";
+ public static final String MESSAGE_CONTACTS_LISTED_OVERVIEW = "%1$d contacts listed!";
+ public static final String MESSAGE_EVENTS_LISTED_OVERVIEW = "%1$d events listed!";
+ public static final String MESSAGE_CONTACT_LINK_OPENED = "Contact %1$s link opened in browser";
+ public static final String MESSAGE_EVENT_LINK_OPENED = "Event %1$s link opened in browser";
+ public static final String MESSAGE_CONTACT_LINK_NOT_FOUND = "Contact %1$s link not found!";
+ public static final String MESSAGE_EVENT_LINK_NOT_FOUND = "Event %1$s link not found!";
+ public static final String MESSAGE_DUPLICATE_INDEXES_PROVIDED = "There are duplicates in the indexes provided!";
+ public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+ public static final String MESSAGE_INVALID_RANGE = "Range given is invalid";
+ public static final String MESSAGE_CONTACT_FIELD_COPIED = "Contact %1$s copied to clipboard!";
+ public static final String MESSAGE_EVENT_FIELD_COPIED = "Event %1$s copied to clipboard!";
}
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java
index 19536439c09..e1c8859d9c8 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/seedu/address/commons/core/index/Index.java
@@ -45,10 +45,25 @@ public static Index fromOneBased(int oneBasedIndex) {
return new Index(oneBasedIndex - 1);
}
+ /**
+ * Compares if this index is more than another index.
+ *
+ * @param index The index to compare to.
+ * @return true if this index is more than the index compared; false otherwise.
+ */
+ public boolean isMoreThan(Index index) {
+ return this.zeroBasedIndex > index.zeroBasedIndex;
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
|| (other instanceof Index // instanceof handles nulls
&& zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check
}
+
+ @Override
+ public int hashCode() {
+ return zeroBasedIndex;
+ }
}
diff --git a/src/main/java/seedu/address/commons/core/range/Range.java b/src/main/java/seedu/address/commons/core/range/Range.java
new file mode 100644
index 00000000000..60b38f41965
--- /dev/null
+++ b/src/main/java/seedu/address/commons/core/range/Range.java
@@ -0,0 +1,49 @@
+package seedu.address.commons.core.range;
+
+import seedu.address.commons.core.index.Index;
+
+public class Range {
+ private Index start;
+ private Index end;
+
+ /**
+ * Constructs an inclusive range from start index to end index.
+ *
+ * @param start An index representing the start.
+ * @param end An index representing the end.
+ */
+ public Range(Index start, Index end) {
+ assert start.getZeroBased() >= 0;
+ assert end.getZeroBased() >= 0;
+ if (start.isMoreThan(end)) {
+ throw new IndexOutOfBoundsException();
+ }
+ this.start = start;
+ this.end = end;
+ }
+
+ public Index getStart() {
+ return start;
+ }
+
+ public Index getEnd() {
+ return end;
+ }
+
+ /**
+ * Creates a Range object with start and end indexes representing the same index.
+ *
+ * @param index The index to be converted.
+ */
+ public static Range convertFromIndex(Index index) {
+ return new Range(index, index);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Range // instanceof handles nulls
+ && start.equals(((Range) other).start)
+ && (end.equals(((Range) other).end))); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..57e4a341490 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -5,7 +5,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.Arrays;
+import java.util.Locale;
/**
* Helper functions for handling strings.
@@ -14,11 +14,12 @@ public class StringUtil {
/**
* Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
+ * Ignores case, and a full word match is not required.
*
examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
- * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+ * containsWordIgnoreCase("ABc def", "AB") == true
+ * containsWordIgnoreCase("ABc def", "Gh") == false
*
* @param sentence cannot be null
* @param word cannot be null, cannot be empty, must be a single word
@@ -27,15 +28,13 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
requireNonNull(word);
- String preppedWord = word.trim();
+ String preppedWord = word.trim().toLowerCase(Locale.ROOT);
checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word");
- String preppedSentence = sentence;
- String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
+ String preppedSentence = sentence.toLowerCase(Locale.ROOT);
- return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ return preppedSentence.contains(preppedWord);
}
/**
@@ -65,4 +64,18 @@ public static boolean isNonZeroUnsignedInteger(String s) {
return false;
}
}
+
+ /**
+ * Returns true if {@code s} represents a valid range; false otherwise.
+ */
+ public static boolean isValidRange(String s) {
+ requireNonNull(s);
+ if (s.matches("\\d+-\\d+")) {
+ String[] indexes = s.split("-");
+ int start = Integer.parseInt(indexes[0]);
+ int end = Integer.parseInt(indexes[1]);
+ return start > 0 && start <= end;
+ }
+ return false;
+ }
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..1ee6cbf7452 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,7 +8,10 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
/**
* API of the Logic component
@@ -31,7 +34,10 @@ public interface Logic {
ReadOnlyAddressBook getAddressBook();
/** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
+ ObservableList getFilteredContactList();
+
+ /** Returns an unmodifiable view of the filtered list of events */
+ ObservableList getFilteredEventList();
/**
* Returns the user prefs' address book file path.
@@ -43,8 +49,30 @@ public interface Logic {
*/
GuiSettings getGuiSettings();
+ /**
+ * Returns the display settings of the events.
+ */
+ EventDisplaySetting getEventDisplaySetting();
+
+ /**
+ * Returns the display settings of the contacts.
+ */
+ ContactDisplaySetting getContactDisplaySetting();
+
/**
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ /** Changes the filter to the model so that only contacts linked to {@code event} will be shown. */
+ void filterContactsWithLinksToEvent(Event event);
+
+ /** Changes the filter to the model so that only events linked to {@code contact} will be shown. */
+ void filterEventsWithLinkToContact(Contact contact);
+
+ /** Changes the filter of the contacts to show all contacts. */
+ void resetFilterOfContacts();
+
+ /** Changes the filter of the events to show all events. */
+ void resetFilterOfEvents();
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..a3d7f211f43 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -9,12 +9,16 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.AddressBookParser;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
import seedu.address.storage.Storage;
/**
@@ -44,6 +48,9 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
CommandResult commandResult;
Command command = addressBookParser.parseCommand(commandText);
commandResult = command.execute(model);
+ if (command instanceof Undoable) {
+ model.commitHistory();
+ }
try {
storage.saveAddressBook(model.getAddressBook());
@@ -60,8 +67,13 @@ public ReadOnlyAddressBook getAddressBook() {
}
@Override
- public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
+ public ObservableList getFilteredContactList() {
+ return model.getFilteredContactList();
+ }
+
+ @Override
+ public ObservableList getFilteredEventList() {
+ return model.getFilteredEventList();
}
@Override
@@ -74,8 +86,38 @@ public GuiSettings getGuiSettings() {
return model.getGuiSettings();
}
+ @Override
+ public EventDisplaySetting getEventDisplaySetting() {
+ return model.getEventDisplaySetting();
+ }
+
+ @Override
+ public ContactDisplaySetting getContactDisplaySetting() {
+ return model.getContactDisplaySetting();
+ }
+
@Override
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ @Override
+ public void filterContactsWithLinksToEvent(Event event) {
+ model.updateFilteredContactList(contact -> contact.getLinkedEvents().contains(event.getUuid()));
+ }
+
+ @Override
+ public void filterEventsWithLinkToContact(Contact contact) {
+ model.updateFilteredEventList(event -> event.getLinkedContacts().contains(contact.getUuid()));
+ }
+
+ @Override
+ public void resetFilterOfContacts() {
+ model.rerenderContactCards(true);
+ }
+
+ @Override
+ public void resetFilterOfEvents() {
+ model.rerenderEventCards(true);
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
deleted file mode 100644
index 71656d7c5c8..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Adds a person to the address book.
- */
-public class AddCommand extends Command {
-
- public static final String COMMAND_WORD = "add";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
- + PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
- + PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
-
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
-
- private final Person toAdd;
-
- /**
- * Creates an AddCommand to add the specified {@code Person}
- */
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
-
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.addPerson(toAdd);
- return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddCommand // instanceof handles nulls
- && toAdd.equals(((AddCommand) other).toAdd));
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
deleted file mode 100644
index 9c86b1fa6e4..00000000000
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-
-/**
- * Clears the address book.
- */
-public class ClearCommand extends Command {
-
- public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..548d3ea4537 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -2,8 +2,12 @@
import static java.util.Objects.requireNonNull;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
+import seedu.address.model.event.EventChanger;
+
/**
* Represents the result of a command execution.
*/
@@ -11,19 +15,36 @@ public class CommandResult {
private final String feedbackToUser;
- /** Help information should be shown to the user. */
+ /**
+ * Help information should be shown to the user.
+ */
private final boolean showHelp;
- /** The application should exit. */
+ /**
+ * The application should exit.
+ */
private final boolean exit;
+ /**
+ * The calendar should be shown to the user.
+ */
+ private final boolean showCalendar;
+
+ /**
+ * The list of all events to be changed. By default an empty list
+ */
+ private final List eventChangerList;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean showCalendar) {
+ assert numberOfTrueAtMost1(showHelp, exit, showCalendar) : "Invalid combination of boolean values.";
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.showCalendar = showCalendar;
+ this.eventChangerList = new ArrayList<>();
}
/**
@@ -31,7 +52,19 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
+ * and other fields set to their default value.
+ */
+ public CommandResult(String feedbackToUser, List eventChangerList) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = false;
+ this.exit = false;
+ this.showCalendar = false;
+ this.eventChangerList = requireNonNull(eventChangerList);
}
public String getFeedbackToUser() {
@@ -46,6 +79,14 @@ public boolean isExit() {
return exit;
}
+ public boolean isShowCalendar() {
+ return showCalendar;
+ }
+
+ public List getEventChangerList() {
+ return eventChangerList;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -59,8 +100,10 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
- && showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && showHelp == otherCommandResult.showHelp
+ && exit == otherCommandResult.exit
+ && showCalendar == otherCommandResult.showCalendar
+ && eventChangerList.equals(otherCommandResult.eventChangerList);
}
@Override
@@ -68,4 +111,25 @@ public int hashCode() {
return Objects.hash(feedbackToUser, showHelp, exit);
}
+ /**
+ * For assertion checking.
+ */
+ private boolean numberOfTrueAtMost1(boolean first, boolean second, boolean third) {
+ // Calculated using k-map to obtain disjunctive normal form.
+ return (!first && !second) || (!first && !third) || (!second && !third);
+ }
+
+ /**
+ * For testing purposes.
+ */
+ @Override
+ public String toString() {
+ return "CommandResult{"
+ + "feedbackToUser='" + feedbackToUser + '\''
+ + ", showHelp=" + showHelp
+ + ", exit=" + exit
+ + ", showCalendar=" + showCalendar
+ + ", eventChangerList=" + eventChangerList
+ + '}';
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
deleted file mode 100644
index 02fd256acba..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Deletes a person identified using it's displayed index from the address book.
- */
-public class DeleteCommand extends Command {
-
- public static final String COMMAND_WORD = "delete";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
-
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
-
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
deleted file mode 100644
index 7e36114902f..00000000000
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Edits the details of an existing person in the address book.
- */
-public class EditCommand extends Command {
-
- public static final String COMMAND_WORD = "edit";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
- + "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
-
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
- public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
-
- private final Index index;
- private final EditPersonDescriptor editPersonDescriptor;
-
- /**
- * @param index of the person in the filtered person list to edit
- * @param editPersonDescriptor details to edit the person with
- */
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
-
- this.index = index;
- this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
-
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
-
- /**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
- * edited with {@code editPersonDescriptor}.
- */
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
-
- Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
- Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
- Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
- Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
- }
-
- /**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
- */
- public static class EditPersonDescriptor {
- private Name name;
- private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
-
- public EditPersonDescriptor() {}
-
- /**
- * Copy constructor.
- * A defensive copy of {@code tags} is used internally.
- */
- public EditPersonDescriptor(EditPersonDescriptor toCopy) {
- setName(toCopy.name);
- setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
- }
-
- /**
- * Returns true if at least one field is edited.
- */
- public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
- }
-
- public void setName(Name name) {
- this.name = name;
- }
-
- public Optional getName() {
- return Optional.ofNullable(name);
- }
-
- public void setPhone(Phone phone) {
- this.phone = phone;
- }
-
- public Optional getPhone() {
- return Optional.ofNullable(phone);
- }
-
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- public Optional getAddress() {
- return Optional.ofNullable(address);
- }
-
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
- }
-
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- // state check
- EditPersonDescriptor e = (EditPersonDescriptor) other;
-
- return getName().equals(e.getName())
- && getPhone().equals(e.getPhone())
- && getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
- }
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index d6b19b0a0de..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof FindCommand // instanceof handles nulls
- && predicate.equals(((FindCommand) other).predicate)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/Undoable.java b/src/main/java/seedu/address/logic/commands/Undoable.java
new file mode 100644
index 00000000000..4291f3e1325
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/Undoable.java
@@ -0,0 +1,5 @@
+package seedu.address.logic.commands;
+
+/** Represents a command that can be undone. */
+public interface Undoable {
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CAddCommand.java b/src/main/java/seedu/address/logic/commands/contact/CAddCommand.java
new file mode 100644
index 00000000000..7285c04edaa
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CAddCommand.java
@@ -0,0 +1,76 @@
+package seedu.address.logic.commands.contact;
+
+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.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+
+/**
+ * Adds a contact to the address book.
+ */
+public class CAddCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cadd";
+ public static final String PARAMETERS = PREFIX_NAME + "NAME "
+ + PREFIX_EMAIL + "EMAIL "
+ + "[" + PREFIX_PHONE + "PHONE_NUMBER] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_TELEGRAM + "TELEGRAM_HANDLE] "
+ + "[" + PREFIX_ZOOM + "ZOOM] "
+ + "[" + PREFIX_TAG + "TAG]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a contact to SoConnect. \n"
+ + "Parameters: "
+ + PARAMETERS
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "John Doe "
+ + PREFIX_EMAIL + "e7654321@u.nus.edu "
+ + PREFIX_PHONE + "98765432 "
+ + PREFIX_ADDRESS + "COM1-0201 "
+ + PREFIX_TELEGRAM + "jonnyjonny "
+ + PREFIX_ZOOM + "https://nus-sg.zoom.us/j/0123456789?pwd=ABCDEFGHIJKLMNOPDJFHISDFSDH "
+ + PREFIX_TAG + "TA "
+ + PREFIX_TAG + "Senior";
+
+ public static final String MESSAGE_SUCCESS = "New contact added: %1$s";
+ public static final String MESSAGE_DUPLICATE_CONTACT = "This contact already exists in the address book";
+
+ private final Contact toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Contact}
+ */
+ public CAddCommand(Contact contact) {
+ requireNonNull(contact);
+ toAdd = contact;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (model.hasContact(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_CONTACT);
+ }
+ model.addContact(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 CAddCommand // instanceof handles nulls
+ && toAdd.equals(((CAddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CClearCommand.java b/src/main/java/seedu/address/logic/commands/contact/CClearCommand.java
new file mode 100644
index 00000000000..48d564293fe
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CClearCommand.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.commands.contact;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+
+/**
+ * Clears all entries of contacts from SoConnect.
+ */
+public class CClearCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cclear";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_SUCCESS = "All contacts have been cleared!";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.resetContacts();
+ // re-render UI to remove all links
+ model.rerenderEventCards(true);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CDeleteCommand.java b/src/main/java/seedu/address/logic/commands/contact/CDeleteCommand.java
new file mode 100644
index 00000000000..23d3340ee9a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CDeleteCommand.java
@@ -0,0 +1,78 @@
+package seedu.address.logic.commands.contact;
+
+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.commons.core.range.Range;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+
+/**
+ * Deletes a contact identified using it's displayed index from the address book.
+ */
+public class CDeleteCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cdelete";
+ public static final String PARAMETERS = "INDEX1[-INDEX2]";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the contact identified by the index number used in the displayed contact list.\n"
+ + "Parameters: " + PARAMETERS + " (both indexes must be a positive integer)\n"
+ + "Note: index must be a positive integer and INDEX1 must be smaller than or equal to INDEX2"
+ + " if the optional INDEX2 is included)\n"
+ + "Example 1: " + COMMAND_WORD + " 1\n"
+ + "Example 2: " + COMMAND_WORD + " 2-5";
+
+ public static final String MESSAGE_DELETE_CONTACT_SUCCESS = "Deleted Contact: %1$s";
+
+ private final Range targetRange;
+
+ public CDeleteCommand(Range targetRange) {
+ this.targetRange = targetRange;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ Index startIndex = targetRange.getStart();
+ Index endIndex = targetRange.getEnd();
+ int end = endIndex.getZeroBased();
+ if (end >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ String commandResult = "";
+ int indexToDelete = startIndex.getZeroBased();
+ // delete the same index starting from the start index, since after deleting a contact,
+ // the remaining contacts with larger indexes will have their index decreased by 1. Hence,
+ // the next bigger index will have the same index as the deleted contact.
+ for (int i = indexToDelete; i <= end; i++) {
+ Contact contactToDelete = lastShownList.get(indexToDelete);
+ model.deleteContact(contactToDelete);
+ commandResult += String.format(MESSAGE_DELETE_CONTACT_SUCCESS, contactToDelete);
+ if (i != end) {
+ commandResult += "\n";
+ }
+ }
+ // rerender UI to update the links for events with links to deleted contact
+ model.rerenderEventCards(true);
+ return new CommandResult(commandResult);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CDeleteCommand // instanceof handles nulls
+ && targetRange.equals(((CDeleteCommand) other).targetRange)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CEditCommand.java b/src/main/java/seedu/address/logic/commands/contact/CEditCommand.java
new file mode 100644
index 00000000000..5b34fadf03e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CEditCommand.java
@@ -0,0 +1,338 @@
+package seedu.address.logic.commands.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DELETE_TAG;
+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.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CONTACTS;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Phone;
+import seedu.address.model.contact.TelegramHandle;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Edits the details of an existing contact in the address book.
+ */
+public class CEditCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cedit";
+ public static final String PARAMETERS = "INDEX (must be a positive integer) "
+ + "[" + PREFIX_NAME + "NAME] "
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_EMAIL + "EMAIL] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_TELEGRAM + "TELEGRAM] "
+ + "[" + PREFIX_ZOOM + "ZOOM] "
+ + "[" + PREFIX_TAG + "TAG]... "
+ + "[" + PREFIX_DELETE_TAG + "DELETE_TAG]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the contact identified "
+ + "by the index number used in the displayed contact list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_PHONE + "91234567 "
+ + PREFIX_EMAIL + "johndoe@example.com";
+
+ public static final String MESSAGE_EDIT_CONTACT_SUCCESS = "Edited Contact: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_CONTACT = "This contact already exists in the address book.";
+ public static final String MESSAGE_TAG_TO_ADD_ALREADY_IN_ORIGINAL = "Contact already has %s tag.\n";
+ public static final String MESSAGE_TAG_TO_DELETE_NOT_IN_ORIGINAL =
+ "Contact does not contain %s tag to delete.\n";
+
+ private final Index index;
+ private final EditContactDescriptor editContactDescriptor;
+ // to be displayed to user if user tries to delete a tag that does not exist
+ // or add a tag that already exists
+ private String infoMessage = "";
+
+ /**
+ * @param index of the contact in the filtered contact list to edit
+ * @param editContactDescriptor details to edit the contact with
+ */
+ public CEditCommand(Index index, EditContactDescriptor editContactDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editContactDescriptor);
+
+ this.index = index;
+ this.editContactDescriptor = new EditContactDescriptor(editContactDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code Contact} with the details of {@code contactToEdit}
+ * edited with {@code editContactDescriptor}.
+ */
+ private Contact createEditedContact(Contact contactToEdit, EditContactDescriptor editContactDescriptor) {
+ assert contactToEdit != null;
+
+ Name updatedName = editContactDescriptor.getName().orElse(contactToEdit.getName());
+ Phone updatedPhone = editContactDescriptor.getPhone().orElse(contactToEdit.getPhone());
+ Email updatedEmail = editContactDescriptor.getEmail().orElse(contactToEdit.getEmail());
+ Address updatedAddress = editContactDescriptor.getAddress().orElse(contactToEdit.getAddress());
+ TelegramHandle updatedTelegram = editContactDescriptor.getTelegramHandle()
+ .orElse(contactToEdit.getTelegramHandle());
+ ZoomLink updatedZoomLink = editContactDescriptor.getZoomLink().orElse(contactToEdit.getZoomLink());
+ Set updatedNewTags = editContactDescriptor.getTags().orElse(new HashSet<>());
+ Set updatedDeletedTags = editContactDescriptor.getTagsToDelete().orElse(new HashSet<>());
+ Set updatedTags = editContactDescriptor.isShouldDeleteAllTags()
+ ? updatedNewTags : addAndRemoveTags(updatedNewTags, updatedDeletedTags, contactToEdit.getTags());
+ Contact updatedContact = new Contact(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedZoomLink,
+ updatedTelegram, updatedTags, contactToEdit.getUuid(), contactToEdit.getLinkedEvents(),
+ contactToEdit.getIsMarked());
+ // update the edited contact to the hashmap that stores references to all contacts
+ Contact.addToMap(updatedContact);
+ return updatedContact;
+ }
+
+ /**
+ * Creates and returns a {@code Set} with tags from {@code original} and {@code toAdd}, but
+ * tags in {@code toRemove} will be excluded.
+ */
+ private Set addAndRemoveTags(Set toAdd, Set toRemove, Set original) {
+ Set updatedTags = new HashSet<>(original);
+ String result = "\nNote:\n";
+ for (Tag tag : toAdd) {
+ if (!updatedTags.add(tag)) { // if the tag to delete is not in the original tags
+ result += String.format(MESSAGE_TAG_TO_ADD_ALREADY_IN_ORIGINAL, tag);
+ }
+ }
+ for (Tag tag : toRemove) {
+ if (!updatedTags.remove(tag)) { // if the tag to delete is not in the original tags
+ result += String.format(MESSAGE_TAG_TO_DELETE_NOT_IN_ORIGINAL, tag);
+ }
+ }
+ infoMessage = !result.equals("\nNote:\n") ? result : "";
+ toRemove.forEach(updatedTags::remove);
+ updatedTags.addAll(toAdd);
+ return updatedTags;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact contactToEdit = lastShownList.get(index.getZeroBased());
+ Contact editedContact = createEditedContact(contactToEdit, editContactDescriptor);
+
+ if (!contactToEdit.isSameContact(editedContact) && model.hasContact(editedContact)) {
+ throw new CommandException(MESSAGE_DUPLICATE_CONTACT);
+ }
+
+ model.setContact(contactToEdit, editedContact);
+ model.updateFilteredContactList(PREDICATE_SHOW_ALL_CONTACTS);
+ // rerender UI to show latest change for events with links to edited contact
+ model.rerenderEventCards(true);
+ String result = String.format(MESSAGE_EDIT_CONTACT_SUCCESS, editedContact) + infoMessage;
+ return new CommandResult(result);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof CEditCommand)) {
+ return false;
+ }
+
+ // state check
+ CEditCommand e = (CEditCommand) other;
+ return index.equals(e.index)
+ && editContactDescriptor.equals(e.editContactDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the contact with. Each non-empty field value will replace the
+ * corresponding field value of the contact.
+ */
+ public static class EditContactDescriptor {
+ private Name name;
+ private Phone phone;
+ private Email email;
+ private Address address;
+ private TelegramHandle telegramHandle;
+ private ZoomLink zoomLink;
+ private Set tags;
+ private Set tagsToDelete;
+ private boolean shouldDeleteAllTags = false;
+
+ public EditContactDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditContactDescriptor(EditContactDescriptor toCopy) {
+ setName(toCopy.name);
+ setPhone(toCopy.phone);
+ setEmail(toCopy.email);
+ setAddress(toCopy.address);
+ setTelegramHandle(toCopy.telegramHandle);
+ setZoomLink(toCopy.zoomLink);
+ setTags(toCopy.tags);
+ setTagsToDelete(toCopy.tagsToDelete);
+ setShouldDeleteAllTags(toCopy.shouldDeleteAllTags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, telegramHandle, zoomLink,
+ tags, tagsToDelete) || shouldDeleteAllTags;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
+ }
+
+ public void setPhone(Phone phone) {
+ this.phone = phone;
+ }
+
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ public void setEmail(Email email) {
+ this.email = email;
+ }
+
+ public Optional getAddress() {
+ return Optional.ofNullable(address);
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public Optional getTelegramHandle() {
+ return Optional.ofNullable(telegramHandle);
+ }
+
+ public void setTelegramHandle(TelegramHandle telegramHandle) {
+ this.telegramHandle = telegramHandle;
+ }
+
+ public Optional getZoomLink() {
+ return Optional.ofNullable(zoomLink);
+ }
+
+ public void setZoomLink(ZoomLink zoomLink) {
+ this.zoomLink = zoomLink;
+ }
+
+ /**
+ * Returns an unmodifiable tag set to be added, 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();
+ }
+
+ /**
+ * 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 && !tags.isEmpty()) ? new HashSet<>(tags) : null;
+ }
+
+ /**
+ * Returns an unmodifiable tag set to be removed, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tagsToDelete} is null.
+ */
+ public Optional> getTagsToDelete() {
+ return tagsToDelete != null ? Optional.of(Collections.unmodifiableSet((tagsToDelete))) : Optional.empty();
+ }
+
+ /**
+ * Sets {@code tagsToDelete} to this object's {@code tagsToDelete}.
+ * A defensive copy of {@code tagsToDelete} is used internally.
+ */
+ public void setTagsToDelete(Set tagsToDelete) {
+ this.tagsToDelete = (tagsToDelete != null && !tagsToDelete.isEmpty()) ? new HashSet<>(tagsToDelete) : null;
+ }
+
+ public boolean isShouldDeleteAllTags() {
+ return shouldDeleteAllTags;
+ }
+
+ /**
+ * Sets the boolean condition of whether all tags should be cleared first.
+ */
+ public void setShouldDeleteAllTags(boolean shouldDeleteAllTags) {
+ this.shouldDeleteAllTags = shouldDeleteAllTags;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditContactDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditContactDescriptor e = (EditContactDescriptor) other;
+
+ return getName().equals(e.getName())
+ && getPhone().equals(e.getPhone())
+ && getEmail().equals(e.getEmail())
+ && getAddress().equals(e.getAddress())
+ && getTelegramHandle().equals(e.getTelegramHandle())
+ && getZoomLink().equals(e.getZoomLink())
+ && getTags().equals(e.getTags())
+ && getTagsToDelete().equals(e.getTagsToDelete());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CFindCommand.java b/src/main/java/seedu/address/logic/commands/contact/CFindCommand.java
new file mode 100644
index 00000000000..e37e95811bb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CFindCommand.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands.contact;
+
+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_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+import seedu.address.model.contact.ContactContainsKeywordsPredicate;
+import seedu.address.model.contact.ContactDisplaySetting;
+
+/**
+ * Finds and lists all persons in address book whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class CFindCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cfind";
+ public static final String PARAMETERS = "[KEYWORD]… "
+ + "[" + PREFIX_PHONE + "KEYWORD…] "
+ + "[" + PREFIX_EMAIL + "KEYWORD…] "
+ + "[" + PREFIX_ADDRESS + "KEYWORD…] "
+ + "[" + PREFIX_TELEGRAM + "KEYWORD…] "
+ + "[" + PREFIX_ZOOM + "KEYWORD…] "
+ + "[" + PREFIX_TAG + "KEYWORD…]\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts whose fields contains any of the "
+ + "given keywords.\n"
+ + "At least one keyword must be present. "
+ + "For name search, keywords must follow directly after the command word\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " alice bob charlie "
+ + PREFIX_PHONE + "91234567 "
+ + PREFIX_EMAIL + "johndoe@example.com";
+
+ private final ContactContainsKeywordsPredicate predicate;
+
+ public CFindCommand(ContactContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setContactDisplaySetting(ContactDisplaySetting.DEFAULT_SETTING);
+ model.updateFilteredContactList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_CONTACTS_LISTED_OVERVIEW, model.getFilteredContactList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CFindCommand // instanceof handles nulls
+ && predicate.equals(((CFindCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CListCommand.java b/src/main/java/seedu/address/logic/commands/contact/CListCommand.java
new file mode 100644
index 00000000000..5e3622c7a8a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CListCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands.contact;
+
+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_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Objects;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+import seedu.address.model.contact.ContactDisplaySetting;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class CListCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "clist";
+ public static final String PARAMETERS = "[" + PREFIX_PHONE + "] "
+ + "[" + PREFIX_EMAIL + "] "
+ + "[" + PREFIX_ADDRESS + "] "
+ + "[" + PREFIX_TELEGRAM + "] "
+ + "[" + PREFIX_ZOOM + "] "
+ + "[" + PREFIX_TAG + "] \n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_SUCCESS = "Listed all contacts";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all the contacts on the screen with all"
+ + " details by default.\n"
+ + "Include optional parameters to filter details.\n"
+ + "Parameters should not be followed by any values.\n" //added this to warn users not to add any values.
+ + "Parameters: "
+ + PARAMETERS
+ + "Example: " + COMMAND_WORD + " " + PREFIX_EMAIL + " " + PREFIX_ZOOM;
+
+ private final ContactDisplaySetting displaySetting;
+
+ public CListCommand(ContactDisplaySetting displaySetting) {
+ this.displaySetting = displaySetting;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setContactDisplaySetting(displaySetting);
+ model.rerenderContactCards(false);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(displaySetting);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ return other instanceof CListCommand
+ && ((CListCommand) other).displaySetting.equals(displaySetting);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CMarkCommand.java b/src/main/java/seedu/address/logic/commands/contact/CMarkCommand.java
new file mode 100644
index 00000000000..42587d4061e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CMarkCommand.java
@@ -0,0 +1,90 @@
+package seedu.address.logic.commands.contact;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+
+
+public class CMarkCommand extends Command implements Undoable {
+ public static final String COMMAND_WORD = "cmark";
+
+ public static final String PARAMETERS = "INDEX [INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks all contact(s) indexed at "
+ + "the specified index(es) and displays them at the top of the contact list.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Marked contact: %1$s";
+ public static final String MESSAGE_ALREADY_MARKED = "Contact already marked: %1$s";
+
+ private final List indexesToMark;
+
+ /**
+ * Class constructor, takes in a list of {@code index}
+ */
+ public CMarkCommand(List indexes) {
+ requireNonNull(indexes);
+ this.indexesToMark = indexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ String commandResult = "";
+ List lastShownList = model.getFilteredContactList();
+ if (indexesToMark.stream().anyMatch(index -> index.getZeroBased() >= lastShownList.size())) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+ Collections.reverse(indexesToMark);
+ List contactsMarked = new ArrayList<>();
+ for (Index index : indexesToMark) {
+ Contact contact = lastShownList.get(index.getZeroBased());
+ commandResult += String.format("%s", generateCommandResultMessage(contact, contact.getIsMarked()));
+ Contact newContact = contact.markContact();
+ model.setContact(contact, newContact);
+ contactsMarked.add(newContact);
+ }
+ model.rearrangeContactsInOrder(contactsMarked, true);
+ return new CommandResult(commandResult);
+ }
+
+ /**
+ * Creates and returns a marked {@code Contact} with the details of {@code contactToMark}
+ */
+ private static Contact createMarkedContact(Contact contactToMark) {
+ return new Contact(contactToMark.getName(), contactToMark.getPhone(), contactToMark.getEmail(),
+ contactToMark.getAddress(), contactToMark.getZoomLink(), contactToMark.getTelegramHandle(),
+ contactToMark.getTags(), contactToMark.getUuid(), contactToMark.getLinkedEvents(), true);
+ }
+
+ private String generateCommandResultMessage(Contact contact,
+ boolean isAlreadyMarked) {
+ String message;
+ if (isAlreadyMarked) {
+ message = String.format(MESSAGE_ALREADY_MARKED, contact);
+ } else {
+ message = String.format(MESSAGE_SUCCESS, contact);
+ }
+ return message += "\n";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CMarkCommand // instanceof handles nulls
+ && indexesToMark.equals(((CMarkCommand) other).indexesToMark)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CUnmarkCommand.java b/src/main/java/seedu/address/logic/commands/contact/CUnmarkCommand.java
new file mode 100644
index 00000000000..52d003d1b6d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CUnmarkCommand.java
@@ -0,0 +1,82 @@
+package seedu.address.logic.commands.contact;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+
+public class CUnmarkCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "cunmark";
+
+ public static final String PARAMETERS = "INDEX [INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Unmarks all contact(s) indexed at "
+ + "the specified index(es) and displays them after the marked contacts.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Unmarked contact: %1$s";
+ public static final String MESSAGE_NOT_MARKED = "Contact not marked: %1$s";
+
+ private final List indexesToUnmark;
+
+ /**
+ * Class constructor, takes in a list of {@code index}
+ */
+ public CUnmarkCommand(List indexes) {
+ requireNonNull(indexes);
+ this.indexesToUnmark = indexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ String commandResult = "";
+ List lastShownList = model.getFilteredContactList();
+ if (indexesToUnmark.stream().anyMatch(index -> index.getZeroBased() >= lastShownList.size())) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+ Collections.reverse(indexesToUnmark);
+ List contactsUnmarked = new ArrayList<>();
+ for (Index index : indexesToUnmark) {
+ Contact contact = lastShownList.get(index.getZeroBased());
+ commandResult += String.format("%s", generateCommandResultMessage(contact, contact.getIsMarked()));
+ if (contact.getIsMarked()) {
+ Contact newContact = contact.unmarkContact();
+ model.setContact(contact, newContact);
+ contactsUnmarked.add(newContact);
+ }
+ }
+ model.rearrangeContactsInOrder(contactsUnmarked, false);
+ return new CommandResult(commandResult);
+ }
+
+ private String generateCommandResultMessage(Contact contact, boolean isAlreadyMarked) {
+ String message;
+ if (!isAlreadyMarked) {
+ message = String.format(MESSAGE_NOT_MARKED, contact);
+ } else {
+ message = String.format(MESSAGE_SUCCESS, contact);
+ }
+ return message += "\n";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CUnmarkCommand // instanceof handles nulls
+ && indexesToUnmark.equals(((CUnmarkCommand) other).indexesToUnmark)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/contact/CViewCommand.java b/src/main/java/seedu/address/logic/commands/contact/CViewCommand.java
new file mode 100644
index 00000000000..582cb93f918
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/contact/CViewCommand.java
@@ -0,0 +1,65 @@
+package seedu.address.logic.commands.contact;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+
+
+/**
+ * Views one person in full detail in the SoConnect to the user.
+ */
+public class CViewCommand extends Command implements Undoable {
+ public static final String COMMAND_WORD = "cview";
+
+ public static final String SYNTAX = COMMAND_WORD + " INDEX";
+
+ public static final String MESSAGE_SUCCESS = "Viewing Contact: %1$s";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": View one contact of the index provided "
+ + "on the screen with all details.\n"
+ + "Parameters: INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index index;
+
+ public CViewCommand(Index index) {
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ Index viewIndex = index;
+ int intIndex = viewIndex.getZeroBased();
+ if (intIndex < 0 || intIndex >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+ model.setContactDisplaySetting(new ContactDisplaySetting(true));
+ model.updateContactListByIndex(viewIndex);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, lastShownList.get(0)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ return other instanceof CViewCommand
+ && ((CViewCommand) other).index.equals(index);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EAddCommand.java b/src/main/java/seedu/address/logic/commands/event/EAddCommand.java
new file mode 100644
index 00000000000..6abe93078b3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EAddCommand.java
@@ -0,0 +1,85 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.List;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventChanger;
+
+/**
+ * Adds a event to the address book.
+ */
+public class EAddCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "eadd";
+ public static final String PARAMETERS = "" + PREFIX_NAME + "NAME "
+ + "" + PREFIX_START_TIME + "START "
+ + "[" + PREFIX_END_TIME + "END] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_ZOOM + "ZOOM] "
+ + "[" + PREFIX_TAG + "TAG]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an event to SoConnect. \n"
+ + "Parameters: "
+ + PARAMETERS
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "Summer Party "
+ + PREFIX_START_TIME + "12-12-2021 15:12 "
+ + PREFIX_DESCRIPTION + "end of semester party "
+ + PREFIX_ADDRESS + "123, Clementi Rd, S1234665 "
+ + PREFIX_ZOOM + "http://zoomlink.com "
+ + PREFIX_TAG + "fun";
+
+ 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";
+ public static final String MESSAGE_INVALID_DATE_TIME_RANGE = "Event start time cannot be later than end time.";
+
+ private final Event toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Event}
+ * @param event the specified Event
+ */
+ public EAddCommand(Event event) {
+ requireNonNull(event);
+ toAdd = event;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasEvent(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+
+ if (toAdd.getEndDateAndTime() != null && toAdd.getEndDateAndTime().isBefore(toAdd.getStartDateAndTime())) {
+ throw new CommandException(MESSAGE_INVALID_DATE_TIME_RANGE);
+ }
+
+ model.addEvent(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), List.of(EventChanger.addEventChanger(toAdd)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EAddCommand // instanceof handles nulls
+ && toAdd.equals(((EAddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EClearCommand.java b/src/main/java/seedu/address/logic/commands/event/EClearCommand.java
new file mode 100644
index 00000000000..69032fa17ed
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EClearCommand.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+import seedu.address.model.event.EventChanger;
+
+/**
+ * Clears all events in SoConnect.
+ */
+public class EClearCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "eclear";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_SUCCESS = "All events have been cleared!";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.resetEvents();
+ // rerender UI to remove all links
+ model.rerenderContactCards(true);
+ return new CommandResult(MESSAGE_SUCCESS, List.of(EventChanger.clearEventChanger()));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EDeleteCommand.java b/src/main/java/seedu/address/logic/commands/event/EDeleteCommand.java
new file mode 100644
index 00000000000..b26d011a4cc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EDeleteCommand.java
@@ -0,0 +1,80 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.core.range.Range;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventChanger;
+
+/**
+ * Deletes an event identified using its displayed index from the SoConnect.
+ */
+public class EDeleteCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "edelete";
+ public static final String PARAMETERS = "INDEX1[-INDEX2]";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the event identified by the index number used in the displayed event list.\n"
+ + "Parameters: " + PARAMETERS + " (both indexes must be a positive integer)\n"
+ + "Note: index must be a positive integer and INDEX1 must be smaller than or equal to INDEX2"
+ + " if the optional INDEX2 is included)\n"
+ + "Example 1: " + COMMAND_WORD + " 1\n"
+ + "Example 2: " + COMMAND_WORD + " 2-5";
+
+ public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Deleted Event: %1$s";
+
+ private final Range targetRange;
+
+ public EDeleteCommand(Range targetRange) {
+ this.targetRange = targetRange;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredEventList();
+
+ Index startIndex = targetRange.getStart();
+ Index endIndex = targetRange.getEnd();
+ int end = endIndex.getZeroBased();
+ if (end >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ String commandResult = "";
+ int indexToDelete = startIndex.getZeroBased();
+ List eventChangerList = new ArrayList<>();
+ for (int i = indexToDelete; i <= end; i++) {
+ Event eventToDelete = lastShownList.get(indexToDelete);
+ model.deleteEvent(eventToDelete);
+ eventChangerList.add(EventChanger.deleteEventChanger(eventToDelete));
+ commandResult += String.format(MESSAGE_DELETE_EVENT_SUCCESS, eventToDelete);
+ if (i != end) {
+ commandResult += "\n";
+ }
+ }
+ // rerender UI to update the links for contacts with links to deleted event
+ model.rerenderContactCards(true);
+ return new CommandResult(commandResult, eventChangerList);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EDeleteCommand // instanceof handles nulls
+ && targetRange.equals(((EDeleteCommand) other).targetRange)); // state check
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/event/EEditCommand.java b/src/main/java/seedu/address/logic/commands/event/EEditCommand.java
new file mode 100644
index 00000000000..562a10467fc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EEditCommand.java
@@ -0,0 +1,339 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DELETE_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.event.Description;
+import seedu.address.model.event.EndDateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventChanger;
+import seedu.address.model.event.StartDateTime;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Edits the details of an existing event in the address book.
+ */
+public class EEditCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "eedit";
+ public static final String PARAMETERS = "INDEX (must be a positive integer) "
+ + "[" + PREFIX_NAME + "NAME] "
+ + "[" + PREFIX_START_TIME + "START] "
+ + "[" + PREFIX_END_TIME + "END] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_ZOOM + "ZOOM] "
+ + "[" + PREFIX_TAG + "TAG]... "
+ + "[" + PREFIX_DELETE_TAG + "DELETE TAG]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the event identified "
+ + "by the index number used in the displayed event list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_START_TIME + "01-10-2021 15:00 "
+ + PREFIX_ADDRESS + "12th Street";
+
+ 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 address book.";
+ public static final String MESSAGE_INVALID_DATE_TIME_RANGE = "Event start time cannot be later than end time.";
+ public static final String MESSAGE_TAG_TO_ADD_ALREADY_IN_ORIGINAL = "Event already has %s tag.\n";
+ public static final String MESSAGE_TAG_TO_DELETE_NOT_IN_ORIGINAL =
+ "Event does not contain %s tag to delete.\n";
+
+ private final Index index;
+ private final EditEventDescriptor editEventDescriptor;
+ // to be displayed to user if user tries to delete a tag that does not exist
+ // or add a tag that already exists
+ private String infoMessage = "";
+
+ /**
+ * @param index of the event in the filtered event list to edit
+ * @param editEventDescriptor details to edit the event with
+ */
+ public EEditCommand(Index index, EditEventDescriptor editEventDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editEventDescriptor);
+
+ this.index = index;
+ this.editEventDescriptor = new EditEventDescriptor(editEventDescriptor);
+ }
+
+ /**
+ * Creates and returns a {@code Event} with the details of {@code eventToEdit}
+ * edited with {@code editEventDescriptor}.
+ */
+ private Event createEditedEvent(Event eventToEdit, EditEventDescriptor editEventDescriptor) {
+ assert eventToEdit != null;
+
+ Name updatedName = editEventDescriptor.getName().orElse(eventToEdit.getName());
+ StartDateTime updatedStartDateTime = editEventDescriptor.getStartDateTime()
+ .orElse(eventToEdit.getStartDateAndTime());
+ EndDateTime updatedEndDateTime = editEventDescriptor.getEndDateTime()
+ .orElse(eventToEdit.getEndDateAndTime());
+ Description updatedDescription = editEventDescriptor.getDescription().orElse(eventToEdit.getDescription());
+ Address updatedAddress = editEventDescriptor.getAddress().orElse(eventToEdit.getAddress());
+ ZoomLink updatedZoomLink = editEventDescriptor.getZoomLink().orElse(eventToEdit.getZoomLink());
+ Set updatedNewTags = editEventDescriptor.getTags().orElse(new HashSet<>());
+ Set updatedDeletedTags = editEventDescriptor.getTagsToDelete().orElse(new HashSet<>());
+ Set updatedTags = editEventDescriptor.getShouldDeleteAllTags()
+ ? updatedNewTags : addAndRemoveTags(updatedNewTags, updatedDeletedTags, eventToEdit.getTags());
+ Event updatedEvent = new Event(updatedName, updatedStartDateTime, updatedEndDateTime, updatedDescription,
+ updatedAddress, updatedZoomLink, updatedTags, eventToEdit.getUuid(), eventToEdit.getLinkedContacts(),
+ eventToEdit.getIsMarked());
+ // update the edited event to the hashmap that stores references to all events
+ Event.addToMap(updatedEvent);
+ return updatedEvent;
+ }
+
+ /**
+ * Creates and returns a {@code Set} with tags from {@code original} and {@code toAdd}, but
+ * tags in {@code toRemove} will be excluded.
+ */
+ private Set addAndRemoveTags(Set toAdd, Set toRemove, Set original) {
+ Set updatedTags = new HashSet<>(original);
+ String result = "\nNote:\n";
+ for (Tag tag : toAdd) {
+ if (!updatedTags.add(tag)) { // if the tag to delete is not in the original tags
+ result += String.format(MESSAGE_TAG_TO_ADD_ALREADY_IN_ORIGINAL, tag);
+ }
+ }
+ for (Tag tag : toRemove) {
+ if (!updatedTags.remove(tag)) { // if the tag to delete is not in the original tags
+ result += String.format(MESSAGE_TAG_TO_DELETE_NOT_IN_ORIGINAL, tag);
+ }
+ }
+ infoMessage = !result.equals("\nNote:\n") ? result : "";
+ updatedTags.addAll(toAdd);
+ return updatedTags;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredEventList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ Event eventToEdit = lastShownList.get(index.getZeroBased());
+ Event editedEvent = createEditedEvent(eventToEdit, editEventDescriptor);
+
+ if (!eventToEdit.isSameEvent(editedEvent) && model.hasEvent(editedEvent)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+
+ if (editedEvent.getEndDateAndTime() != null
+ && editedEvent.getEndDateAndTime().isBefore(editedEvent.getStartDateAndTime())) {
+ throw new CommandException(MESSAGE_INVALID_DATE_TIME_RANGE);
+ }
+
+ model.setEvent(eventToEdit, editedEvent);
+ model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
+ // rerender UI to show latest change for contacts with links to edited event
+ model.rerenderContactCards(true);
+ String result = String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent) + infoMessage;
+ return new CommandResult(result, List.of(EventChanger.editEventChanger(eventToEdit, editedEvent)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EEditCommand)) {
+ return false;
+ }
+
+ // state check
+ EEditCommand e = (EEditCommand) 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 Name name;
+ private StartDateTime start;
+ private EndDateTime end;
+ private Description description;
+ private Address address;
+ private ZoomLink zoom;
+ private Set tags;
+ private Set tagsToDelete;
+ private boolean shouldDeleteAllTags = false;
+
+ public EditEventDescriptor() {
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditEventDescriptor(EditEventDescriptor toCopy) {
+ setName(toCopy.name);
+ setStartDateTime(toCopy.start);
+ setEndDateTime(toCopy.end);
+ setDescription(toCopy.description);
+ setAddress(toCopy.address);
+ setZoomLink(toCopy.zoom);
+ setTags(toCopy.tags);
+ setTagsToDelete(toCopy.tagsToDelete);
+ setShouldDeleteAllTags(toCopy.shouldDeleteAllTags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, start, end, description, address, zoom, tags, tagsToDelete)
+ || shouldDeleteAllTags;
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setStartDateTime(StartDateTime start) {
+ this.start = start;
+ }
+
+ public Optional getStartDateTime() {
+ return Optional.ofNullable(start);
+ }
+
+ public void setEndDateTime(EndDateTime end) {
+ this.end = end;
+ }
+
+ public Optional getEndDateTime() {
+ return Optional.ofNullable(end);
+ }
+
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public Optional getAddress() {
+ return Optional.ofNullable(address);
+ }
+
+ public void setZoomLink(ZoomLink zoom) {
+ this.zoom = zoom;
+ }
+
+ public Optional getZoomLink() {
+ return Optional.ofNullable(zoom);
+ }
+
+ /**
+ * 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 && !tags.isEmpty()) ? new HashSet<>(tags) : null;
+ }
+
+ public Optional> getTags() {
+ return Optional.ofNullable(tags);
+ }
+ /**
+ * Returns an unmodifiable tag set to be removed, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tagsToDelete} is null.
+ */
+ public Optional> getTagsToDelete() {
+ return tagsToDelete != null ? Optional.of(Collections.unmodifiableSet((tagsToDelete))) : Optional.empty();
+ }
+
+ /**
+ * Sets {@code tagsToDelete} to this object's {@code tagsToDelete}.
+ * A defensive copy of {@code tagsToDelete} is used internally.
+ */
+ public void setTagsToDelete(Set tagsToDelete) {
+ this.tagsToDelete = (tagsToDelete != null && !tagsToDelete.isEmpty()) ? new HashSet<>(tagsToDelete) : null;
+ }
+
+ public boolean getShouldDeleteAllTags() {
+ return shouldDeleteAllTags;
+ }
+
+ /**
+ * Sets the boolean condition of whether all tags should be cleared first.
+ */
+ public void setShouldDeleteAllTags(boolean shouldDeleteAllTags) {
+ this.shouldDeleteAllTags = shouldDeleteAllTags;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditEventDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditEventDescriptor e = (EditEventDescriptor) other;
+
+ return getName().equals(e.getName())
+ && getStartDateTime().equals(e.getStartDateTime())
+ && getEndDateTime().equals(e.getEndDateTime())
+ && getDescription().equals(e.getDescription())
+ && getAddress().equals(e.getAddress())
+ && getZoomLink().equals(e.getZoomLink())
+ && getTags().equals(e.getTags())
+ && getTagsToDelete().equals(e.getTagsToDelete());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EFindCommand.java b/src/main/java/seedu/address/logic/commands/event/EFindCommand.java
new file mode 100644
index 00000000000..30e79c8392b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EFindCommand.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+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_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+import seedu.address.model.event.EventContainsKeywordsPredicate;
+import seedu.address.model.event.EventDisplaySetting;
+
+/**
+ * Finds and lists all events in SoConnect which have names containing any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class EFindCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "efind";
+ public static final String PARAMETERS = "[KEYWORD]… "
+ + "[" + PREFIX_START_TIME + "KEYWORD…] "
+ + "[" + PREFIX_END_TIME + "KEYWORD…] "
+ + "[" + PREFIX_ADDRESS + "KEYWORD…] "
+ + "[" + PREFIX_DESCRIPTION + "KEYWORD…] "
+ + "[" + PREFIX_ZOOM + "KEYWORD…] "
+ + "[" + PREFIX_TAG + "KEYWORD…]\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all events whose fields contains any of the "
+ + "given keywords.\n"
+ + "At least one keyword must be present. "
+ + "For name search, keywords must follow directly after the command word\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " cs 2103t "
+ + PREFIX_START_TIME + "2020-12-01 "
+ + PREFIX_EMAIL + "johndoe@example.com";
+
+ private final EventContainsKeywordsPredicate predicate;
+
+ public EFindCommand(EventContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setEventDisplaySetting(EventDisplaySetting.DEFAULT_SETTING);
+ model.updateFilteredEventList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EFindCommand // instanceof handles nulls
+ && predicate.equals(((EFindCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/ELinkCommand.java b/src/main/java/seedu/address/logic/commands/event/ELinkCommand.java
new file mode 100644
index 00000000000..64ed4423479
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/ELinkCommand.java
@@ -0,0 +1,102 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.event.Event;
+
+/** Links an event to a list of contacts. */
+public class ELinkCommand extends Command implements Undoable {
+ public static final String COMMAND_WORD = "elink";
+ public static final String PARAMETERS = "EVENT_INDEX "
+ + PREFIX_CONTACT + "CONTACT_INDEX [" + PREFIX_CONTACT + "CONTACT_INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Links an event to one or more contacts. \n"
+ + "Parameters: "
+ + PARAMETERS
+ + "Example 1: elink 1 c/1\n"
+ + "Example 2: elink 3 c/1 c/2 c/3";
+ public static final String MESSAGE_SUCCESS = "Successfully linked the event %s to the contact %s.\n";
+ public static final String MESSAGE_ALREADY_LINKED = "Event %s is already linked to the contact %s.\n";
+
+ private final Index eventIndex;
+ private final Set contactIndexes;
+
+ /**
+ * Creates an ELinkCommand to link the specified {@code Event} to a {@code Contact}
+ */
+ public ELinkCommand(Index eventIndex, Set contactIndexes) {
+ assert !contactIndexes.isEmpty() : "Set of contact indices cannot be empty.";
+ this.eventIndex = eventIndex;
+ this.contactIndexes = contactIndexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // check validity of command
+ List lastShownEventList = model.getFilteredEventList();
+ List lastShownContactList = model.getFilteredContactList();
+ checkCommandValidity(lastShownEventList, lastShownContactList);
+
+ // execution of command
+ CommandResult commandResult = linkEventAndContacts(model, lastShownEventList, lastShownContactList);
+
+ // rerender UI to show the links between event and each of the contacts
+ model.rerenderAllCards();
+
+ return commandResult;
+ }
+
+ private void checkCommandValidity(List eventList, List contactList) throws CommandException {
+ if (eventIndex.getZeroBased() >= eventList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ for (Index contactIndex : contactIndexes) {
+ if (contactIndex.getZeroBased() >= contactList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+ }
+ }
+
+ private CommandResult linkEventAndContacts(Model model, List lastShownEventList,
+ List lastShownContactList) {
+ String commandResult = "";
+ for (Index contactIndex : contactIndexes) {
+ // have to get the event from the list again because a new event replaces the index whenever
+ // a link occurs, hence cannot use the old reference of event.
+ Event eventToLink = lastShownEventList.get(eventIndex.getZeroBased());
+ Contact contactToLink = lastShownContactList.get(contactIndex.getZeroBased());
+ if (contactToLink.hasLinkTo(eventToLink)) {
+ assert eventToLink.hasLinkTo(contactToLink) : "Both should have links to each other";
+ commandResult += String.format(MESSAGE_ALREADY_LINKED, eventToLink.getName(), contactToLink.getName());
+ continue;
+ }
+ model.linkEventAndContact(eventToLink, contactToLink);
+ commandResult += String.format(MESSAGE_SUCCESS, eventToLink.getName(), contactToLink.getName());
+ }
+ return new CommandResult(commandResult);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ELinkCommand // instanceof handles nulls
+ && eventIndex.equals(((ELinkCommand) other).eventIndex)
+ && contactIndexes.equals(((ELinkCommand) other).contactIndexes)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EListCommand.java b/src/main/java/seedu/address/logic/commands/event/EListCommand.java
new file mode 100644
index 00000000000..2ffa8599678
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EListCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Objects;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+import seedu.address.model.event.EventDisplaySetting;
+
+/**
+ * Lists all events in the address book to the user.
+ */
+public class EListCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "elist";
+ public static final String PARAMETERS = "[" + PREFIX_START_TIME + "] "
+ + "[" + PREFIX_END_TIME + "] "
+ + "[" + PREFIX_DESCRIPTION + "] "
+ + "[" + PREFIX_ADDRESS + "] "
+ + "[" + PREFIX_ZOOM + "] "
+ + "[" + PREFIX_TAG + "] \n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_SUCCESS = "Listed all events";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all the events on the screen with all"
+ + " details by default.\n"
+ + "Include optional parameters to filter details.\n"
+ + "Parameters should not be followed by any values.\n" //added this to warn users not to add any values.
+ + "Parameters: "
+ + PARAMETERS
+ + "Example: " + COMMAND_WORD + " " + PREFIX_END_TIME + " " + PREFIX_ZOOM;
+
+ public final EventDisplaySetting displaySetting;
+
+ public EListCommand(EventDisplaySetting displaySetting) {
+ this.displaySetting = displaySetting;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.setEventDisplaySetting(displaySetting);
+ model.rerenderEventCards(false);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(displaySetting);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ return other instanceof EListCommand
+ && ((EListCommand) other).displaySetting.equals(displaySetting);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EMarkCommand.java b/src/main/java/seedu/address/logic/commands/event/EMarkCommand.java
new file mode 100644
index 00000000000..14c902b9b98
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EMarkCommand.java
@@ -0,0 +1,82 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+
+public class EMarkCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "emark";
+
+ public static final String PARAMETERS = "INDEX [INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks all event(s) indexed at "
+ + "the specified index(es) and displays them at the top of the event list.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Marked event: %1$s";
+ public static final String MESSAGE_ALREADY_MARKED = "Event already marked: %1$s";
+
+ private final List indexesToMark;
+
+ /**
+ * Class constryctor, takes in a list of {@code index}
+ */
+ public EMarkCommand(List indexes) {
+ requireNonNull(indexes);
+ this.indexesToMark = indexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ String commandResult = "";
+ List lastShownList = model.getFilteredEventList();
+ if (indexesToMark.stream().anyMatch(index -> index.getZeroBased() >= lastShownList.size())) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+ Collections.reverse(indexesToMark);
+ List eventsMarked = new ArrayList<>();
+ for (Index index : indexesToMark) {
+ Event event = lastShownList.get(index.getZeroBased());
+ commandResult += String.format("%s", generateCommandResultMessage(event, event.getIsMarked()));
+ Event newEvent = event.markEvent();
+ model.setEvent(event, newEvent);
+ eventsMarked.add(newEvent);
+ }
+ model.rearrangeEventsInOrder(eventsMarked, true);
+ return new CommandResult(commandResult);
+ }
+
+ private String generateCommandResultMessage(Event event,
+ boolean isAlreadyMarked) {
+ String message;
+ if (isAlreadyMarked) {
+ message = String.format(MESSAGE_ALREADY_MARKED, event);
+ } else {
+ message = String.format(MESSAGE_SUCCESS, event);
+ }
+ return message += "\n";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EMarkCommand // instanceof handles nulls
+ && indexesToMark.equals(((EMarkCommand) other).indexesToMark)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/ESortCommand.java b/src/main/java/seedu/address/logic/commands/event/ESortCommand.java
new file mode 100644
index 00000000000..7ce3ae15d8b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/ESortCommand.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.model.Model;
+
+/** Sorts the address book and show only the upcoming and ongoing events. */
+public class ESortCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "esort";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_SUCCESS = "Showing upcoming events in sorted order.";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.sortUpcomingFilteredEventList();
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EUnlinkCommand.java b/src/main/java/seedu/address/logic/commands/event/EUnlinkCommand.java
new file mode 100644
index 00000000000..1c87847d923
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EUnlinkCommand.java
@@ -0,0 +1,124 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.event.Event;
+
+/** Unlinks an event from a list of contacts. */
+public class EUnlinkCommand extends Command implements Undoable {
+ public static final String COMMAND_WORD = "eunlink";
+
+ public static final String PARAMETERS = "EVENT_INDEX "
+ + PREFIX_CONTACT + "CONTACT_INDEX [" + PREFIX_CONTACT + "CONTACT_INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Unlinks an event from one or more contacts."
+ + " Include the argument \"c/*\" to clear all links.\n"
+ + "Parameters: "
+ + PARAMETERS
+ + "Example 1: eunlink 1 c/1\n"
+ + "Example 2: eunlink 3 c/1 c/2 c/3\n"
+ + "Example 3: eunlink 2 c/*";
+ public static final String MESSAGE_SUCCESS = "Successfully unlinked the event %s from the contact %s\n";
+ public static final String MESSAGE_SUCCESS_CLEAR_ALL = "Successfully unlinked the event %s from all contacts.";
+ public static final String MESSAGE_NOT_LINKED = "Event %s is already not linked to the contact %s.\n";
+
+ private final Index eventIndex;
+ private final Set contactIndexes;
+ private final boolean isClearAllLinks;
+
+ /**
+ * Creates an EUnlinkCommand to remove the link from the specified {@code Event} to a {@code Contact}
+ */
+ public EUnlinkCommand(Index eventIndex, Set contactIndexes, boolean isClearAllLinks) {
+ requireAllNonNull(eventIndex, contactIndexes);
+ assert (!contactIndexes.isEmpty() || isClearAllLinks)
+ && !(!contactIndexes.isEmpty() && isClearAllLinks)
+ : "Either the set is empty or isClearAllLinks is true, but not both";
+ this.eventIndex = eventIndex;
+ this.contactIndexes = contactIndexes;
+ this.isClearAllLinks = isClearAllLinks;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // check validity of command
+ List lastShownEventList = model.getFilteredEventList();
+ List lastShownContactList = model.getFilteredContactList();
+ checkCommandValidity(lastShownEventList, lastShownContactList);
+
+ // execution of command
+ CommandResult commandResult;
+ Event eventToUnlink = lastShownEventList.get(eventIndex.getZeroBased());
+ if (!isClearAllLinks) {
+ commandResult = unlinkEventAndContacts(model, lastShownEventList, lastShownContactList);
+ } else {
+ commandResult = unlinkEventAndAllContacts(eventToUnlink, model);
+ }
+
+ // rerender UI to show the links between event and each of the contacts
+ model.rerenderAllCards();
+
+ return commandResult;
+ }
+
+ private void checkCommandValidity(List eventList, List contactList) throws CommandException {
+ if (eventIndex.getZeroBased() >= eventList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ for (Index contactIndex : contactIndexes) {
+ if (contactIndex.getZeroBased() >= contactList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+ }
+ }
+
+ private CommandResult unlinkEventAndContacts(Model model, List lastShownEventList,
+ List lastShownContactList) {
+ String commandResult = "";
+ for (Index contactIndex : contactIndexes) {
+ // have to get the event from the list again because a new event replaces the index whenever
+ // an unlink occurs, hence cannot use the old reference of event.
+ Event eventToUnlink = lastShownEventList.get(eventIndex.getZeroBased());
+ Contact contactToUnlink = lastShownContactList.get(contactIndex.getZeroBased());
+ if (!contactToUnlink.hasLinkTo(eventToUnlink)) {
+ assert !eventToUnlink.hasLinkTo(contactToUnlink) : "Both should not have links to each other";
+ commandResult += String.format(MESSAGE_NOT_LINKED, eventToUnlink.getName(), contactToUnlink.getName());
+ continue;
+ }
+ model.unlinkEventAndContact(eventToUnlink, contactToUnlink);
+ commandResult += String.format(MESSAGE_SUCCESS, eventToUnlink.getName(), contactToUnlink.getName());
+ }
+ return new CommandResult(commandResult);
+ }
+
+ private CommandResult unlinkEventAndAllContacts(Event eventToUnlink, Model model) {
+ model.unlinkAllContactsFromEvent(eventToUnlink);
+ return new CommandResult(String.format(MESSAGE_SUCCESS_CLEAR_ALL, eventToUnlink.getName()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EUnlinkCommand // instanceof handles nulls
+ && eventIndex.equals(((EUnlinkCommand) other).eventIndex)
+ && contactIndexes.equals(((EUnlinkCommand) other).contactIndexes)
+ && isClearAllLinks == ((EUnlinkCommand) other).isClearAllLinks); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EUnmarkCommand.java b/src/main/java/seedu/address/logic/commands/event/EUnmarkCommand.java
new file mode 100644
index 00000000000..187d33a72f4
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EUnmarkCommand.java
@@ -0,0 +1,82 @@
+package seedu.address.logic.commands.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+public class EUnmarkCommand extends Command implements Undoable {
+
+ public static final String COMMAND_WORD = "eunmark";
+
+ public static final String PARAMETERS = "INDEX [INDEX]...\n";
+ public static final String SYNTAX = COMMAND_WORD + " " + PARAMETERS;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Unmarks all event(s) indexed at "
+ + "the specified index(es) and displays them after the marked events.\n"
+ + "Parameters: " + PARAMETERS
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Unmarked event: %1$s";
+ public static final String MESSAGE_NOT_MARKED = "Event not marked: %1$s";
+
+ private final List indexesToUnmark;
+
+ /**
+ * Class constructor, takes in a list of {@code index}
+ */
+ public EUnmarkCommand(List indexes) {
+ requireNonNull(indexes);
+ this.indexesToUnmark = indexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ String commandResult = "";
+ List lastShownList = model.getFilteredEventList();
+ if (indexesToUnmark.stream().anyMatch(index -> index.getZeroBased() >= lastShownList.size())) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+ Collections.reverse(indexesToUnmark);
+ List eventsUnmarked = new ArrayList<>();
+ for (Index index : indexesToUnmark) {
+ Event event = lastShownList.get(index.getZeroBased());
+ commandResult += String.format("%s", generateCommandResultMessage(event, event.getIsMarked()));
+ if (event.getIsMarked()) {
+ Event newEvent = event.unmarkEvent();
+ model.setEvent(event, newEvent);
+ eventsUnmarked.add(newEvent);
+ }
+ }
+ model.rearrangeEventsInOrder(eventsUnmarked, false);
+ return new CommandResult(commandResult);
+ }
+
+ private String generateCommandResultMessage(Event event, boolean isAlreadyMarked) {
+ String message;
+ if (!isAlreadyMarked) {
+ message = String.format(MESSAGE_NOT_MARKED, event);
+ } else {
+ message = String.format(MESSAGE_SUCCESS, event);
+ }
+ return message += "\n";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EUnmarkCommand // instanceof handles nulls
+ && indexesToUnmark.equals(((EUnmarkCommand) other).indexesToUnmark)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/event/EViewCommand.java b/src/main/java/seedu/address/logic/commands/event/EViewCommand.java
new file mode 100644
index 00000000000..98ee123db84
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/event/EViewCommand.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands.event;
+
+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.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.Undoable;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
+
+
+/**
+ * Views one event in full detail in SoConnect to the user.
+ */
+public class EViewCommand extends Command implements Undoable {
+ public static final String COMMAND_WORD = "eview";
+
+ public static final String SYNTAX = COMMAND_WORD + " INDEX";
+
+ public static final String MESSAGE_SUCCESS = "Viewing Event: %1$s";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": View one event of the index provided "
+ + "on the screen with all details.\n"
+ + "Parameters: INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index index;
+
+ public EViewCommand(Index index) {
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredEventList();
+
+ Index viewIndex = index;
+ int intIndex = viewIndex.getZeroBased();
+ if (intIndex < 0 || intIndex >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+ model.setEventDisplaySetting(new EventDisplaySetting(true));
+ model.updateEventListByIndex(viewIndex);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, lastShownList.get(0)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ return other instanceof EViewCommand
+ && ((EViewCommand) other).index.equals(index);
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/general/CalendarCommand.java b/src/main/java/seedu/address/logic/commands/general/CalendarCommand.java
new file mode 100644
index 00000000000..fe9fa69a61b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/general/CalendarCommand.java
@@ -0,0 +1,22 @@
+package seedu.address.logic.commands.general;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.model.Model;
+
+/**
+ * Represents the command to open a calendar window showing all events.
+ */
+public class CalendarCommand extends Command {
+
+ public static final String COMMAND_WORD = "calendar";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_SUCCESS = "Showing calendar.";
+
+ @Override
+ public CommandResult execute(Model model) {
+ return new CommandResult(MESSAGE_SUCCESS, false, false, true);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/general/ExitCommand.java
similarity index 65%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/seedu/address/logic/commands/general/ExitCommand.java
index 3dd85a8ba90..d1681162df4 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/general/ExitCommand.java
@@ -1,5 +1,7 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.general;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
/**
@@ -9,11 +11,13 @@ public class ExitCommand extends Command {
public static final String COMMAND_WORD = "exit";
+ public static final String SYNTAX = COMMAND_WORD;
+
public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/general/HelpCommand.java
similarity index 72%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/seedu/address/logic/commands/general/HelpCommand.java
index bf824f91bd0..df6d56b4d64 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/general/HelpCommand.java
@@ -1,5 +1,7 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.general;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.model.Model;
/**
@@ -9,6 +11,8 @@ public class HelpCommand extends Command {
public static final String COMMAND_WORD = "help";
+ public static final String SYNTAX = COMMAND_WORD;
+
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
+ "Example: " + COMMAND_WORD;
@@ -16,6 +20,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, false);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/general/RedoCommand.java b/src/main/java/seedu/address/logic/commands/general/RedoCommand.java
new file mode 100644
index 00000000000..b5b3536486f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/general/RedoCommand.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.commands.general;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Represents the command to redo a change by {@code UndoCommand}.
+ */
+public class RedoCommand extends Command {
+ public static final String COMMAND_WORD = "redo";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Restore the previously undone state of addressBook";
+
+ public static final String MESSAGE_SUCCESS = "Command redone";
+
+ public static final String MESSAGE_FAIL_TO_REDO = "No command to redo.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (!model.isRedoable()) {
+ throw new CommandException(MESSAGE_FAIL_TO_REDO);
+ }
+ model.redoHistory();
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/general/UndoCommand.java b/src/main/java/seedu/address/logic/commands/general/UndoCommand.java
new file mode 100644
index 00000000000..cd06f1c7d9b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/general/UndoCommand.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.commands.general;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Represents the command to undo command changes.
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+
+ public static final String SYNTAX = COMMAND_WORD;
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Restore the previous state of addressBook";
+
+ public static final String MESSAGE_SUCCESS = "Command undone";
+
+ public static final String MESSAGE_FAIL_TO_UNDO = "No command to undo.";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (!model.isUndoable()) {
+ throw new CommandException(MESSAGE_FAIL_TO_UNDO);
+ }
+ model.undoHistory();
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
deleted file mode 100644
index 3b8bfa035e8..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new AddCommand object
- */
-public class AddCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the AddCommand
- * and returns an AddCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
-
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
-
- return new AddCommand(person);
- }
-
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..de7350994bb 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -6,15 +6,51 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.contact.CAddCommand;
+import seedu.address.logic.commands.contact.CClearCommand;
+import seedu.address.logic.commands.contact.CDeleteCommand;
+import seedu.address.logic.commands.contact.CEditCommand;
+import seedu.address.logic.commands.contact.CFindCommand;
+import seedu.address.logic.commands.contact.CListCommand;
+import seedu.address.logic.commands.contact.CMarkCommand;
+import seedu.address.logic.commands.contact.CUnmarkCommand;
+import seedu.address.logic.commands.contact.CViewCommand;
+import seedu.address.logic.commands.event.EAddCommand;
+import seedu.address.logic.commands.event.EClearCommand;
+import seedu.address.logic.commands.event.EDeleteCommand;
+import seedu.address.logic.commands.event.EEditCommand;
+import seedu.address.logic.commands.event.EFindCommand;
+import seedu.address.logic.commands.event.ELinkCommand;
+import seedu.address.logic.commands.event.EListCommand;
+import seedu.address.logic.commands.event.EMarkCommand;
+import seedu.address.logic.commands.event.ESortCommand;
+import seedu.address.logic.commands.event.EUnlinkCommand;
+import seedu.address.logic.commands.event.EUnmarkCommand;
+import seedu.address.logic.commands.event.EViewCommand;
+import seedu.address.logic.commands.general.CalendarCommand;
+import seedu.address.logic.commands.general.ExitCommand;
+import seedu.address.logic.commands.general.HelpCommand;
+import seedu.address.logic.commands.general.RedoCommand;
+import seedu.address.logic.commands.general.UndoCommand;
+import seedu.address.logic.parser.contact.CAddCommandParser;
+import seedu.address.logic.parser.contact.CDeleteCommandParser;
+import seedu.address.logic.parser.contact.CEditCommandParser;
+import seedu.address.logic.parser.contact.CFindCommandParser;
+import seedu.address.logic.parser.contact.CListCommandParser;
+import seedu.address.logic.parser.contact.CMarkCommandParser;
+import seedu.address.logic.parser.contact.CUnmarkCommandParser;
+import seedu.address.logic.parser.contact.CViewCommandParser;
+import seedu.address.logic.parser.event.EAddCommandParser;
+import seedu.address.logic.parser.event.EDeleteCommandParser;
+import seedu.address.logic.parser.event.EEditCommandParser;
+import seedu.address.logic.parser.event.EFindCommandParser;
+import seedu.address.logic.parser.event.ELinkCommandParser;
+import seedu.address.logic.parser.event.EListCommandParser;
+import seedu.address.logic.parser.event.EMarkCommandParser;
+import seedu.address.logic.parser.event.EUnlinkCommandParser;
+import seedu.address.logic.parser.event.EUnmarkCommandParser;
+import seedu.address.logic.parser.event.EViewCommandParser;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -44,23 +80,68 @@ public Command parseCommand(String userInput) throws ParseException {
final String arguments = matcher.group("arguments");
switch (commandWord) {
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
+ case CAddCommand.COMMAND_WORD:
+ return new CAddCommandParser().parse(arguments);
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
+ case CEditCommand.COMMAND_WORD:
+ return new CEditCommandParser().parse(arguments);
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
+ case CDeleteCommand.COMMAND_WORD:
+ return new CDeleteCommandParser().parse(arguments);
- case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
+ case CClearCommand.COMMAND_WORD:
+ return new CClearCommand();
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
+ case CFindCommand.COMMAND_WORD:
+ return new CFindCommandParser().parse(arguments);
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
+ case CListCommand.COMMAND_WORD:
+ return new CListCommandParser().parse(arguments);
+
+ case CViewCommand.COMMAND_WORD:
+ return new CViewCommandParser().parse(arguments);
+
+ case CMarkCommand.COMMAND_WORD:
+ return new CMarkCommandParser().parse(arguments);
+
+ case CUnmarkCommand.COMMAND_WORD:
+ return new CUnmarkCommandParser().parse(arguments);
+
+ case EAddCommand.COMMAND_WORD:
+ return new EAddCommandParser().parse(arguments);
+
+ case EEditCommand.COMMAND_WORD:
+ return new EEditCommandParser().parse(arguments);
+
+ case EDeleteCommand.COMMAND_WORD:
+ return new EDeleteCommandParser().parse(arguments);
+
+ case EClearCommand.COMMAND_WORD:
+ return new EClearCommand();
+
+ case EFindCommand.COMMAND_WORD:
+ return new EFindCommandParser().parse(arguments);
+
+ case EListCommand.COMMAND_WORD:
+ return new EListCommandParser().parse(arguments);
+
+ case ELinkCommand.COMMAND_WORD:
+ return new ELinkCommandParser().parse(arguments);
+
+ case EUnlinkCommand.COMMAND_WORD:
+ return new EUnlinkCommandParser().parse(arguments);
+
+ case ESortCommand.COMMAND_WORD:
+ return new ESortCommand();
+
+ case EViewCommand.COMMAND_WORD:
+ return new EViewCommandParser().parse(arguments);
+
+ case EMarkCommand.COMMAND_WORD:
+ return new EMarkCommandParser().parse(arguments);
+
+ case EUnmarkCommand.COMMAND_WORD:
+ return new EUnmarkCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
@@ -68,6 +149,15 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case CalendarCommand.COMMAND_WORD:
+ return new CalendarCommand();
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..c0bac3f86c4 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
@@ -57,4 +57,19 @@ public List getAllValues(Prefix prefix) {
public String getPreamble() {
return getValue(new Prefix("")).orElse("");
}
+
+ /**
+ * Checks if a prefix present in this argument multimap has a non-empty value
+ */
+ public boolean anyPrefixValueNotEmpty() {
+ return argMultimap.values().stream().anyMatch(ls -> ls.stream().anyMatch(str -> !str.isBlank()));
+ }
+
+ /**
+ * Returns true if there are no prefixes in the argument multimap.
+ */
+ public boolean noPrefixesPresent() {
+ return argMultimap.entrySet().stream()
+ .allMatch(prefixListEntry -> prefixListEntry.getKey().equals(new Prefix("")));
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..6243ec17ad1 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -11,5 +11,11 @@ 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_TELEGRAM = new Prefix("th/");
+ public static final Prefix PREFIX_ZOOM = new Prefix("z/");
+ public static final Prefix PREFIX_DELETE_TAG = new Prefix("dt/");
+ public static final Prefix PREFIX_START_TIME = new Prefix("at/");
+ public static final Prefix PREFIX_END_TIME = new Prefix("end/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/");
+ public static final Prefix PREFIX_CONTACT = new Prefix("c/");
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
deleted file mode 100644
index 845644b7dea..00000000000
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new EditCommand object
- */
-public class EditCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the EditCommand
- * and returns an EditCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public EditCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- Index index;
-
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
- }
-
- EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
- if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
- editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
- }
- if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
- editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
- }
- if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
- editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
- }
- if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
- editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
- }
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
-
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
- }
-
- return new EditCommand(index, editPersonDescriptor);
- }
-
- /**
- * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
- * If {@code tags} contain only one element which is an empty string, it will be parsed into a
- * {@code Set} containing zero tags.
- */
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
- assert tags != null;
-
- if (tags.isEmpty()) {
- return Optional.empty();
- }
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
deleted file mode 100644
index 4fb71f23103..00000000000
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import java.util.Arrays;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Parses input arguments and creates a new FindCommand object
- */
-public class FindCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the FindCommand
- * and returns a FindCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
- }
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..62d0bc280f3 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -1,18 +1,30 @@
package seedu.address.logic.parser;
import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_INDEX;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_RANGE;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
+import seedu.address.commons.core.range.Range;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Phone;
+import seedu.address.model.contact.TelegramHandle;
+import seedu.address.model.event.DateAndTime;
+import seedu.address.model.event.Description;
+import seedu.address.model.event.EndDateTime;
+import seedu.address.model.event.StartDateTime;
import seedu.address.model.tag.Tag;
/**
@@ -20,7 +32,7 @@
*/
public class ParserUtil {
- public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
@@ -35,13 +47,64 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
+ /**
+ * Parses parameter into a {@code Range} and returns it. Leading and trailing whitespaces will be trimmed.
+ * @throws ParseException if the specified range is invalid.
+ */
+ public static Range parseRange(String range) throws ParseException {
+ String trimmedRange = range.trim();
+ if (!StringUtil.isValidRange(trimmedRange)) {
+ throw new ParseException(MESSAGE_INVALID_RANGE);
+ }
+ String[] rangeArr = trimmedRange.split("-");
+ Index start = parseIndex(rangeArr[0]);
+ Index end = parseIndex(rangeArr[1]);
+ return new Range(start, end);
+ }
+
+ /**
+ * Parses parameter into a {@code Range} and returns it. Leading and trailing whitespaces will be trimmed.
+ * First, it will try to parse into an {@code Index}. If successful, the index will be converted to a
+ * Range from itself to itself. Else, it will parse into a Range.
+ * @throws ParseException if specified argument can neither be parsed into an Index nor a Range.
+ */
+ public static Range parseDeleteArgument(String args) throws ParseException {
+ try {
+ Index index = parseIndex(args);
+ return Range.convertFromIndex(index);
+ } catch (ParseException pe) {
+ return parseRange(args);
+ }
+ }
+
+ /**
+ * Parses parameter into a list of {@code Index} and returns it. Leading and trailing whitespaces will be trimmed.
+ * @throws ParseException if specified argument cannot be parsed into a Index.
+ */
+ public static List parseMarkIndexes(String args) throws ParseException {
+ String[] stringIndexes = args.split("\\s+");
+ List indexes = new ArrayList<>();
+ for (String index : stringIndexes) {
+ index = index.trim();
+ if (index.equals("")) {
+ continue;
+ }
+ Index parsedIndex = parseIndex(index.trim());
+ if (indexes.contains(parsedIndex)) {
+ throw new ParseException(Messages.MESSAGE_DUPLICATE_INDEXES_PROVIDED);
+ }
+ indexes.add(parsedIndex);
+ }
+ return indexes;
+ }
+
/**
* Parses a {@code String name} into a {@code Name}.
* Leading and trailing whitespaces will be trimmed.
*
* @throws ParseException if the given {@code name} is invalid.
*/
- public static Name parseName(String name) throws ParseException {
+ public static Name parseContactName(String name) throws ParseException {
requireNonNull(name);
String trimmedName = name.trim();
if (!Name.isValidName(trimmedName)) {
@@ -95,6 +158,99 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String telegramHandle} into an {@code TelegramHandle}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code telegramHandle} is invalid.
+ */
+ public static TelegramHandle parseTelegram(String telegramHandle) throws ParseException {
+ requireNonNull(telegramHandle);
+ String trimmedTelegram = telegramHandle.trim();
+ if (!TelegramHandle.isValidHandle(trimmedTelegram)) {
+ throw new ParseException(TelegramHandle.MESSAGE_CONSTRAINTS);
+ }
+ return new TelegramHandle(trimmedTelegram);
+ }
+
+ /**
+ * Parses a {@code String name} into a {@code Name}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static Name parseEventName(String name) throws ParseException {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ if (!Name.isValidName(trimmedName)) {
+ throw new ParseException(Name.MESSAGE_CONSTRAINTS);
+ }
+ return new Name(trimmedName);
+ }
+
+ /**
+ * Parses a {@code String startDateTime} into a {@code StartDateTime}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @param startDateTime when the event starts
+ * @throws ParseException if the given {@code startDateTime} is invalid.
+ */
+ public static StartDateTime parseStartDateTime(String startDateTime) throws ParseException {
+ requireNonNull(startDateTime);
+ String trimmedStartDateTime = startDateTime.trim();
+ if (!StartDateTime.isValidDateTime(trimmedStartDateTime)) {
+ throw new ParseException(DateAndTime.MESSAGE_CONSTRAINTS);
+ }
+ return new StartDateTime(trimmedStartDateTime);
+ }
+
+ /**
+ * Parses a {@code String endDateTime} into a {@code EndDateTime}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @param endDateTime when the event ends
+ * @throws ParseException if the given {@code endDateTime} is invalid.
+ */
+ public static EndDateTime parseEndDateTime(String endDateTime) throws ParseException {
+ requireNonNull(endDateTime);
+ String trimmedEndDateTime = endDateTime.trim();
+ if (!StartDateTime.isValidDateTime(trimmedEndDateTime)) {
+ throw new ParseException(DateAndTime.MESSAGE_CONSTRAINTS);
+ }
+ return new EndDateTime(trimmedEndDateTime);
+ }
+
+ /**
+ * Parses a {@code String description} into a {@code Description}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ */
+ public static Description parseDescription(String description) throws ParseException {
+ requireNonNull(description);
+ String trimmedDescription = description.trim();
+ if (!Description.isValidDescription(trimmedDescription)) {
+ throw new ParseException(Description.MESSAGE_CONSTRAINTS);
+ }
+ return new Description(trimmedDescription);
+ }
+
+ /**
+ * Parses a {@code String zoomLink} into a {@code ZoomLink}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code zoomLink} is invalid.
+ */
+ public static ZoomLink parseZoomLink(String zoomLink) throws ParseException {
+ requireNonNull(zoomLink);
+ String trimmedZoomLink = zoomLink.trim();
+ if (!ZoomLink.isValidZoomLink(trimmedZoomLink)) {
+ throw new ParseException(ZoomLink.MESSAGE_CONSTRAINTS);
+ }
+
+ return new ZoomLink(trimmedZoomLink);
+
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
diff --git a/src/main/java/seedu/address/logic/parser/contact/CAddCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CAddCommandParser.java
new file mode 100644
index 00000000000..cd476d26109
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CAddCommandParser.java
@@ -0,0 +1,88 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.contact.CAddCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Phone;
+import seedu.address.model.contact.TelegramHandle;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new CAddCommand object
+ */
+public class CAddCommandParser 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 CAddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_TELEGRAM, PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_EMAIL)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CAddCommand.MESSAGE_USAGE));
+ }
+
+ // Compulsory fields
+ Name name = ParserUtil.parseContactName(argMultimap.getValue(PREFIX_NAME).get());
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
+
+ // Optional fields
+ Address address = null;
+ Phone phone = null;
+ TelegramHandle handle = null;
+ ZoomLink zoomLink = null;
+ if (arePrefixesPresent(argMultimap, PREFIX_ADDRESS)) {
+ address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_PHONE)) {
+ phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_TELEGRAM)) {
+ handle = ParserUtil.parseTelegram(argMultimap.getValue(PREFIX_TELEGRAM).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_ZOOM)) {
+ zoomLink = ParserUtil.parseZoomLink(argMultimap.getValue(PREFIX_ZOOM).get());
+ }
+
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+
+ Contact contact = new Contact(name, phone, email, address, zoomLink, handle, tagList);
+
+ return new CAddCommand(contact);
+ }
+
+ /**
+ * 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/contact/CDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CDeleteCommandParser.java
new file mode 100644
index 00000000000..b9e784b86f1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CDeleteCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.range.Range;
+import seedu.address.logic.commands.contact.CDeleteCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new CDeleteCommand object
+ */
+public class CDeleteCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the CDeleteCommand
+ * and returns a CDeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CDeleteCommand parse(String args) throws ParseException {
+ try {
+ Range range = ParserUtil.parseDeleteArgument(args);
+ return new CDeleteCommand(range);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, CDeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CEditCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CEditCommandParser.java
new file mode 100644
index 00000000000..9aecff568e2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CEditCommandParser.java
@@ -0,0 +1,101 @@
+package seedu.address.logic.parser.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DELETE_TAG;
+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.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.contact.CEditCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new CEditCommand object
+ */
+public class CEditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the CEditCommand
+ * and returns an CEditCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CEditCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_ZOOM, PREFIX_TELEGRAM, PREFIX_TAG, PREFIX_DELETE_TAG);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CEditCommand.MESSAGE_USAGE), pe);
+ }
+
+ CEditCommand.EditContactDescriptor editContactDescriptor = new CEditCommand.EditContactDescriptor();
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editContactDescriptor.setName(ParserUtil.parseContactName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ editContactDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
+ editContactDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
+ editContactDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ZOOM).isPresent()) {
+ editContactDescriptor.setZoomLink(ParserUtil.parseZoomLink(argMultimap.getValue(PREFIX_ZOOM).get()));
+ }
+ if (argMultimap.getValue(PREFIX_TELEGRAM).isPresent()) {
+ editContactDescriptor.setTelegramHandle(
+ ParserUtil.parseTelegram(argMultimap.getValue(PREFIX_TELEGRAM).get()));
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editContactDescriptor::setTags);
+
+ // Check for delete all
+ if (argMultimap.getAllValues(PREFIX_DELETE_TAG).stream().anyMatch(arg -> arg.equals("*"))) {
+ editContactDescriptor.setShouldDeleteAllTags(true);
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_DELETE_TAG)).ifPresent(editContactDescriptor::setTagsToDelete);
+
+ if (!editContactDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(CEditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new CEditCommand(index, editContactDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
+ * If {@code tags} contain an asterisk, it will be ignored.
+ */
+ private Optional> parseTagsForEdit(Collection tags) throws ParseException {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ // To handle case for dt/*
+ tags.remove("*");
+ return Optional.of(ParserUtil.parseTags(tags));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CFindCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CFindCommandParser.java
new file mode 100644
index 00000000000..661c1d61773
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CFindCommandParser.java
@@ -0,0 +1,104 @@
+package seedu.address.logic.parser.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.contact.CFindCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.contact.ContactContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new CFindCommand object
+ */
+public class CFindCommandParser implements Parser {
+
+ public static final String MESSAGE_MISSING_KEYWORD = "There must be at least one keyword present for prefix '%s'.";
+ private ContactContainsKeywordsPredicate predicate;
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the CFindCommand
+ * and returns a CFindCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CFindCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_TELEGRAM, PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+
+ if (noPrefixesPresent(argMultimap)
+ && argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CFindCommand.MESSAGE_USAGE));
+ }
+
+ predicate = new ContactContainsKeywordsPredicate();
+ if (!argMultimap.getPreamble().isEmpty()) {
+ String[] nameKeywords = argMultimap.getPreamble().trim().split("\\s+");
+ predicate = new ContactContainsKeywordsPredicate(Arrays.asList(nameKeywords));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_PHONE)) {
+ predicate.setPhoneKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_PHONE));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_EMAIL)) {
+ predicate.setEmailKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_EMAIL));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_ADDRESS)) {
+ predicate.setAddressKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_ADDRESS));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_TELEGRAM)) {
+ predicate.setTelegramHandleKeyword(getPrefixValueAndSplit(argMultimap, PREFIX_TELEGRAM));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_ZOOM)) {
+ predicate.setZoomLinkKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_ZOOM));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_TAG)) {
+ predicate.setTagKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_TAG));
+ }
+ return new CFindCommand(predicate);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains present {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean noPrefixesPresent(ArgumentMultimap argumentMultimap) {
+ return Stream.of(PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_TELEGRAM, PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG)
+ .allMatch(prefix -> argumentMultimap.getValue(prefix).isEmpty());
+ }
+
+ /**
+ * Returns true if the given prefix value is present
+ *
+ * @param prefix the given prefix
+ * @return true if the value of the prefix is not empty, false otherwise
+ */
+ private static boolean isPrefixValuePresent(ArgumentMultimap argumentMultimap, Prefix prefix) {
+ return argumentMultimap.getValue(prefix).isPresent();
+ }
+
+ private static List getPrefixValueAndSplit(ArgumentMultimap argumentMultimap, Prefix prefix)
+ throws ParseException {
+ requireNonNull(argumentMultimap.getValue(prefix)); //the prefix must not contain an empty value
+ String value = argumentMultimap.getValue(prefix).get();
+ if (value.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_MISSING_KEYWORD, prefix));
+ }
+ return Stream.of(value.split("\\s+")).collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CListCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CListCommandParser.java
new file mode 100644
index 00000000000..83207d96af8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CListCommandParser.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TELEGRAM;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import seedu.address.logic.commands.contact.CListCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.contact.ContactDisplaySetting;
+
+public class CListCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the CListCommand
+ * and returns a CListCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CListCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_TELEGRAM, PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+ if (argMultimap.anyPrefixValueNotEmpty() || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CListCommand.MESSAGE_USAGE));
+ }
+ ContactDisplaySetting displaySetting;
+ if (argMultimap.noPrefixesPresent()) {
+ displaySetting = ContactDisplaySetting.DEFAULT_SETTING;
+ } else {
+ displaySetting = new ContactDisplaySetting(
+ argMultimap.getValue(PREFIX_PHONE).isPresent(),
+ argMultimap.getValue(PREFIX_EMAIL).isPresent(),
+ argMultimap.getValue(PREFIX_TELEGRAM).isPresent(),
+ argMultimap.getValue(PREFIX_ADDRESS).isPresent(),
+ argMultimap.getValue(PREFIX_ZOOM).isPresent(),
+ argMultimap.getValue(PREFIX_TAG).isPresent()
+ );
+ }
+ return new CListCommand(displaySetting);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CMarkCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CMarkCommandParser.java
new file mode 100644
index 00000000000..a3441cdff8f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CMarkCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.contact.CMarkCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class CMarkCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the CMarkCommand
+ * and returns a CMarkCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CMarkCommand parse(String args) throws ParseException {
+ if (args.trim().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CMarkCommand.MESSAGE_USAGE));
+ }
+ try {
+ List indexes = ParserUtil.parseMarkIndexes(args);
+ return new CMarkCommand(indexes);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(pe.getMessage(), CMarkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CUnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CUnmarkCommandParser.java
new file mode 100644
index 00000000000..5d6a975cd18
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CUnmarkCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.contact.CUnmarkCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class CUnmarkCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the CUnmarkCommand
+ * and returns a CUnmarkCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CUnmarkCommand parse(String args) throws ParseException {
+ if (args.trim().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CUnmarkCommand.MESSAGE_USAGE));
+ }
+ try {
+ List indexes = ParserUtil.parseMarkIndexes(args);
+ return new CUnmarkCommand(indexes);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(pe.getMessage(), CUnmarkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/contact/CViewCommandParser.java b/src/main/java/seedu/address/logic/parser/contact/CViewCommandParser.java
new file mode 100644
index 00000000000..339effd3c06
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/contact/CViewCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser.contact;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.contact.CViewCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class CViewCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the CViewCommand
+ * and returns an CViewCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public CViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new CViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, CViewCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/EAddCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EAddCommandParser.java
new file mode 100644
index 00000000000..4a0d715b742
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EAddCommandParser.java
@@ -0,0 +1,88 @@
+package seedu.address.logic.parser.event;
+
+
+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_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.event.EAddCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.event.Description;
+import seedu.address.model.event.EndDateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.StartDateTime;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new CAddCommand object
+ */
+public class EAddCommandParser 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 EAddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_START_TIME, PREFIX_END_TIME, PREFIX_DESCRIPTION,
+ PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_START_TIME) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EAddCommand.MESSAGE_USAGE));
+ }
+
+ // Compulsory fields
+ Name name = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get());
+ StartDateTime start = ParserUtil.parseStartDateTime(argMultimap.getValue(PREFIX_START_TIME).get());
+
+ // Optional fields
+ EndDateTime end = null;
+ Description description = null;
+ Address address = null;
+ ZoomLink zoom = null;
+ if (arePrefixesPresent(argMultimap, PREFIX_END_TIME)) {
+ end = ParserUtil.parseEndDateTime(argMultimap.getValue(PREFIX_END_TIME).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION)) {
+ description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_ADDRESS)) {
+ address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ }
+ if (arePrefixesPresent(argMultimap, PREFIX_ZOOM)) {
+ zoom = ParserUtil.parseZoomLink(argMultimap.getValue(PREFIX_ZOOM).get());
+ }
+
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+
+ Event event = new Event(name, start, end, description, address, zoom, tagList);
+
+ return new EAddCommand(event);
+ }
+
+ /**
+ * 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/event/EDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EDeleteCommandParser.java
new file mode 100644
index 00000000000..d902ed09bf5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EDeleteCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.parser.event;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.range.Range;
+import seedu.address.logic.commands.event.EDeleteCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new EDeleteCommand object
+ */
+public class EDeleteCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EDeleteCommand
+ * and returns a EDeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EDeleteCommand parse(String args) throws ParseException {
+ try {
+ Range range = ParserUtil.parseDeleteArgument(args);
+ return new EDeleteCommand(range);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EDeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/EEditCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EEditCommandParser.java
new file mode 100644
index 00000000000..ad19d1ab510
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EEditCommandParser.java
@@ -0,0 +1,103 @@
+package seedu.address.logic.parser.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DELETE_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.event.EEditCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new CEditCommand object
+ */
+public class EEditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EEditCommand
+ * and returns an EEditCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EEditCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_START_TIME, PREFIX_END_TIME, PREFIX_DESCRIPTION,
+ PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG, PREFIX_DELETE_TAG);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EEditCommand.MESSAGE_USAGE), pe);
+ }
+
+ EEditCommand.EditEventDescriptor editEventDescriptor = new EEditCommand.EditEventDescriptor();
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editEventDescriptor.setName(ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_START_TIME).isPresent()) {
+ editEventDescriptor.setStartDateTime(ParserUtil
+ .parseStartDateTime(argMultimap.getValue(PREFIX_START_TIME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_END_TIME).isPresent()) {
+ editEventDescriptor.setEndDateTime(ParserUtil
+ .parseEndDateTime(argMultimap.getValue(PREFIX_END_TIME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editEventDescriptor.setDescription(ParserUtil
+ .parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
+ editEventDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ZOOM).isPresent()) {
+ editEventDescriptor.setZoomLink(ParserUtil.parseZoomLink(argMultimap.getValue(PREFIX_ZOOM).get()));
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editEventDescriptor::setTags);
+
+ // Check for delete all
+ if (argMultimap.getAllValues(PREFIX_DELETE_TAG).stream().anyMatch(arg -> arg.equals("*"))) {
+ editEventDescriptor.setShouldDeleteAllTags(true);
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_DELETE_TAG)).ifPresent(editEventDescriptor::setTagsToDelete);
+
+ if (!editEventDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EEditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EEditCommand(index, editEventDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
+ * If {@code tags} contain an asterisk, it will be ignored.
+ */
+ private Optional> parseTagsForEdit(Collection tags) throws ParseException {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ // To handle case for dt/*
+ tags.remove("*");
+ return Optional.of(ParserUtil.parseTags(tags));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/EFindCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EFindCommandParser.java
new file mode 100644
index 00000000000..519fc830229
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EFindCommandParser.java
@@ -0,0 +1,102 @@
+package seedu.address.logic.parser.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.event.EFindCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.event.EventContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new EFindCommand object
+ */
+public class EFindCommandParser implements Parser {
+
+ public static final String MESSAGE_MISSING_KEYWORD = "There must be at least one keyword present for prefix '%s'.";
+ private EventContainsKeywordsPredicate predicate;
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EFindCommand
+ * and returns a EFindCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EFindCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_START_TIME, PREFIX_END_TIME,
+ PREFIX_DESCRIPTION, PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+
+ if (noPrefixesPresent(argMultimap)
+ && argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EFindCommand.MESSAGE_USAGE));
+ }
+
+ predicate = new EventContainsKeywordsPredicate();
+ if (!argMultimap.getPreamble().isEmpty()) {
+ String[] nameKeywords = argMultimap.getPreamble().trim().split("\\s+");
+ predicate = new EventContainsKeywordsPredicate(Arrays.asList(nameKeywords));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_START_TIME)) {
+ predicate.setStartDateTimeKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_START_TIME));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_END_TIME)) {
+ predicate.setEndDateTimeKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_END_TIME));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_ADDRESS)) {
+ predicate.setAddressKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_ADDRESS));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_DESCRIPTION)) {
+ predicate.setDescriptionKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_DESCRIPTION));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_ZOOM)) {
+ predicate.setZoomLinkKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_ZOOM));
+ }
+ if (isPrefixValuePresent(argMultimap, PREFIX_TAG)) {
+ predicate.setTagKeywords(getPrefixValueAndSplit(argMultimap, PREFIX_TAG));
+ }
+ return new EFindCommand(predicate);
+ }
+ /**
+ * Returns true if none of the prefixes contains present {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean noPrefixesPresent(ArgumentMultimap argumentMultimap) {
+ return Stream.of(PREFIX_START_TIME, PREFIX_END_TIME,
+ PREFIX_ADDRESS, PREFIX_DESCRIPTION, PREFIX_ZOOM, PREFIX_TAG)
+ .allMatch(prefix -> argumentMultimap.getValue(prefix).isEmpty());
+ }
+ /**
+ * Returns true if the given prefix value is present
+ *
+ * @param prefix the given prefix
+ * @return true if the value of the prefix is not empty, false otherwise
+ */
+ private static boolean isPrefixValuePresent(ArgumentMultimap argumentMultimap, Prefix prefix) {
+ return argumentMultimap.getValue(prefix).isPresent();
+ }
+
+ private static List getPrefixValueAndSplit(ArgumentMultimap argumentMultimap, Prefix prefix)
+ throws ParseException {
+ requireNonNull(argumentMultimap.getValue(prefix)); //the prefix must not contain an empty value
+ String value = argumentMultimap.getValue(prefix).get();
+ if (value.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_MISSING_KEYWORD, prefix));
+ }
+ return Stream.of(value.split("\\s+")).collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/ELinkCommandParser.java b/src/main/java/seedu/address/logic/parser/event/ELinkCommandParser.java
new file mode 100644
index 00000000000..88660fc9c6a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/ELinkCommandParser.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.parser.event;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.event.ELinkCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ELinkCommand object
+ */
+public class ELinkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ELinkCommand
+ * and returns an ELinkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ELinkCommand parse(String args) throws ParseException {
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CONTACT);
+ if (!arePrefixesPresent(argMultimap, PREFIX_CONTACT)
+ || argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ELinkCommand.MESSAGE_USAGE));
+ }
+ String index = argMultimap.getPreamble();
+ try {
+ Index eventIndex = ParserUtil.parseIndex(index);
+ List listOfValues = argMultimap.getAllValues(PREFIX_CONTACT);
+ Set contactIndexes = new HashSet<>();
+ for (String value : listOfValues) {
+ contactIndexes.add(ParserUtil.parseIndex(value));
+ }
+ return new ELinkCommand(eventIndex, contactIndexes);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ELinkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ /**
+ * 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/event/EListCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EListCommandParser.java
new file mode 100644
index 00000000000..b9c9d94d1a1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EListCommandParser.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.parser.event;
+
+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_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_START_TIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ZOOM;
+
+import seedu.address.logic.commands.event.EListCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.event.EventDisplaySetting;
+
+/**
+ * Parses input arguments and creates a new EListCommand object
+ */
+public class EListCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EListCommand
+ * and returns an EListCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EListCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_START_TIME, PREFIX_END_TIME, PREFIX_DESCRIPTION,
+ PREFIX_ADDRESS, PREFIX_ZOOM, PREFIX_TAG);
+ if (argMultimap.anyPrefixValueNotEmpty() || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EListCommand.MESSAGE_USAGE));
+ }
+ EventDisplaySetting displaySetting;
+ if (argMultimap.noPrefixesPresent()) {
+ displaySetting = EventDisplaySetting.DEFAULT_SETTING;
+ } else {
+ displaySetting = new EventDisplaySetting(
+ argMultimap.getValue(PREFIX_START_TIME).isPresent(),
+ argMultimap.getValue(PREFIX_END_TIME).isPresent(),
+ argMultimap.getValue(PREFIX_DESCRIPTION).isPresent(),
+ argMultimap.getValue(PREFIX_ADDRESS).isPresent(),
+ argMultimap.getValue(PREFIX_ZOOM).isPresent(),
+ argMultimap.getValue(PREFIX_TAG).isPresent()
+ );
+ }
+ return new EListCommand(displaySetting);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/EMarkCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EMarkCommandParser.java
new file mode 100644
index 00000000000..9f723d10908
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EMarkCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser.event;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.event.EMarkCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class EMarkCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the EMarkCommand
+ * and returns a EMarkCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EMarkCommand parse(String args) throws ParseException {
+ if (args.trim().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EMarkCommand.MESSAGE_USAGE));
+ }
+ List indexes = ParserUtil.parseMarkIndexes(args);
+ return new EMarkCommand(indexes);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/event/EUnlinkCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EUnlinkCommandParser.java
new file mode 100644
index 00000000000..7c0904bd6fd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EUnlinkCommandParser.java
@@ -0,0 +1,63 @@
+package seedu.address.logic.parser.event;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.event.EUnlinkCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new EUnlinkCommand object
+ */
+public class EUnlinkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EUnlinkCommand
+ * and returns an EUnlinkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EUnlinkCommand parse(String args) throws ParseException {
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CONTACT);
+ if (!arePrefixesPresent(argMultimap, PREFIX_CONTACT)
+ || argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EUnlinkCommand.MESSAGE_USAGE));
+ }
+ String index = argMultimap.getPreamble();
+ try {
+ Index eventIndex = ParserUtil.parseIndex(index);
+ List listOfValues = argMultimap.getAllValues(PREFIX_CONTACT);
+ Set contactIndexes = new HashSet<>();
+ for (String value : listOfValues) {
+ if (value.equals("*")) { // delete all links
+ return new EUnlinkCommand(eventIndex, Set.of(), true);
+ }
+ contactIndexes.add(ParserUtil.parseIndex(value));
+ }
+ return new EUnlinkCommand(eventIndex, contactIndexes, false);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EUnlinkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ /**
+ * 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/event/EUnmarkCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EUnmarkCommandParser.java
new file mode 100644
index 00000000000..502237f734c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/event/EUnmarkCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser.event;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.event.EUnmarkCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class EUnmarkCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the EUnmarkCommand
+ * and returns a EUnmarkCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EUnmarkCommand parse(String args) throws ParseException {
+ if (args.trim().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EUnmarkCommand.MESSAGE_USAGE));
+ }
+ try {
+ List indexes = ParserUtil.parseMarkIndexes(args);
+ return new EUnmarkCommand(indexes);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(pe.getMessage(), EUnmarkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/event/EViewCommandParser.java
similarity index 54%
rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
rename to src/main/java/seedu/address/logic/parser/event/EViewCommandParser.java
index 522b93081cc..fce195007ea 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/event/EViewCommandParser.java
@@ -1,29 +1,27 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.event;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.event.EViewCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.ParserUtil;
import seedu.address.logic.parser.exceptions.ParseException;
-/**
- * Parses input arguments and creates a new DeleteCommand object
- */
-public class DeleteCommandParser implements Parser {
-
+public class EViewCommandParser implements Parser {
/**
- * Parses the given {@code String} of arguments in the context of the DeleteCommand
- * and returns a DeleteCommand object for execution.
+ * Parses the given {@code String} of arguments in the context of the EViewCommand
+ * and returns an EViewCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
- public DeleteCommand parse(String args) throws ParseException {
+ @Override
+ public EViewCommand parse(String args) throws ParseException {
try {
Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
+ return new EViewCommand(index);
} catch (ParseException pe) {
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EViewCommand.MESSAGE_USAGE), pe);
}
}
-
}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..14789f2a9fd 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -3,18 +3,24 @@
import static java.util.Objects.requireNonNull;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.UniqueContactList;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.UniqueEventList;
/**
* Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
+ * Duplicates are not allowed (by .isSameContact comparison)
*/
public class AddressBook implements ReadOnlyAddressBook {
- private final UniquePersonList persons;
+ private final UniqueContactList contacts;
+ private final UniqueEventList events;
/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
@@ -24,27 +30,56 @@ public class AddressBook implements ReadOnlyAddressBook {
* among constructors.
*/
{
- persons = new UniquePersonList();
+ contacts = new UniqueContactList();
+ events = new UniqueEventList();
}
- public AddressBook() {}
+ public AddressBook() {
+ }
/**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
+ * Creates an AddressBook using the Contacts and Events in the {@code toBeCopied}
*/
public AddressBook(ReadOnlyAddressBook toBeCopied) {
this();
resetData(toBeCopied);
}
+ /**
+ * Create a copy of the current addressBook
+ *
+ * @return a copy of current addressBook
+ */
+ public AddressBook copy() {
+ AddressBook toCopy = new AddressBook();
+ toCopy.setContacts(contacts.copy());
+ toCopy.setEvents(events.copy());
+ return toCopy;
+ }
+
//// list overwrite operations
/**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
+ * Replaces the contents of the contact list with {@code contacts}.
+ * {@code contacts} must not contain duplicate contacts.
+ */
+ public void setContacts(List contacts) {
+ this.contacts.setContacts(contacts);
+ }
+
+ /**
+ * Replaces the contents of the event list with {@code events}.
+ * {@code events} must not contain duplicate events.
*/
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
+ public void setEvents(List events) {
+ this.events.setEvents(events);
+ }
+
+ /**
+ * Resets the existing data of contacts of this {@code AddressBook}.
+ */
+ public void resetContacts() {
+ this.contacts.resetContacts();
}
/**
@@ -53,68 +88,209 @@ public void setPersons(List persons) {
public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
- setPersons(newData.getPersonList());
+ setContacts(newData.getContactList());
+ setEvents(newData.getEventList());
+ }
+
+ /**
+ * Places marked {@code contacts} at the top of the list.
+ * Places the newly marked contacts or replaces newly unmarked contacts
+ * in the order specified in {@code contactsToRearrange} if specified.
+ */
+ public void rearrangeContactsInOrder(List contactsToRearrange, boolean isMarked) {
+ contacts.rearrangeContactsInOrder(contactsToRearrange, isMarked);
+ }
+
+ //// event-level operations
+
+ /**
+ * Returns true if an event with the same name as {@code event} exists in the address book.
+ */
+ public boolean hasEvent(Event event) {
+ requireNonNull(event);
+ return events.contains(event);
+ }
+
+ /**
+ * Adds an event to the address book.
+ * The event must not already exist in the address book.
+ */
+ public void addEvent(Event e) {
+ events.add(e);
+ Event.addToMap(e);
+ }
+
+ /**
+ * Replaces the given event {@code target} in the list with {@code editedEvent}.
+ * {@code target} must exist in the address book.
+ * The event name of {@code editedEvent} must not be the same as another existing event in the address book.
+ */
+ public void setEvent(Event target, Event editedEvent) {
+ requireNonNull(editedEvent);
+
+ events.setEvent(target, editedEvent);
+ }
+
+ /**
+ * Sorts the filtered event list to show all upcoming events. This will change the order of the filtered list
+ * and remove any events which have concluded.
+ */
+ public void sortEvents() {
+ events.sortEvents();
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeEvent(Event key) {
+ // unlink all the contacts linked to event before removing event
+ Event updatedEvent = unlinkContactsFromEvent(key);
+ events.remove(updatedEvent);
}
- //// person-level operations
+ /**
+ * Creates a link between the event and contact.
+ */
+ public void linkEventAndContact(Event event, Contact contact) {
+ Event updatedEvent = event.linkTo(contact);
+ Contact updatedContact = contact.linkTo(event);
+ setEvent(event, updatedEvent);
+ setContact(contact, updatedContact);
+ }
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Removes the link between the event and contact.
*/
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
+ public void unlinkEventAndContact(Event event, Contact contact) {
+ Event updatedEvent = event.unlink(contact);
+ Contact updatedContact = contact.unlink(event);
+ setEvent(event, updatedEvent);
+ setContact(contact, updatedContact);
}
/**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
+ * Unlink all the contacts linked to the given event {@code e}, but does not remove the stored links in the event.
*/
- public void addPerson(Person p) {
- persons.add(p);
+ public Event unlinkContactsFromEvent(Event e) {
+ Set contactsUuid = e.getLinkedContacts();
+ contactsUuid.iterator()
+ .forEachRemaining(contactUuid -> {
+ Contact linkedContact = Contact.findByUuid(contactUuid);
+ setContact(linkedContact, linkedContact.unlink(e));
+ });
+ Event updatedEvent = e.clearAllLinks();
+ setEvent(e, updatedEvent);
+ return updatedEvent;
}
/**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
+ * Resets the existing data of events of this {@code AddressBook}.
+ */
+ public void resetEvents() {
+ this.events.resetEvents();
+ }
+
+ /**
+ * Places marked {@code events} at the top of the list.
+ * Places the newly marked events or replaces newly unmarked events
+ * in the order specified in {@code eventsToRearrange} if specified.
+ */
+ public void rearrangeEventsInOrder(List eventsToRearrange, boolean isMarked) {
+ events.rearrangeEventsInOrder(eventsToRearrange, isMarked);
+ }
+
+ //// contact-level operations
+
+ /**
+ * Returns true if a contact with the same identity as {@code contact} exists in the address book.
+ */
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ return contacts.contains(contact);
+ }
+
+ /**
+ * Adds a contact to the address book.
+ * The contact must not already exist in the address book.
+ */
+ public void addContact(Contact c) {
+ contacts.add(c);
+ Contact.addToMap(c);
+ }
+
+ /**
+ * Replaces the given contact {@code target} in the list with {@code editedContact}.
* {@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.
+ * The contact name of {@code editedContact} must not be the same as another existing contact in the address book.
*/
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
+ public void setContact(Contact target, Contact editedContact) {
+ requireNonNull(editedContact);
- persons.setPerson(target, editedPerson);
+ contacts.setContact(target, editedContact);
}
/**
* Removes {@code key} from this {@code AddressBook}.
* {@code key} must exist in the address book.
*/
- public void removePerson(Person key) {
- persons.remove(key);
+ public void removeContact(Contact key) {
+ // unlink all the events linked to contact before removing contact
+ Contact updatedContact = unlinkEventsFromContact(key);
+ contacts.remove(updatedContact);
+ }
+
+ /**
+ * Unlink all the events linked to the given contact {@code c}, but does not remove the stored links in the contact.
+ */
+ public Contact unlinkEventsFromContact(Contact c) {
+ Set eventsUuid = c.getLinkedEvents();
+ eventsUuid.iterator()
+ .forEachRemaining(eventUuid -> {
+ Event linkedEvent = Event.findByUuid(eventUuid);
+ setEvent(linkedEvent, linkedEvent.unlink(c));
+ });
+ Contact updatedContact = c.clearAllLinks();
+ setContact(c, updatedContact);
+ return updatedContact;
+ }
+
+ /**
+ * Update the list of contacts and list of events in their respective UUID map.
+ */
+ public void updateDataMaps() {
+ contacts.updateContactMap();
+ events.updateEventMap();
}
//// util methods
@Override
public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
- // TODO: refine later
+ return contacts.asUnmodifiableObservableList().size() + " contacts\n"
+ + events.asUnmodifiableObservableList().size() + " events";
+ }
+
+ @Override
+ public ObservableList getContactList() {
+ return contacts.asUnmodifiableObservableList();
}
@Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
+ public ObservableList getEventList() {
+ return events.asUnmodifiableObservableList();
}
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
- || (other instanceof AddressBook // instanceof handles nulls
- && persons.equals(((AddressBook) other).persons));
+ || (other instanceof AddressBook // instanceof handles nulls
+ && contacts.equals(((AddressBook) other).contacts)
+ && events.equals(((AddressBook) other).events));
}
@Override
public int hashCode() {
- return persons.hashCode();
+ return Objects.hash(contacts, events);
}
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..72d29638d20 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,18 +1,26 @@
package seedu.address.model;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
/**
* The API of the Model component.
*/
public interface Model {
/** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_CONTACTS = unused -> true;
+ Predicate PREDICATE_HIDE_ALL_CONTACTS = unused -> false;
+ Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true;
+ Predicate PREDICATE_HIDE_ALL_EVENTS = unused -> false;
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -34,6 +42,26 @@ public interface Model {
*/
void setGuiSettings(GuiSettings guiSettings);
+ /**
+ * Returns the display settings of the events.
+ */
+ EventDisplaySetting getEventDisplaySetting();
+
+ /**
+ * Sets the display settings of the events.
+ */
+ void setEventDisplaySetting(EventDisplaySetting eventDisplaySetting);
+
+ /**
+ * Returns the display settings of the contacts.
+ */
+ ContactDisplaySetting getContactDisplaySetting();
+
+ /**
+ * Sets the display settings of the contacts.
+ */
+ void setContactDisplaySetting(ContactDisplaySetting displaySetting);
+
/**
* Returns the user prefs' address book file path.
*/
@@ -49,39 +77,176 @@ public interface Model {
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
- /** Returns the AddressBook */
+ /** Returns the current AddressBook */
ReadOnlyAddressBook getAddressBook();
+ /** Adds new state of AddressBook to its history list */
+ void commitHistory();
+
+ /** Restores the previous addressBook state from its history */
+ void undoHistory();
+
+ /** Restores the previously undone state from its history */
+ void redoHistory();
+
+ /** Checks if the current state of addressBook is undoable */
+ boolean isUndoable();
+
+ /** Check if the current state of addressBook is redoable */
+ boolean isRedoable();
+
+ //=========== Contact Management =============================================================
+
+ /**
+ * Returns true if a contact with the same identity as {@code contact} exists in the address book.
+ */
+ boolean hasContact(Contact contact);
+
+ /**
+ * Deletes the given contact.
+ * The contact must exist in the address book.
+ */
+ void deleteContact(Contact target);
+
+ /**
+ * Adds the given contact.
+ * {@code contact} must not already exist in the address book.
+ */
+ void addContact(Contact contact);
+
+ /**
+ * Replaces the given contact {@code target} with {@code editedContact}.
+ * {@code target} must exist in the address book.
+ * The contact name of {@code editedContact} must not be the same as another existing contact in the address book.
+ */
+ void setContact(Contact target, Contact editedContact);
+
+ /**
+ * Remove all contacts from SoConnect.
+ */
+ void resetContacts();
+
+ /** Returns an unmodifiable view of the filtered contact list */
+ ObservableList getFilteredContactList();
+
+ /**
+ * Updates the filter of the filtered contact list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredContactList(Predicate super Contact> predicate);
+
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * This will change the order of the filtered list, marked contacts will be placed at the top of the list.
+ * Places the newly marked contacts or replaces unmarked contacts
+ * in the order specified in {@code indexes} and depending on {@code isMark}.
*/
- boolean hasPerson(Person person);
+ void rearrangeContactsInOrder(List contacts, boolean isMark);
/**
- * Deletes the given person.
- * The person must exist in the address book.
+ * Updates the filter of the filtered contact list to show the contact at {@code index}.
+ * @throws NullPointerException if {@code index} is null.
*/
- void deletePerson(Person target);
+ void updateContactListByIndex(Index index);
+
+ //=========== Event Management =============================================================
+
+ /**
+ * Returns true if an event with the same name as {@code event} exists in the address book.
+ */
+ boolean hasEvent(Event event);
+
+ /**
+ * Deletes the given event.
+ * The event must exist in the address book.
+ */
+ void deleteEvent(Event target);
/**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
+ * Adds the given event.
+ * {@code event} must not already exist in the address book.
*/
- void addPerson(Person person);
+ void addEvent(Event event);
/**
- * Replaces the given person {@code target} with {@code editedPerson}.
+ * Replaces the given event {@code target} with {@code editedEvent}.
* {@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.
+ * The event name of {@code editedEvent} must not be the same as another existing event in the address book.
*/
- void setPerson(Person target, Person editedPerson);
+ void setEvent(Event target, Event editedEvent);
+
+ /** Returns an unmodifiable view of the filtered event list */
+ ObservableList getFilteredEventList();
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
+ /**
+ * Removes all events from SoConnect.
+ */
+ void resetEvents();
/**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ * Updates the filter of the filtered event list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
*/
- void updateFilteredPersonList(Predicate predicate);
+ void updateFilteredEventList(Predicate super Event> predicate);
+
+ /**
+ * Links an event to a contact. Both the event and contact will have reference to each other.
+ * @param event The event to link to contact.
+ * @param contact The contact to link to event.
+ */
+ void linkEventAndContact(Event event, Contact contact);
+
+ /**
+ * Unlinks an event to a contact. Both references between the contact and the event will be removed.
+ * @param event The event to unlink from contact.
+ * @param contact The contact to unlink from event.
+ */
+ void unlinkEventAndContact(Event event, Contact contact);
+
+ /** Unlinks an {@code event} from all its linked contacts. Both references between the contact and the event
+ * will be removed.
+ */
+ void unlinkAllContactsFromEvent(Event event);
+
+ /**
+ * Sorts the filtered event list to show all upcoming events. This will change the order of the filtered list
+ * and remove any events which have concluded.
+ */
+ void sortUpcomingFilteredEventList();
+
+ /**
+ * Updates the filter of the filtered event list to show the event at {@code index}.
+ * @throws NullPointerException if {@code index} is null.
+ */
+ void updateEventListByIndex(Index index);
+
+ /**
+ * Re-render contact cards in UI to show the most updated version.
+ * @param useBackSamePredicate whether the same predicate should be refreshed.
+ * Otherwise, the filter will be set to all contacts.
+ */
+ void rerenderContactCards(boolean useBackSamePredicate);
+
+ /**
+ * Re-render event cards in UI to show the most updated version.
+ * @param useBackSamePredicate whether the same predicate should be refreshed.
+ * Otherwise, the filter will be set to all contacts.
+ */
+ void rerenderEventCards(boolean useBackSamePredicate);
+
+ /**
+ * Re-render both contact and event cards in UI to show the most updated version.
+ */
+ void rerenderAllCards();
+
+ /**
+ * This will change the order of the filtered list, marked events will be placed at the top of the list.
+ * Places the newly marked events or replaces unmarked events
+ * in the order specified in {@code indexes} and depending on {@code isMark}.
+ */
+ void rearrangeEventsInOrder(List events, boolean isMark);
+
+ /**
+ * Removes all links between contacts and events.
+ */
+ void removeAllLinks();
}
diff --git a/src/main/java/seedu/address/model/ModelDisplaySetting.java b/src/main/java/seedu/address/model/ModelDisplaySetting.java
new file mode 100644
index 00000000000..acf675f615d
--- /dev/null
+++ b/src/main/java/seedu/address/model/ModelDisplaySetting.java
@@ -0,0 +1,134 @@
+package seedu.address.model;
+
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CONTACTS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
+
+/**
+ * Keep track of display setting of each model
+ */
+public class ModelDisplaySetting {
+ private final ContactDisplaySetting contactDisplaySetting;
+ private final EventDisplaySetting eventDisplaySetting;
+ private final Predicate super Contact> contactDisplayPredicate;
+ private final Predicate super Event> eventDisplayPredicate;
+
+ /**
+ * Constructor of a default model display setting that shows all fields and all contacts and events
+ */
+ public ModelDisplaySetting() {
+ contactDisplaySetting = ContactDisplaySetting.DEFAULT_SETTING;
+ eventDisplaySetting = EventDisplaySetting.DEFAULT_SETTING;
+ contactDisplayPredicate = PREDICATE_SHOW_ALL_CONTACTS;
+ eventDisplayPredicate = PREDICATE_SHOW_ALL_EVENTS;
+ }
+
+ /**
+ * Constructor of a customised display setting
+ *
+ * @param contactDisplaySetting contact display setting to be set
+ * @param eventDisplaySetting event display setting to be set
+ * @param contactDisplayPredicate contact display predicate to be set
+ * @param eventDisplayPredicate event display predicate to be set
+ */
+ public ModelDisplaySetting(
+ ContactDisplaySetting contactDisplaySetting, EventDisplaySetting eventDisplaySetting,
+ Predicate super Contact> contactDisplayPredicate,
+ Predicate super Event> eventDisplayPredicate) {
+ this.contactDisplaySetting = contactDisplaySetting;
+ this.eventDisplaySetting = eventDisplaySetting;
+ this.contactDisplayPredicate = contactDisplayPredicate;
+ this.eventDisplayPredicate = eventDisplayPredicate;
+ }
+
+ public ContactDisplaySetting getContactDisplaySetting() {
+ return contactDisplaySetting;
+ }
+
+ public EventDisplaySetting getEventDisplaySetting() {
+ return eventDisplaySetting;
+ }
+
+ public Predicate super Contact> getContactDisplayPredicate() {
+ return contactDisplayPredicate;
+ }
+
+ public Predicate super Event> getEventDisplayPredicate() {
+ return eventDisplayPredicate;
+ }
+
+ /**
+ * Create a copy of the current model display setting
+ *
+ * @return a copy of model display setting
+ */
+ public ModelDisplaySetting copy() {
+ return new ModelDisplaySetting(contactDisplaySetting, eventDisplaySetting, contactDisplayPredicate,
+ eventDisplayPredicate);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof ModelDisplaySetting)) {
+ return false;
+ }
+
+ ModelDisplaySetting other = (ModelDisplaySetting) obj;
+ return eventDisplaySetting.equals(other.eventDisplaySetting)
+ && contactDisplaySetting.equals(other.contactDisplaySetting);
+ }
+
+ /**
+ * Returns a new {@code ModelDisplaySetting} object with the same properties,
+ * except for the {@code ContactDisplaySetting}.
+ */
+ public ModelDisplaySetting differentContactDisplaySetting(ContactDisplaySetting displaySetting) {
+ return new ModelDisplaySetting(displaySetting, eventDisplaySetting,
+ contactDisplayPredicate, eventDisplayPredicate);
+ }
+
+ /**
+ * Returns a new {@code ModelDisplaySetting} object with the same properties,
+ * except for the {@code EventDisplaySetting}.
+ */
+ public ModelDisplaySetting differentEventDisplaySetting(EventDisplaySetting displaySetting) {
+ return new ModelDisplaySetting(contactDisplaySetting, displaySetting,
+ contactDisplayPredicate, eventDisplayPredicate);
+ }
+
+ /**
+ * Returns a new {@code ModelDisplaySetting} object with the same properties,
+ * except for the {@code predicate} for contacts.
+ */
+ public ModelDisplaySetting differentContactDisplayPredicate(Predicate super Contact> predicate) {
+ return new ModelDisplaySetting(contactDisplaySetting, eventDisplaySetting,
+ predicate, eventDisplayPredicate);
+ }
+
+ /**
+ * Returns a new {@code ModelDisplaySetting} object with the same properties,
+ * except for the {@code predicate} for events.
+ */
+ public ModelDisplaySetting differentEventDisplayPredicate(Predicate super Event> predicate) {
+ return new ModelDisplaySetting(contactDisplaySetting, eventDisplaySetting,
+ contactDisplayPredicate, predicate);
+ }
+
+ @Override
+ public String toString() {
+ return "ModelDisplaySetting{"
+ + "contactDisplaySetting=" + contactDisplaySetting
+ + ", eventDisplaySetting=" + eventDisplaySetting
+ + '}';
+ }
+}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 0650c954f5c..a98ba8c4e76 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,6 +4,7 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
@@ -11,7 +12,12 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.ContactDisplaySetting;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDisplaySetting;
+import seedu.address.model.history.ModelHistory;
/**
* Represents the in-memory model of the address book data.
@@ -19,9 +25,13 @@
public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+ private final ModelHistory modelHistory = new ModelHistory();
private final AddressBook addressBook;
private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
+ private final FilteredList filteredContacts;
+ private final FilteredList filteredEvents;
+
+ private ModelDisplaySetting modelDisplaySetting = new ModelDisplaySetting();
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -34,11 +44,17 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredContacts = new FilteredList<>(this.addressBook.getContactList());
+ filteredEvents = new FilteredList<>(this.addressBook.getEventList());
+ modelHistory.commit(addressBook, modelDisplaySetting);
}
+ /**
+ * Initializes a ModelManager with the default addressBook and userPrefs.
+ */
public ModelManager() {
this(new AddressBook(), new UserPrefs());
+ modelHistory.commit(addressBook, modelDisplaySetting);
}
//=========== UserPrefs ==================================================================================
@@ -65,6 +81,37 @@ public void setGuiSettings(GuiSettings guiSettings) {
userPrefs.setGuiSettings(guiSettings);
}
+ //=========== AddressBook Display Setting =======================================================================
+
+ /**
+ * Clear history of all display setting of model
+ */
+ public void clearHistory() {
+ modelHistory.clearHistory();
+ }
+
+ public EventDisplaySetting getEventDisplaySetting() {
+ return modelDisplaySetting.getEventDisplaySetting();
+ }
+
+ public void setEventDisplaySetting(EventDisplaySetting eventDisplaySetting) {
+ requireNonNull(eventDisplaySetting);
+ modelDisplaySetting = modelDisplaySetting.differentEventDisplaySetting(eventDisplaySetting);
+ }
+
+ @Override
+ public ContactDisplaySetting getContactDisplaySetting() {
+ return modelDisplaySetting.getContactDisplaySetting();
+ }
+
+ @Override
+ public void setContactDisplaySetting(ContactDisplaySetting displaySetting) {
+ requireNonNull(displaySetting);
+ modelDisplaySetting = modelDisplaySetting.differentContactDisplaySetting(displaySetting);
+ }
+
+ //=========== AddressBook Storage ================================================================================
+
@Override
public Path getAddressBookFilePath() {
return userPrefs.getAddressBookFilePath();
@@ -88,45 +135,190 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ //=========== Versioned AddressBook ================================================================================
+
+ @Override
+ public void commitHistory() {
+ modelHistory.commit(addressBook.copy(), modelDisplaySetting);
+ }
+
+ @Override
+ public void undoHistory() {
+ ModelHistory.HistoryInstance instance = modelHistory.undo();
+ addressBook.resetData(instance.getAddressBook());
+ addressBook.updateDataMaps();
+ modelDisplaySetting = instance.getDisplaySetting();
+ filteredContacts.setPredicate(modelDisplaySetting.getContactDisplayPredicate());
+ filteredEvents.setPredicate(modelDisplaySetting.getEventDisplayPredicate());
+ rerenderAllCards();
+ }
+
+ @Override
+ public void redoHistory() {
+ ModelHistory.HistoryInstance instance = modelHistory.redo();
+ addressBook.resetData(instance.getAddressBook());
+ addressBook.updateDataMaps();
+ modelDisplaySetting = instance.getDisplaySetting();
+ filteredContacts.setPredicate(modelDisplaySetting.getContactDisplayPredicate());
+ filteredEvents.setPredicate(modelDisplaySetting.getEventDisplayPredicate());
+ rerenderAllCards();
+ }
+
+ @Override
+ public boolean isUndoable() {
+ return modelHistory.isUndoable();
+ }
+
+ @Override
+ public boolean isRedoable() {
+ return modelHistory.isRedoable();
+ }
+
+ //=========== Manage Contacts ======================
+
+ @Override
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ return addressBook.hasContact(contact);
+ }
+
+ @Override
+ public void deleteContact(Contact target) {
+ addressBook.removeContact(target);
+ }
+
+ @Override
+ public void addContact(Contact contact) {
+ addressBook.addContact(contact);
+ updateFilteredContactList(PREDICATE_SHOW_ALL_CONTACTS);
+ }
+
+ @Override
+ public void setContact(Contact target, Contact editedContact) {
+ requireAllNonNull(target, editedContact);
+ addressBook.setContact(target, editedContact);
+ }
+
+ @Override
+ public void resetContacts() {
+ // not necessary to remove links from contacts since they will be deleted, but just to be strict
+ // about the bidirectional relationship
+ removeAllLinks();
+
+ this.addressBook.resetContacts();
+ }
+
+ //=========== Manage Events ======================
+
+ @Override
+ public boolean hasEvent(Event event) {
+ requireNonNull(event);
+ return addressBook.hasEvent(event);
+ }
+
@Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
+ public void deleteEvent(Event target) {
+ addressBook.removeEvent(target);
}
@Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ public void addEvent(Event event) {
+ addressBook.addEvent(event);
+ updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
}
@Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ public void setEvent(Event target, Event editedEvent) {
+ requireAllNonNull(target, editedEvent);
+ addressBook.setEvent(target, editedEvent);
}
@Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
+ public void resetEvents() {
+ // not necessary to remove links from events since they will be deleted, but just to be strict
+ // about the bidirectional relationship
+ removeAllLinks();
- addressBook.setPerson(target, editedPerson);
+ this.addressBook.resetEvents();
}
- //=========== Filtered Person List Accessors =============================================================
+ //=========== Filtered Contact List Accessors =====================
/**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
+ * Returns an unmodifiable view of the list of {@code Contact} backed by the internal list of
* {@code versionedAddressBook}
*/
@Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
+ public ObservableList getFilteredContactList() {
+ return filteredContacts;
}
@Override
- public void updateFilteredPersonList(Predicate predicate) {
+ public void updateFilteredContactList(Predicate super Contact> predicate) {
requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
+ filteredContacts.setPredicate(predicate);
+ modelDisplaySetting = modelDisplaySetting.differentContactDisplayPredicate(predicate);
+ }
+
+ @Override
+ public void updateContactListByIndex(Index index) {
+ requireNonNull(index);
+ Contact targetContact = filteredContacts.get(index.getZeroBased());
+ Predicate super Contact> predicate = curr -> curr.isSameContact(targetContact);
+ modelDisplaySetting = modelDisplaySetting.differentContactDisplayPredicate(predicate);
+ filteredContacts.setPredicate(predicate);
+ }
+ @Override
+ public void rearrangeContactsInOrder(List contacts, boolean isMarked) {
+ addressBook.rearrangeContactsInOrder(contacts, isMarked);
+ }
+
+ //=========== Filtered Event List Accessors =======================
+ /**
+ * Returns an unmodifiable view of the list of {@code Event} backed by the internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getFilteredEventList() {
+ return filteredEvents;
+ }
+
+ @Override
+ public void updateFilteredEventList(Predicate super Event> predicate) {
+ requireNonNull(predicate);
+ filteredEvents.setPredicate(predicate);
+ modelDisplaySetting = modelDisplaySetting.differentEventDisplayPredicate(predicate);
+ }
+
+ @Override
+ public void sortUpcomingFilteredEventList() {
+ Predicate super Event> currentPredicate = filteredEvents.getPredicate();
+ // Remove events that have passed
+ addressBook.sortEvents();
+ updateFilteredEventList(getNewPredicate(currentPredicate));
+ }
+
+ /**
+ * Returns the new predicate for displaying only upcoming and ongoing events
+ */
+ private static Predicate super Event> getNewPredicate(Predicate super Event> originalPredicate) {
+ return event -> (originalPredicate == null || originalPredicate.test(event))
+ && ((event.getEndDateAndTime() == null && event.getStartDateAndTime().isNotBeforeNow())
+ || (event.getEndDateAndTime() != null && event.getEndDateAndTime().isNotBeforeNow()));
+ }
+
+ @Override
+ public void updateEventListByIndex(Index index) {
+ requireNonNull(index);
+ Event targetEvent = filteredEvents.get(index.getZeroBased());
+ Predicate super Event> predicate = curr -> curr.isSameEvent(targetEvent);
+ filteredEvents.setPredicate(predicate);
+ modelDisplaySetting = modelDisplaySetting.differentEventDisplayPredicate(predicate);
+ }
+
+ @Override
+ public void rearrangeEventsInOrder(List events, boolean isMark) {
+ addressBook.rearrangeEventsInOrder(events, isMark);
}
@Override
@@ -145,7 +337,55 @@ public boolean equals(Object obj) {
ModelManager other = (ModelManager) obj;
return addressBook.equals(other.addressBook)
&& userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
+ && filteredContacts.equals(other.filteredContacts)
+ && filteredEvents.equals(other.filteredEvents)
+ && modelDisplaySetting.equals(other.modelDisplaySetting);
+ }
+
+ @Override
+ public void linkEventAndContact(Event event, Contact contact) {
+ requireAllNonNull(event, contact);
+
+ addressBook.linkEventAndContact(event, contact);
+ }
+
+ @Override
+ public void unlinkEventAndContact(Event event, Contact contact) {
+ requireAllNonNull(event, contact);
+
+ addressBook.unlinkEventAndContact(event, contact);
+ }
+
+ @Override
+ public void unlinkAllContactsFromEvent(Event event) {
+ addressBook.unlinkContactsFromEvent(event);
}
+ @Override
+ public void rerenderContactCards(boolean useBackSamePredicate) {
+ ModelHistory.HistoryInstance historyInstance = modelHistory.getCurrentHistoryInstance();
+ Predicate super Contact> oldPred = historyInstance.getDisplaySetting().getContactDisplayPredicate();
+ updateFilteredContactList(PREDICATE_HIDE_ALL_CONTACTS); // Hide first to update the contact cards.
+ updateFilteredContactList(useBackSamePredicate ? oldPred : PREDICATE_SHOW_ALL_CONTACTS);
+ }
+
+ @Override
+ public void rerenderEventCards(boolean useBackSamePredicate) {
+ ModelHistory.HistoryInstance historyInstance = modelHistory.getCurrentHistoryInstance();
+ Predicate super Event> oldPred = historyInstance.getDisplaySetting().getEventDisplayPredicate();
+ updateFilteredEventList(PREDICATE_HIDE_ALL_EVENTS); // Hide first to update the event cards.
+ updateFilteredEventList(useBackSamePredicate ? oldPred : PREDICATE_SHOW_ALL_EVENTS);
+ }
+
+ @Override
+ public void rerenderAllCards() {
+ rerenderContactCards(true);
+ rerenderEventCards(true);
+ }
+
+ @Override
+ public void removeAllLinks() {
+ filteredEvents.forEach(event -> setEvent(event, event.clearAllLinks()));
+ filteredContacts.forEach(contact -> setContact(contact, contact.clearAllLinks()));
+ }
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..e791892c6d2 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -1,7 +1,8 @@
package seedu.address.model;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.event.Event;
/**
* Unmodifiable view of an address book
@@ -9,9 +10,14 @@
public interface ReadOnlyAddressBook {
/**
- * Returns an unmodifiable view of the persons list.
- * This list will not contain any duplicate persons.
+ * Returns an unmodifiable view of the contacts list.
+ * This list will not contain any duplicate contacts.
*/
- ObservableList getPersonList();
+ ObservableList getContactList();
+ /**
+ * Returns an unmodifiable view of the events list.
+ * This list will not contain any duplicate events.
+ */
+ ObservableList getEventList();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..dfbafb49f9a 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -14,7 +14,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path addressBookFilePath = Paths.get("data" , "soconnect.json");
/**
* Creates a {@code UserPrefs} with default values.
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/common/Address.java
similarity index 70%
rename from src/main/java/seedu/address/model/person/Address.java
rename to src/main/java/seedu/address/model/common/Address.java
index 60472ca22a0..30080644d9d 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/common/Address.java
@@ -1,10 +1,14 @@
-package seedu.address.model.person;
+package seedu.address.model.common;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
/**
- * Represents a Person's address in the address book.
+ * Represents address of an event or contact in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
*/
public class Address {
@@ -31,7 +35,7 @@ public Address(String address) {
}
/**
- * Returns true if a given string is a valid email.
+ * Returns true if a given string is a valid address.
*/
public static boolean isValidAddress(String test) {
return test.matches(VALIDATION_REGEX);
@@ -49,6 +53,16 @@ public boolean equals(Object other) {
&& value.equals(((Address) other).value)); // state check
}
+ /**
+ * Checks if this {@code value} contains any of {@code strings}
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ assert value != null : "the value should not be null";
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(value, string));
+ }
+
@Override
public int hashCode() {
return value.hashCode();
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/common/Name.java
similarity index 75%
rename from src/main/java/seedu/address/model/person/Name.java
rename to src/main/java/seedu/address/model/common/Name.java
index 79244d71cf7..8025f6450ac 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/common/Name.java
@@ -1,10 +1,14 @@
-package seedu.address.model.person;
+package seedu.address.model.common;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
/**
- * Represents a Person's name in the address book.
+ * Represents the name of a contact or an event in SoConnect.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
public class Name {
@@ -51,6 +55,15 @@ public boolean equals(Object other) {
&& fullName.equals(((Name) other).fullName)); // state check
}
+ /**
+ * Checks if this {@code fullName} contains any of keywords in {@code strings}
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(fullName, string));
+ }
+
@Override
public int hashCode() {
return fullName.hashCode();
diff --git a/src/main/java/seedu/address/model/common/ZoomLink.java b/src/main/java/seedu/address/model/common/ZoomLink.java
new file mode 100644
index 00000000000..b253a33da10
--- /dev/null
+++ b/src/main/java/seedu/address/model/common/ZoomLink.java
@@ -0,0 +1,61 @@
+package seedu.address.model.common;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Represents zoom link of a contact (usually a professor or tutor).
+ * Guarantees: immutable
+ */
+public class ZoomLink {
+
+ public static final String MESSAGE_CONSTRAINTS = "Zoom link can take in any value and it should not be blank.";
+
+ public static final String VALIDATION_REGEX = "^(?!\\s*$).+";
+
+ public final String link;
+
+ /**
+ * Constructs a {@code ZoomLink}.
+ *
+ * @param link A valid zoom link.
+ */
+ public ZoomLink(String link) {
+ requireNonNull(link);
+ checkArgument(isValidZoomLink(link), MESSAGE_CONSTRAINTS);
+ this.link = link;
+ }
+
+ /**
+ * Returns true if a given string is a valid zoom link.
+ */
+ public static boolean isValidZoomLink(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return link;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ZoomLink // instanceof handles nulls
+ && link.equals(((ZoomLink) other).link)); // state check
+ }
+
+ /**
+ * Checks if this {@code link} contains any keywords in {@code strings}
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ assert link != null : "the link should not be null";
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(link, string));
+ }
+}
diff --git a/src/main/java/seedu/address/model/contact/Contact.java b/src/main/java/seedu/address/model/contact/Contact.java
new file mode 100644
index 00000000000..5b3559181a6
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/Contact.java
@@ -0,0 +1,334 @@
+package seedu.address.model.contact;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+import seedu.address.model.common.Address;
+import seedu.address.model.common.Name;
+import seedu.address.model.common.ZoomLink;
+import seedu.address.model.event.Event;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a Contact in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Contact {
+ // stores references to all contacts, accessible by their unique UUIDs
+ private static HashMap map = new HashMap<>();
+
+ // Identity fields
+ private final Name name;
+ private final Phone phone;
+ private final Email email;
+ private final TelegramHandle telegramHandle;
+ private final UUID uuid;
+
+ // Data fields
+ private final Address address;
+ private final ZoomLink zoomLink;
+ private final Set tags = new HashSet<>();
+ private final Set linkedEvents = new HashSet<>();
+ private final boolean isMarked;
+
+ /**
+ * Name, email and tags must be present and not null.
+ */
+ public Contact(
+ Name name, Phone phone, Email email, Address address, ZoomLink zoomLink,
+ TelegramHandle telegramHandle, Set tags) {
+ requireAllNonNull(name, email, tags);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.telegramHandle = telegramHandle;
+ this.zoomLink = zoomLink;
+ this.uuid = UUID.randomUUID(); // to generate a uuid to uniquely identify contact
+ this.isMarked = false;
+ }
+
+ /**
+ * This constructor is for creating contact stored in storage. The contact stored in storage contains information
+ * of uuid and events linked to it, in addition to information about other fields.
+ * This constructor ensures that everytime the application loads the data from storage, the uuid of the contact
+ * stays the same and contains uuid of events that are linked.
+ */
+ public Contact(
+ Name name, Phone phone, Email email, Address address, ZoomLink zoomLink,
+ TelegramHandle telegramHandle, Set tags, UUID uuid, Set linkedEvents, boolean isMarked) {
+ requireAllNonNull(name, email, tags, uuid, linkedEvents);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.telegramHandle = telegramHandle;
+ this.zoomLink = zoomLink;
+ this.uuid = uuid;
+ this.linkedEvents.addAll(linkedEvents);
+ this.isMarked = isMarked;
+ }
+
+ /**
+ * Adds the contact to the hashmap that stores references to all contacts.
+ * If the hashmap already contains the UUID of the contact as key, the value associated to the
+ * key will be replaced to the contact passed as parameter to this method.
+ *
+ * @param contact The contact to be added.
+ */
+ public static void addToMap(Contact contact) {
+ map.put(contact.getUuid(), contact);
+ }
+
+ /**
+ * Returns a contact with the unique UUID that is passed in to the method.
+ * The UUID passed as parameter MUST be a valid UUID that is stored in the hashmap as a key.
+ *
+ * @param uuid The unique identifier for a contact in the hashmap.
+ */
+ public static Contact findByUuid(UUID uuid) {
+ assert map.containsKey(uuid) : "The uuid must be valid and already in the hashmap as a key.";
+ return map.get(uuid);
+ }
+
+ public Name getName() {
+ return name;
+ }
+
+ public Phone getPhone() {
+ return phone;
+ }
+
+ public Email getEmail() {
+ return email;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public TelegramHandle getTelegramHandle() {
+ return telegramHandle;
+ }
+
+ public ZoomLink getZoomLink() {
+ return zoomLink;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getTags() {
+ return Collections.unmodifiableSet(tags);
+ }
+
+ /**
+ * Returns a unmodifiable set of UUIDs, each uniquely represents an event, that are linked to the contact object
+ * that calls this method.
+ */
+ public Set getLinkedEvents() {
+ return Collections.unmodifiableSet(linkedEvents);
+ }
+
+ public boolean getIsMarked() {
+ return isMarked;
+ }
+
+ /**
+ * Checks if this {@code name} contains any keywords in {code strings}
+ */
+ public boolean nameAnyMatch(List strings) {
+ return name.containsString(strings);
+ }
+
+ /**
+ * Checks if this {@code email} contains any keywords in {code strings}
+ */
+ public boolean emailAnyMatch(List strings) {
+ return email.containsString(strings);
+ }
+
+ /**
+ * Checks if this {@code phone} contains any keywords in {code strings}
+ */
+ public boolean phoneAnyMatch(List strings) {
+ return (phone != null) && phone.containsString(strings);
+ }
+
+ /**
+ * Checks if this {@code address} contains any keywords in {code strings}
+ */
+ public boolean addressAnyMatch(List strings) {
+ return (address != null) && address.containsString(strings);
+ }
+
+ /**
+ * Checks if this {@code telegramHandle} contains any keywords in {code strings}
+ */
+ public boolean telegramHandleAnyMatch(List strings) {
+ return (telegramHandle != null) && telegramHandle.containsString(strings);
+ }
+
+ /**
+ * Checks if this {@code zoomLink} contains any keywords in {code strings}
+ */
+ public boolean zoomLinkAnyMatch(List strings) {
+ return (zoomLink != null) && zoomLink.containsString(strings);
+ }
+
+ /**
+ * Checks if at least one of the {@code tags} contains any keywords in {code strings}
+ */
+ public boolean anyTagsContain(List strings) {
+ return tags.stream().anyMatch(tag -> tag.containsString(strings));
+ }
+
+ /**
+ * Returns true if both contacts have the same name.
+ * This defines a weaker notion of equality between two contacts.
+ */
+ public boolean isSameContact(Contact otherContact) {
+ if (otherContact == this) {
+ return true;
+ }
+
+ return otherContact != null
+ && otherContact.getName().equals(getName());
+ }
+
+ /**
+ * Checks if the contact is linked to a particular event.
+ */
+ public boolean hasLinkTo(Event event) {
+ UUID eventUuid = event.getUuid();
+ return linkedEvents.contains(eventUuid);
+ }
+
+ /**
+ * Links the event to the contact object that calls this method.
+ *
+ * @param event The event to be linked with.
+ * @return The contact that has link to the event passed in as parameter.
+ */
+ public Contact linkTo(Event event) {
+ Set updatedLinkedEvents = new HashSet<>(linkedEvents);
+ updatedLinkedEvents.add(event.getUuid());
+ Contact updatedContact = new Contact(name, phone, email, address, zoomLink, telegramHandle, tags,
+ uuid, updatedLinkedEvents, isMarked);
+ addToMap(updatedContact); // must update the map to represent the latest changes
+ return updatedContact;
+ }
+
+ /**
+ * Removes the link between the event and the contact object that calls this method.
+ *
+ * @param event The event to be unlinked.
+ * @return The contact that has no link to the event passed in as parameter.
+ */
+ public Contact unlink(Event event) {
+ Set updatedLinkedEvents = new HashSet<>(linkedEvents);
+ updatedLinkedEvents.remove(event.getUuid());
+ Contact updatedContact = new Contact(name, phone, email, address, zoomLink, telegramHandle, tags,
+ uuid, updatedLinkedEvents, isMarked);
+ addToMap(updatedContact);
+ return updatedContact;
+ }
+
+ /**
+ * Removes all links to the contact object that calls this method.
+ *
+ * @return The contact that has no link to any contacts.
+ */
+ public Contact clearAllLinks() {
+ Contact updatedContact = new Contact(name, phone, email, address, zoomLink, telegramHandle, tags,
+ uuid, new HashSet<>(), isMarked);
+ addToMap(updatedContact);
+ return updatedContact;
+ }
+
+ /**
+ * Marks contact object that calls this method.
+ *
+ * @return The contact that has been marked.
+ */
+ public Contact markContact() {
+ Contact updatedContact =
+ new Contact(name, phone, email, address, zoomLink, telegramHandle, tags, uuid, linkedEvents, true);
+ addToMap(updatedContact);
+ return updatedContact;
+ }
+
+ /**
+ * Removes the mark in the contact object that calls this method.
+ *
+ * @return The contact that has been un-marked.
+ */
+ public Contact unmarkContact() {
+ Contact updatedContact =
+ new Contact(name, phone, email, address, zoomLink, telegramHandle, tags, uuid, linkedEvents, false);
+ addToMap(updatedContact);
+ return updatedContact;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(getName())
+ .append("; Email: ")
+ .append(getEmail())
+ .append(getPhone() != null ? "; Phone: " + getPhone() : "") // optional
+ .append(getAddress() != null ? "; Address: " + getAddress() : "") // optional
+ .append(getZoomLink() != null ? "; Zoom Link: " + getZoomLink() : "") // optional
+ .append(getTelegramHandle() != null ? "; Telegram: " + getTelegramHandle() : ""); // optional
+
+ Set tags = getTags();
+ if (!tags.isEmpty()) {
+ builder.append("; Tags: ");
+ tags.forEach(builder::append);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns true if both contacts have the same details.
+ * This defines a stronger notion of equality between two contacts.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ Contact contact = (Contact) other;
+ return Objects.equals(getName(), contact.getName())
+ && Objects.equals(getPhone(), contact.getPhone())
+ && Objects.equals(getEmail(), contact.getEmail())
+ && Objects.equals(getTelegramHandle(), contact.getTelegramHandle())
+ && Objects.equals(getZoomLink(), contact.getZoomLink())
+ && Objects.equals(getAddress(), contact.getAddress())
+ && Objects.equals(getTags(), contact.getTags())
+ && Objects.equals(getIsMarked(), contact.getIsMarked());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ getName(), getPhone(), getEmail(), getTelegramHandle(), getZoomLink(), getAddress(), getTags());
+ }
+}
diff --git a/src/main/java/seedu/address/model/contact/ContactContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/contact/ContactContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..4fbf853d345
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/ContactContainsKeywordsPredicate.java
@@ -0,0 +1,88 @@
+package seedu.address.model.contact;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Tests that a {@code Contact} contains any of the keywords given.
+ */
+public class ContactContainsKeywordsPredicate implements Predicate {
+ private final List nameKeywords;
+ private List phoneKeywords = new ArrayList<>();
+ private List emailKeywords = new ArrayList<>();
+ private List telegramHandleKeywords = new ArrayList<>();
+ private List addressKeywords = new ArrayList<>();
+ private List zoomLinkKeywords = new ArrayList<>();
+ private List tagKeywords = new ArrayList<>();
+
+ /**
+ * Creates a {@code ContactContainsKeywordsPredicate} object with the name keywords.
+ */
+ public ContactContainsKeywordsPredicate(List nameKeywords) {
+ requireNonNull(nameKeywords);
+ this.nameKeywords = nameKeywords;
+ }
+
+ /**
+ * Creates a {@code ContactContainsKeywordsPredicate} object with no keywords.
+ */
+ public ContactContainsKeywordsPredicate() {
+ nameKeywords = new ArrayList<>();
+ }
+
+ public void setPhoneKeywords(List phoneKeywords) {
+ requireNonNull(phoneKeywords);
+ this.phoneKeywords = phoneKeywords;
+ }
+
+ public void setEmailKeywords(List emailKeywords) {
+ requireNonNull(emailKeywords);
+ this.emailKeywords = emailKeywords;
+ }
+
+ public void setAddressKeywords(List addressKeywords) {
+ requireNonNull(addressKeywords);
+ this.addressKeywords = addressKeywords;
+ }
+
+ public void setTelegramHandleKeyword(List telegramHandleKeywords) {
+ requireNonNull(telegramHandleKeywords);
+ this.telegramHandleKeywords = telegramHandleKeywords;
+ }
+
+ public void setZoomLinkKeywords(List zoomLinkKeywords) {
+ requireNonNull(zoomLinkKeywords);
+ this.zoomLinkKeywords = zoomLinkKeywords;
+ }
+
+ public void setTagKeywords(List tagKeywords) {
+ requireNonNull(tagKeywords);
+ this.tagKeywords = tagKeywords;
+ }
+
+ @Override
+ public boolean test(Contact contact) { // applied Law of Demeter, don't access contact fields' strings
+ requireNonNull(contact);
+ return contact.nameAnyMatch(nameKeywords) || contact.phoneAnyMatch(phoneKeywords)
+ || contact.emailAnyMatch(emailKeywords) || contact.addressAnyMatch(addressKeywords)
+ || contact.zoomLinkAnyMatch(zoomLinkKeywords) || contact.telegramHandleAnyMatch(telegramHandleKeywords)
+ || contact.anyTagsContain(tagKeywords);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsKeywordsPredicate // instanceof handles nulls
+ && nameKeywords.equals(((ContactContainsKeywordsPredicate) other).nameKeywords)
+ && phoneKeywords.equals(((ContactContainsKeywordsPredicate) other).phoneKeywords)
+ && emailKeywords.equals(((ContactContainsKeywordsPredicate) other).emailKeywords)
+ && addressKeywords.equals(((ContactContainsKeywordsPredicate) other).addressKeywords)
+ && telegramHandleKeywords.equals(((ContactContainsKeywordsPredicate) other).telegramHandleKeywords)
+ && zoomLinkKeywords.equals(((ContactContainsKeywordsPredicate) other).zoomLinkKeywords)
+ && tagKeywords.equals(((ContactContainsKeywordsPredicate) other).tagKeywords)); // state check
+ }
+}
+
diff --git a/src/main/java/seedu/address/model/contact/ContactDisplaySetting.java b/src/main/java/seedu/address/model/contact/ContactDisplaySetting.java
new file mode 100644
index 00000000000..a6c36764a04
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/ContactDisplaySetting.java
@@ -0,0 +1,114 @@
+package seedu.address.model.contact;
+
+import java.util.Objects;
+
+/**
+ * Contains all the display settings for contacts in the model manager.
+ */
+public class ContactDisplaySetting {
+ public static final ContactDisplaySetting DEFAULT_SETTING = new ContactDisplaySetting(false);
+ private final boolean willDisplayPhone;
+ private final boolean willDisplayEmail;
+ private final boolean willDisplayTelegramHandle;
+ private final boolean willDisplayAddress;
+ private final boolean willDisplayZoomLink;
+ private final boolean willDisplayTags;
+ private final boolean isViewingFull;
+
+ /**
+ * Creates a new {@code ContactDisplaySetting} with the given settings.
+ * This is usually used in {@code CListCommand}, where the {@code isViewingFull} is always false.
+ */
+ public ContactDisplaySetting(
+ boolean willDisplayPhone, boolean willDisplayEmail, boolean willDisplayTelegramHandle,
+ boolean willDisplayAddress,
+ boolean willDisplayZoomLink, boolean willDisplayTags) {
+ this.willDisplayPhone = willDisplayPhone;
+ this.willDisplayEmail = willDisplayEmail;
+ this.willDisplayTelegramHandle = willDisplayTelegramHandle;
+ this.willDisplayAddress = willDisplayAddress;
+ this.willDisplayZoomLink = willDisplayZoomLink;
+ this.willDisplayTags = willDisplayTags;
+ this.isViewingFull = false;
+ }
+
+ /**
+ * Creates a new {@code ContactDisplaySetting} with the given settings.
+ * By default, all willDisplayXXX fields will be true.
+ */
+ public ContactDisplaySetting(boolean isViewingFull) {
+ willDisplayPhone = true;
+ willDisplayEmail = true;
+ willDisplayTelegramHandle = true;
+ willDisplayAddress = true;
+ willDisplayZoomLink = true;
+ willDisplayTags = true;
+ this.isViewingFull = isViewingFull;
+ }
+
+ public boolean willDisplayAddress() {
+ return willDisplayAddress;
+ }
+
+ public boolean willDisplayZoomLink() {
+ return willDisplayZoomLink;
+ }
+
+ public boolean willDisplayTags() {
+ return willDisplayTags;
+ }
+
+ public boolean isViewingFull() {
+ return isViewingFull;
+ }
+
+ public boolean willDisplayPhone() {
+ return willDisplayPhone;
+ }
+
+ public boolean willDisplayEmail() {
+ return willDisplayEmail;
+ }
+
+ public boolean willDisplayTelegramHandle() {
+ return willDisplayTelegramHandle;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ContactDisplaySetting)) {
+ return false;
+ }
+ ContactDisplaySetting that = (ContactDisplaySetting) o;
+ return willDisplayPhone() == that.willDisplayPhone() && willDisplayEmail() == that.willDisplayEmail()
+ && willDisplayTelegramHandle() == that.willDisplayTelegramHandle()
+ && willDisplayAddress() == that.willDisplayAddress()
+ && willDisplayZoomLink() == that.willDisplayZoomLink()
+ && willDisplayTags() == that.willDisplayTags()
+ && isViewingFull() == that.isViewingFull();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ willDisplayPhone(), willDisplayEmail(), willDisplayTelegramHandle(),
+ willDisplayAddress(), willDisplayZoomLink(), willDisplayTags(), isViewingFull());
+ }
+
+ /** For debugging/logging purposes. */
+ @Override
+ public String toString() {
+ return "ContactDisplaySetting{"
+ + "willDisplayPhone=" + willDisplayPhone
+ + ", willDisplayEmail=" + willDisplayEmail
+ + ", willDisplayTelegramHandle=" + willDisplayTelegramHandle
+ + ", willDisplayAddress=" + willDisplayAddress
+ + ", willDisplayZoomLink=" + willDisplayZoomLink
+ + ", willDisplayTags=" + willDisplayTags
+ + ", isViewingFull=" + isViewingFull
+ + '}';
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/contact/Email.java
similarity index 84%
rename from src/main/java/seedu/address/model/person/Email.java
rename to src/main/java/seedu/address/model/contact/Email.java
index f866e7133de..f781a6f3912 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/contact/Email.java
@@ -1,10 +1,14 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
/**
- * Represents a Person's email in the address book.
+ * Represents a Contact's email in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
*/
public class Email {
@@ -21,6 +25,7 @@ public class Email {
+ " - end with a domain label at least 2 characters long\n"
+ " - have each domain label start and end with alphanumeric characters\n"
+ " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
+
// alphanumeric and special characters
private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
@@ -63,6 +68,16 @@ public boolean equals(Object other) {
&& value.equals(((Email) other).value)); // state check
}
+ /**
+ * Checks if this {@code value} contains any keywords in {@code strings}
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ assert value != null : "the value should not be null";
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(value, string));
+ }
+
@Override
public int hashCode() {
return value.hashCode();
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/contact/Phone.java
similarity index 72%
rename from src/main/java/seedu/address/model/person/Phone.java
rename to src/main/java/seedu/address/model/contact/Phone.java
index 872c76b382f..95e8d7da915 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/contact/Phone.java
@@ -1,10 +1,14 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
/**
- * Represents a Person's phone number in the address book.
+ * Represents a Contact's phone number in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
*/
public class Phone {
@@ -45,6 +49,16 @@ public boolean equals(Object other) {
&& value.equals(((Phone) other).value)); // state check
}
+ /**
+ * Checks if this {@code value} contains any keywords in {@code strings}
+ * @param strings
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(value, string));
+ }
+
@Override
public int hashCode() {
return value.hashCode();
diff --git a/src/main/java/seedu/address/model/contact/TelegramHandle.java b/src/main/java/seedu/address/model/contact/TelegramHandle.java
new file mode 100644
index 00000000000..b2051be8a3d
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/TelegramHandle.java
@@ -0,0 +1,82 @@
+package seedu.address.model.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.List;
+
+import seedu.address.commons.util.StringUtil;
+
+public class TelegramHandle {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Telegram handles should only contain alphanumeric characters or underscores, with minimum 5 characters.";
+
+ /*
+ * At least 5 characters of a-z or A-Z or 0-9 or _.
+ */
+ public static final String VALIDATION_REGEX = "[\\p{Alnum}_]{5,}";
+
+ /**
+ * The telegram username of a contact.
+ */
+ public final String handle;
+
+ /**
+ * The link to the telegram account of the handle.
+ */
+ public final String link;
+
+ /**
+ * Constructs a {@code TelegramHandle}.
+ *
+ * @param handle A valid Telegram handle.
+ */
+ public TelegramHandle(String handle) {
+ requireNonNull(handle);
+ checkArgument(isValidHandle(handle), MESSAGE_CONSTRAINTS);
+ this.handle = handle;
+ link = "https://t.me/" + handle;
+ }
+
+ /**
+ * Returns true if a given string is a valid Telegram handle.
+ */
+ public static boolean isValidHandle(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Gets the link to the contact's telegram chat.
+ */
+ public String getTelegramLink() {
+ return link;
+ }
+
+ @Override
+ public String toString() {
+ return handle;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TelegramHandle // instanceof handles nulls
+ && handle.equals(((TelegramHandle) other).handle)); // state check
+ }
+
+ /**
+ * Checks if this {@code handle} contains any keywords in {@code strings}
+ */
+ public boolean containsString(List strings) {
+ requireNonNull(strings);
+ assert handle != null : "the handle should not be null";
+ return strings.stream().anyMatch(string ->
+ StringUtil.containsWordIgnoreCase(handle, string));
+ }
+
+ @Override
+ public int hashCode() {
+ return handle.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/contact/UniqueContactList.java b/src/main/java/seedu/address/model/contact/UniqueContactList.java
new file mode 100644
index 00000000000..1e25b079189
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/UniqueContactList.java
@@ -0,0 +1,185 @@
+package seedu.address.model.contact;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.contact.exceptions.ContactNotFoundException;
+import seedu.address.model.contact.exceptions.DuplicateContactException;
+
+/**
+ * A list of contacts that enforces uniqueness between its elements and does not allow nulls.
+ * A contact is considered unique by comparing using {@code Contact#isSameContact(Contact)}. As such, adding and
+ * updating of contacts uses Contact#isSameContact(Contact) for equality so as to ensure that the contact being added
+ * or updated is unique in terms of identity in the UniqueContactList. However, the removal of a contact uses
+ * Contact#equals(Object) so as to ensure that the contact with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Contact#isSameContact(Contact)
+ */
+public class UniqueContactList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent contact as the given argument.
+ */
+ public boolean contains(Contact toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameContact);
+ }
+
+ /**
+ * Adds a contact to the list.
+ * The contact must not already exist in the list.
+ */
+ public void add(Contact toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateContactException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the contact {@code target} in the list with {@code editedContact}.
+ * {@code target} must exist in the list.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact in the list.
+ */
+ public void setContact(Contact target, Contact editedContact) {
+ requireAllNonNull(target, editedContact);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new ContactNotFoundException();
+ }
+
+ if (!target.isSameContact(editedContact) && contains(editedContact)) {
+ throw new DuplicateContactException();
+ }
+
+ internalList.set(index, editedContact);
+ }
+
+ /**
+ * Removes the equivalent contact from the list.
+ * The contact must exist in the list.
+ */
+ public void remove(Contact toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new ContactNotFoundException();
+ }
+ }
+
+ public void setContacts(UniqueContactList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code contacts}.
+ * {@code contacts} must not contain duplicate contacts.
+ */
+ public void setContacts(List contacts) {
+ requireAllNonNull(contacts);
+ if (!contactsAreUnique(contacts)) {
+ throw new DuplicateContactException();
+ }
+
+ internalList.setAll(contacts);
+ }
+
+ /**
+ * Remove all the contents of this list.
+ */
+ public void resetContacts() {
+ internalList.clear();
+ }
+
+ /**
+ * Moves marked contacts to the top of the list.
+ * Places the newly marked contacts or replaces newly unmarked contacts
+ * in the order specified in {@code contacts} and
+ * based on {@code isMarked} which signals whether this method is called by
+ * CMarkCommand or otherwise.
+ */
+ public void rearrangeContactsInOrder(List contacts, boolean isMarked) {
+ ObservableList tempList = FXCollections.observableArrayList();
+ if (isMarked) {
+ tempList.addAll(contacts);
+ tempList.addAll(internalList.filtered(contact -> !contacts.contains(contact)));
+ } else {
+ tempList.addAll(internalList.filtered(Contact::getIsMarked));
+ tempList.addAll(internalList.filtered(c -> !c.getIsMarked()));
+ }
+ internalList.clear();
+ internalList.addAll(tempList);
+ }
+
+ /**
+ * Update the UUID map in contacts.
+ */
+ public void updateContactMap() {
+ for (Contact contact : internalList) {
+ Contact.addToMap(contact);
+ }
+ }
+
+ /**
+ * Get a copy of uniqueContactList
+ *
+ * @return a copy of a UniqueContactList
+ */
+ public ObservableList copy() {
+ List