-Adds a person to the address book.
+**:information_source: More information about the help window:**
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+After you successfully clicked on the `Copy URL` button, you will see the following window:
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+
+
+You're now ready to paste the link into your browser!
-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`
+### Exiting the program : `exit`
+
+Exits the program.
-### Listing all persons : `list`
+Format: `exit`
-Shows a list of all persons in the address book.
+### Updating contacts
-Format: `list`
+There is a set of information that you can save for a contact. This section contains the commands for
+adding contacts and modifying their information.
-### Editing a person : `edit`
+### Adding a contact : `add`
-Edits an existing person in the address book.
+You can add a contact to the address book with the **address as an optional field**.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Your contacts are uniquely identified by their `NAME` which is **case-sensitive**.
+e.g. `Alex Yeoh` is different from `alex yeoh`
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+Format: `add n/NAME p/PHONE_NUMBER e/EMAIL [a/ADDRESS] [t/TAG]…`
+
+
:bulb: **Tip:**
+A contact can have 0 or 1 address
+
+
:bulb: **Tip:**
+A contact can have any number of tags (including 0)
+
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+* `add n/John Doe p/98765432 e/johnd@example.com`
+* `add n/Mary Jane p/12345678 e/maryJ@example.com a/Bukit Timah t/completed`
-Finds persons whose names contain any of the given keywords.
+### Editing a contact : `edit`
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Edits an existing contact in the address book. This command can be used in detailed view.
-* 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`
+Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]…`
+
+* Edits the contact at the specified `INDEX`. The index refers to the index number shown in the displayed contact list.
+ The index **must be a positive integer** 1, 2, 3, …
+* At least one of the optional fields must be provided.
+* Existing values will be updated to the input values.
+
+
:bulb: **Tip:**
+To edit tags, use assign and unassign commands
+
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
-### Deleting a person : `delete`
+* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st contact to be `91234567`
+ and `johndoe@example.com` respectively.
+* `edit 1 n/John` Edits the name of the 1st contact to be `John`.
+
+Format in detailed view: `edit [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]…`
+
+Example:
+
+* `edit p/88438809 e/alex_yeoh@example.com` Edits the phone number and email address of the contact in detailed view to
+ be `88438809` and `alex_yeoh@example.com` respectively.
-Deletes the specified person from the address book.
+### Deleting a contact : `delete`
+
+Deletes the specified contact from the address book. This command **only works in list view**.
Format: `delete INDEX`
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
+* Deletes the contact at the specified `INDEX`.
+* The index refers to the index number shown in the displayed contact list.
* The index **must be a positive integer** 1, 2, 3, …
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+
+* `list` followed by `delete 2` deletes the 2nd contact in the address book.
+* `find Betsy` followed by `delete 1` deletes the 1st contact in the results of the `find` command.
+
+### Creating a tag : `tag`
+
+Creates a tag that can be assigned to any contact. This command can be used in detailed view with the same format.
+
+Format: `tag TAGNAME`
+
+* A tag with the same `TAGNAME` can **only be created once**.
+* The `TAGNAME` is case-insensitive. e.g. creating the tag `friends` will not allow `Friends` to be created.
+* `TAGNAME` must be alphanumeric. e.g. `Hello`, `Friends`, `Colleagues`
+* `TAGNAME` such as `-1`, `Sub Contractors` are not allowed. i.e. non-alphanumeric characters, including spaces.
+* To create a tag named `Sub Contractors`, eliminate the whitespace in between in order for it to be a valid tag.
+ e.g. `SubContractors`
+
+
:bulb: **Tip:**
+You can create meaningful tags to assign your contacts with! With tags, you can search for contacts assigned to that particular tag!
+Tags must be created first before you can perform any tag related features.
+
+
+List of tag related features:
+* [Assign Tag](#assigning-a-tag-to-a-contact--assign)
+* [Unassign Tag](#unassigning-a-tag-from-a-contact--unassign)
+* [Find Tag](#locating-contacts-by-tag--findtag)
+* [Delete Tag](#deleting-a-tag--deltag)
+
+Example:
+
+* `tag Friends` creates a tag `Friends` to be stored in the address book.
+ 
+
+### Assigning a tag to a contact : `assign`
+
+Assigns a created tag to a contact. This command can be used in detailed view.
+
+Format: `assign INDEX TAGNAME`
+
+* Assigns a `TAG` with a given `TAGNAME` to a contact at the specified `INDEX`
+* The `TAG` given by the `TAGNAME` must be created first.
+* The `TAGNAME` is case-insensitive.
+* The index refers to the index number shown in the displayed contact list.
+* The index **must be a positive integer** 1, 2, 3, ...
+* The contact should have **at most one** `TAG` with a given unique `TAGNAME`.
+* The contact assigned to the given `TAGNAME` cannot be assigned to the same `TAGNAME` again.
+ e.g. assigning the tag `friends` to Alice at index 1 will not allow `Friends` to be assigned to the same contact.
+
+Example:
+
+* `assign 1 Friends` assigns a tag `Friends` to the contact at index `1`.
+
+ * Before using the command `assign 1 Friends`:
+ 
+
+ * `assign 1 Friends`:
+ 
+
+* `assign 3 Colleagues` assigns a tag `Colleagues` to the contact at index `3`.
+
+ * Before using the command `assign 3 Colleagues`:
+ 
+
+ * `assign 3 Colleagues`:
+ 
+
+Format in detailed view: `assign TAGNAME`
+
+Example:
+
+* `assign client` assigns a tag `client` to the currently viewed contact.
+
+ * `view 1`
+ 
+
+ * `assign client`
+ 
+
+
+### Unassigning a tag from a contact : `unassign`
+
+Unassigns a created tag from a contact. This command can be used in detailed view.
+
+Format: `unassign INDEX TAGNAME`
+
+* Removes a `TAG` with a given `TAGNAME` from a contact at the specified `INDEX`
+* The `TAG` given by the `TAGNAME` must be created first.
+* The `TAGNAME` is case-insensitive.
+* The index refers to the index number shown in the displayed contact list.
+* The index **must be a positive integer** 1, 2, 3, ...
+* The contact must have been assigned to this `TAG` previously.
+
+Example:
+
+* `unassign 1 Friends` removes the tag `Friends` from the contact at index `1`.
+ * Before using the command `unassign 1 Friends`
+ 
+
+ * `unassign 1 Friends`
+ 
+
+
+Format in detailed view: `unassign TAGNAME`
+
+Example:
+
+* `unassign client` removes the tag `client` from the currently viewed contact.
+ * `view 1`
+ 
+
+ * `unassign client`
+ 
+
+### Deleting a tag : `deltag`
+
+Deletes the specified tag(s)
+
+Format: `deltag TAGNAME [MORE_TAGNAME]`
+
+* Deletes the tag(s) identified by the given `TAGNAME`.
+* Unassigns the deleted tags from all contacts who were previously assigned to the `tag` with given `TAGNAME`.
+* If the multiple `TAGNAME` specified has more than 1 `tag` that cannot be identified , the identifiable tag(s) will be deleted.
+
+Examples:
+
+* `deltag friends` deletes the tag `friends`
+
+ * Before using the command `deltag friends`:
+ 
+
+ * `deltag Friends`
+ 
+
+* `deltag friends colleagues` deletes the tag `friends` and `colleagues`
+
+ * Before using the command `deltag friends colleagues`
+ 
+
+ * `deltag friends colleagues`
+ 
+
+* `deltag friends colleagues` when the tag `colleagues` does not exist will delete the tag `friends` and unassign the tag `friends` from every contact
+ * Before using the command `deltag friends colleagues`
+ 
+
+ * `deltag friends colleagues`
+ 
+
+* `deltag colleagues` when the tag `colleagues` does not exist will not change the data.
+
+
+### Adding favourites : `fav`
+
+Toggles the favourite status of your contacts. This command can be used in detailed view.
+Favourited contacts **can be un-favourited** by running the same command on the contact again.
+
+Format: `fav INDEX`
+
+
:bulb: **Tip:**
+You can run `fav INDEX` where `INDEX` is the index of a contact that currently belongs in your favourites list to remove them.
+
+
+Examples:
+
+* `fav 1` — Adds contact at index 1 to your list of favourites
+
+
+
+* `fav 1` - Run the command for the same contact, and the favourite status will be toggled off.
+
+
+
+Format in detailed view: `fav`
+
+Example:
+
+* `fav` Adds the currently viewed contact to your list of favourites.
+
+### Adding high importance flag : `impt`
+
+Adds the contact to your list of contacts with high importance and a red flag will appear beside the contact's name to indicate that.
+This command can be used in detailed view.
+
+Format: `impt INDEX`
+
+
:bulb: **Tip:**
+When a red flag appears beside the contact's name, you can run `impt INDEX` again where `INDEX` is the index of a contact that currently belongs in your list of contacts with high importance to remove them.
+
+
:bulb: **Tip:**
+You may wish to use the `note` command to add a note to indicate why the contact is important. E.g. Mobility Issues.
+
+
+Examples:
+
+* `impt 1` - Adds contact at index 1 to your list of contacts with high importance, indicated by the red flag
+
+
+
+You will remove the red flag beside the contact's name after entering `impt 1` again.
+
+
+
+`note 1 r/Mobility Issues` - Adds a note for your contact at index 1 indicating he/she has mobility issues
+
+
+
+Format in detailed view: `impt`
+
+Example:
+
+* `impt` Adds the currently viewed contact to your list of contacts with high importance.
+
+### Adding deadlines to meet in relation to a contact : `deadline`
+
+Creates a deadline that is placed under the profile of a contact. This command can be used in detailed view.
+
+Format: `deadline INDEX d/DESCRIPTION DATE [d/DESCRIPTION DATE]...`
+
+* Deadline must have description.
+* The given date is added to the contact as deadline.
+* Date should be dd/mm/yyyy
+
+Example:
+
+* `deadline 1 d/windows 01/01/2022` adds a deadline with description `windows` and date `01/01/2022` to the contact in index `1`.
+
+List before `deadline` command:
+
+
+
+List after `deadline` command:
+
+
+
+Format in detailed view: `deadline d/DESCRIPTION DATE [d/DESCRIPTION DATE]...`
+
+Example:
+
+* `deadline d/Lunch meeting 03/06/2022` adds a deadline with description `Lunch meeting` and date `03/06/2022` to the
+ currently viewed contact.
+
+Interface before `deadline` command in detailed view mode:
+
+
+
+Interface after `deadline` command in detailed view mode:
+
+
+
+### Deleting a deadline from a contact : `deldl`
+
+Deletes the deadline under the contact in detailed view. This command cannot be used in list view.
+
+Format: `deldl INDEX`
+
+* Deletes the note at the index of the list of deadlines displayed.
+
+Example:
+
+* `view 2` shows you the detailed view of the contact at index 2, then using `deldl 2` will delete the second deadline in the
+ notes list of the contact
+
+Interface before `deldl` command:
+
+
+
+Interface after `deldl` command:
+
+
+
+### Adding additional notes to a contact : `note`
+
+Adds the given note under the contact. This command can be used in detailed view.
+
+Format: `note INDEX r/NOTES`
+
+* Notes are displayed in a list.
+* The given note is appended to the existing list of notes at the end.
+
+
:bulb: **Tip:**
+Notes store good-to-know information about the user. To classify contacts so that you can search for them, use tags instead.
+
+
+Example:
+
+* `note 2 r/loves green` will add a note under the contact at index 2 that reads `loves green`.
+
+Format in detailed view: `note r/NOTES`
+
+Example:
+
+* `note r/Likes wood furniture` will add a note to currently viewed contact that reads `Likes wood furniture`.
+
+### Deleting notes from a contact : `delnote`
+
+Deletes the note under the contact in detailed view. This command cannot be used in list view.
+
+Format: `delnote INDEX`
+
+* Deletes the note at the index of the list of notes displayed.
+
+Example:
+
+* `view 1` shows you the detailed view of the contact at index 1, then using `delnote 2` will delete the second note in the
+ notes list of the contact
+
+### Adding images : `addimg`
+
+Add image(s) to a contact.
+
+
:bulb: **Tip:**
+Take note that images uploaded are duplicated and stored in the data folder of the app.
+If your computer's storage space is a concern for you, please delete the original image
+after you have uploaded it!
+
+
+Format: `addimg INDEX`
+
+
+
+* Upon running the command, a file chooser will appear for you to select images from.
+* Images can be in `.png` or `.jpg` formats.
+* Images uploaded cannot have duplicate names.
+
+### List contact's images : `images`
+
+Lists the contact's image(s).
+
+Format: `images INDEX`
+
+* You can click on the images to get a focused view of the image.
+
+### Deleting images : `delimg`
+
+Deletes the image(s) associated with a contact.
+
+Format: `delimg INDEX i/IMAGE_INDEX`
+
+
+
+* An image's index is relative to the person it belongs to.
+* You can identify it by running the images command for a given user
+ (as seen in the above image). The `IMAGE_INDEX` of the image will be
+ directly above the image itself.
+
+Example:
+
+`delimg 1 i/2` will delete the second image of the contact
### Clearing all entries : `clear`
-Clears all entries from the address book.
+Clears all entries from the address book. This command can **only be used in list view**.
+
+
:exclamation: **Caution:**
+There is no warning if you run this command, and there is **no way to recover the deleted data**. Ensure you intend to
+run this command.
+
+It is recommended to use this command **only for clearing the sample data provided in the beginning**.
+
Format: `clear`
-### Exiting the program : `exit`
+### Navigating your contact list
-Exits the program.
+As your contact list grows larger, you may start having trouble finding the contact you are looking for.
+However, with these commands to aid you, finding contacts will still be easy and intuitive.
-Format: `exit`
+### Listing Favourites : `favourites`
+
+Lists all your favourite contacts to the list of displayed contacts. This command **only works in list view**.
+
+Format: `favourites`
+
+### Listing contacts with high importance : `impts`
+
+Shows you all contact(s) with high importance, tagged with the red flag. This command **only works in list view**.
+
+Format: `impts`
+
+Examples:
+
+* You can enter `impt 1` followed by `impt 3` to add a red flag beside contacts at index 1 and index 3.
+
+ 
+
+* Use `impts` to list all of your contacts that has been tagged with the red flag.
+
+
+
+### Prioritising relevant contacts to you : `sort`
+
+Sort contacts by given criteria. This command **only works in list view**.
+
+Format: `sort CRITERIA`
+
+* `CRITERIA` should be written in lower-case.
+
+| `CRITERIA` | Order |
+|------------|------------------------------------------------------|
+| `address` | Alphabetically sorted |
+| `deadline` | Early dates to later dates |
+| `email` | Alphabetically sorted |
+| `fav` | Favourited contacts to not favourited contacts |
+| `impt` | HighImportant contacts to not HighImportant contacts |
+| `name` | Alphabetically sorted |
+| `phone` | Numerically sorted |
+
+### Locating contacts by name : `find`
+
+Find contacts whose names contain any of the given keywords. This command **only works in list view**.
+
+Format: `find KEYWORD [MORE_KEYWORDS]`
+
+* 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`
+* Contacts 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`
+ 
+
+### Locating contacts by tag : `findtag`
+
+Find contacts based on the selected tags given by keywords to search for. This command **only works in list view**.
+
+Format: `findtag KEYWORD [MORE_KEYWORDS]`
+
+* Each `findtag` command selects a `TAG` to be found by using the given `KEYWORD`
+ e.g.
+
+ * `findtag friends` adds `friends` as a tag to be searched for
+ * `findtag colleagues` adds `colleagues` to pre-existing search, now containing both colleagues and friends
+* The search is case-insensitive. e.g `tag` will match `Tag`
+* **Only the tag is searched**
+* **Only full words will be matched** e.g. `Ta` will not match `Tag`
+* List of contacts matching at least the searched tag\(s\) will be returned. e.g. `Tag1` will return `Contact` A with
+ tags `Tag1` and `Tag2`.
+
+
:bulb: **Tip:**
+Use `list` to clear currently selected tags!
+
+
+Examples:
+
+* `findtag Friends` returns contacts with tag `Friends`
+ 
+* `findtag Friends` followed by `findtag InProgress AlmostFinished` returns contacts tagged by at least `Friends`, `InProgress` and `AlmostFinished`
+ 
### Saving the data
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+d'Intérieur 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
-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.
+d'Intérieur 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.
: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.
+If your changes to the data file makes its format invalid, d'Intérieur will discard all data and start with an empty data file at the next run.
+### Cycling through input history
+
+You can cycle through your input history by hitting the up arrow key to go back to older inputs,
+and the down arrow key to your latest inputs. You can save time on typing repeat inputs, or re-writing erroneous
+inputs!
+
### Archiving data files `[coming in v2.0]`
_Details coming soon ..._
@@ -175,18 +750,39 @@ _Details coming soon ..._
## 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 the app in the other computer and overwrite the empty data file it creates with the file that contains
+the data of your previous d'Intérieur home folder.
--------------------------------------------------------------------------------------------------------------------
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX`
e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+Empty entries mean the commands cannot be used in the view.
+
+| Action | Format, Examples in List View | Format, Examples in Detailed View |
+|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL [a/ADDRESS] [t/TAG]…`
e.g., `add n/Mary Jane p/12345678 e/maryJ@example.com a/Bukit Timah t/completed` | Same as list view |
+| **Add Image** | `addimg INDEX`
e.g., `addimg 1` | `addimg` |
+| **Assign Tag** | `assign INDEX TAGNAME`
e.g., `assign 1 Friends` | `assign TAGNAME`
e.g., `assign client` |
+| **Clear** | `clear` | - |
+| **Create Tag** | `tag TAGNAME`
e.g., `tag Friends` | Same as list view |
+| **Deadline** | `deadline INDEX d/DESCRIPTION DATE [d/DESCRIPTION DATE]...`
e.g., `deadline 1 d/windows 01/01/2022` | `deadline d/DESCRIPTION DATE [d/DESCRIPTION DATE]...`
e.g., `deadline d/Lunch meeting 03/06/2022` |
+| **Delete** | `delete INDEX`
e.g., `delete 3` | - |
+| **Delete Deadline** | - | `deldl INDEX`
e.g. `deldl 2` |
+| **Delete Image** | `delimg INDEX i/IMAGE_INDEX`
e.g., `delimg 1 i/2` | `delimg i/IMAGE_INDEX`
e.g., `delimg i/1` |
+| **Delete Note** | - | `delnote INDEX`
e.g. `delnote 2` |
+| **Delete Tag** | `deltag TAGNAME`
e.g., `delete friends` | - |
+| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] …`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` | `edit [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]…`
e.g., `edit p/88438809 e/alex_yeoh@example.com` |
+| **Fav** | `fav INDEX`
e.g., `fav 1` | `fav` |
+| **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | - |
+| **Find Tag** | `findtag KEYWORD [MORE_KEYWORDS}`
e.g., `findtag Friends` | - |
+| **Help** | `help` | Same as list view |
+| **Impt** | `impt INDEX`
e.g., `impt 1` | `impt` |
+| **Impts** | `impts` | - |
+| **Sort** | `sort CRITERIA`
e.g., `sort address` | - |
+| **List** | `list` | Same as list view |
+| **List Favourites** | `favourites` | - |
+| **List Images** | `images INDEX`
e.g., `images 1` | `images` |
+| **Note** | `note INDEX r/NOTES`
e.g. `note 2 r/loves green` | `note r/NOTES`
e.g., `note r/Likes wood furniture` |
+| **Unassign Tag** | `unassign INDEX TAGNAME`
e.g., `unassign 1 Friends` | `unassign TAGNAME`
e.g., `unassign client` |
+| **View** | `view INDEX`
e.g., `view 1` | - |
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..8eb7c2e3b6c 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "d'Intérieur"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2122S2-CS2103T-T12-2/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..2287ac5ba6f 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: "d'Intérieur";
font-size: 32px;
}
}
diff --git a/docs/diagrams/DeadlineSequenceDiagram.puml b/docs/diagrams/DeadlineSequenceDiagram.puml
new file mode 100644
index 00000000000..a1e04456857
--- /dev/null
+++ b/docs/diagrams/DeadlineSequenceDiagram.puml
@@ -0,0 +1,92 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":DeadlineCommandParser" as DeadlineCommandParser LOGIC_COLOR
+participant "command:DeadlineCommand" as DeadlineCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+participant ":ParserUtil" as ParserUtil LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("deadline 1 /d return book 1/1/2023")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("deadline 1 /d return book 1/1/2023")
+activate AddressBookParser
+
+create DeadlineCommandParser
+AddressBookParser -> DeadlineCommandParser
+activate DeadlineCommandParser
+
+DeadlineCommandParser --> AddressBookParser
+deactivate DeadlineCommandParser
+
+AddressBookParser -> DeadlineCommandParser : parse("1 /d return book 1/1/2023")
+activate DeadlineCommandParser
+
+DeadlineCommandParser -> ParserUtil : parseIndex("1")
+activate ParserUtil
+
+ParserUtil --> DeadlineCommandParser : index
+deactivate ParserUtil
+
+DeadlineCommandParser -> ParserUtil : parseDeadlines([" return book 1/1/2023"])
+activate ParserUtil
+
+ParserUtil --> DeadlineCommandParser : deadlines
+deactivate ParserUtil
+
+create DeadlineCommand
+DeadlineCommandParser -> DeadlineCommand : DeadlineCommand(index, deadlines)
+activate DeadlineCommand
+
+DeadlineCommand --> DeadlineCommandParser : command
+deactivate DeadlineCommand
+
+DeadlineCommandParser --> AddressBookParser : command
+deactivate DeadlineCommandParser
+
+DeadlineCommandParser -[hidden]-> AddressBookParser
+destroy DeadlineCommandParser
+
+AddressBookParser --> LogicManager : command
+deactivate AddressBookParser
+
+LogicManager -> DeadlineCommand : execute(model)
+activate DeadlineCommand
+
+DeadlineCommand -> Model : getFilteredPersonList()
+activate Model
+
+Model --> DeadlineCommand : lastShownList
+deactivate Model
+
+DeadlineCommand -> Model : setPerson(personToAddDeadline, editedPerson)
+activate Model
+
+deactivate Model
+
+DeadlineCommand -> Model : updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS)
+activate Model
+
+deactivate Model
+
+create CommandResult
+DeadlineCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> DeadlineCommand
+deactivate CommandResult
+
+DeadlineCommand --> LogicManager : CommandResult(feedbackToUser)
+deactivate DeadlineCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/DeadlineState1.puml b/docs/diagrams/DeadlineState1.puml
new file mode 100644
index 00000000000..e0ed99a7aec
--- /dev/null
+++ b/docs/diagrams/DeadlineState1.puml
@@ -0,0 +1,15 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package List
{
+Class "Person1" as Person1
+Class "Person2" as Person2
+Class "Person3" as Person3
+}
+
+Person1 - Person2
+Person2 - Person3
+@enduml
diff --git a/docs/diagrams/DeadlineState2.puml b/docs/diagrams/DeadlineState2.puml
new file mode 100644
index 00000000000..8102cef1ee8
--- /dev/null
+++ b/docs/diagrams/DeadlineState2.puml
@@ -0,0 +1,18 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package List {
+Class "personToAddDeadline" as Person1
+Class "Person2" as Person2
+Class "Person3" as Person3
+}
+
+Person1 - Person2
+Person2 - Person3
+
+class Pointer as "DeadlineCommand"
+Pointer -up-> Person1
+@enduml
diff --git a/docs/diagrams/DeadlineState3.puml b/docs/diagrams/DeadlineState3.puml
new file mode 100644
index 00000000000..4748423e8c7
--- /dev/null
+++ b/docs/diagrams/DeadlineState3.puml
@@ -0,0 +1,21 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package List {
+Class "personToAddDeadline" as Person1
+Class "Person2" as Person2
+Class "Person3" as Person3
+}
+
+Class "editedPerson" as Person4
+
+Person1 - Person2
+Person2 - Person3
+
+class Pointer as "DeadlineCommand"
+Pointer -up-> Person1
+Pointer -up-> Person4
+@enduml
diff --git a/docs/diagrams/DeadlineState4.puml b/docs/diagrams/DeadlineState4.puml
new file mode 100644
index 00000000000..e0b43c4ad76
--- /dev/null
+++ b/docs/diagrams/DeadlineState4.puml
@@ -0,0 +1,18 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package List {
+Class "editedPerson" as Person1
+Class "Person2" as Person2
+Class "Person3" as Person3
+}
+
+Person1 - Person2
+Person2 - Person3
+
+class Pointer as "DeadlineCommand"
+Pointer -up-> Person1
+@enduml
diff --git a/docs/diagrams/FindTagSequenceDiagram.puml b/docs/diagrams/FindTagSequenceDiagram.puml
new file mode 100644
index 00000000000..837dd04cf07
--- /dev/null
+++ b/docs/diagrams/FindTagSequenceDiagram.puml
@@ -0,0 +1,83 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FindTagCommandParser" as FindTagCommandParser LOGIC_COLOR
+participant "f:FindTagCommand" as FindTagCommand LOGIC_COLOR
+participant "p:TagContainsKeywordPredicate" as TagContainsKeywordPredicate LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("findtag friends")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("findtag friends")
+activate AddressBookParser
+
+create FindTagCommandParser
+AddressBookParser -> FindTagCommandParser
+activate FindTagCommandParser
+
+FindTagCommandParser --> AddressBookParser
+deactivate FindTagCommandParser
+
+AddressBookParser -> FindTagCommandParser : parse("friends")
+activate FindTagCommandParser
+
+create FindTagCommand
+FindTagCommandParser -> FindTagCommand
+activate FindTagCommand
+
+FindTagCommand --> FindTagCommandParser : f
+deactivate FindTagCommand
+
+FindTagCommandParser --> AddressBookParser : f
+deactivate FindTagCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+FindTagCommandParser -[hidden]-> AddressBookParser
+destroy FindTagCommandParser
+
+AddressBookParser --> LogicManager : f
+deactivate AddressBookParser
+
+LogicManager -> FindTagCommand : execute()
+activate FindTagCommand
+
+FindTagCommand -> Model : getActivatedTagList()
+activate Model
+
+Model --> FindTagCommand : activatedTagList
+
+create TagContainsKeywordPredicate
+FindTagCommand -> TagContainsKeywordPredicate : {"friends"}
+
+activate TagContainsKeywordPredicate
+TagContainsKeywordPredicate --> FindTagCommand : p
+deactivate TagContainsKeywordPredicate
+
+
+FindTagCommand -> Model : updateFilteredPersonList(p)
+
+Model --> FindTagCommand
+deactivate Model
+
+create CommandResult
+FindTagCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindTagCommand
+deactivate CommandResult
+
+FindTagCommand --> LogicManager : result
+deactivate FindTagCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/InformationPanels.puml b/docs/diagrams/InformationPanels.puml
new file mode 100644
index 00000000000..351d9112d97
--- /dev/null
+++ b/docs/diagrams/InformationPanels.puml
@@ -0,0 +1,42 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor UI_COLOR_T4
+skinparam classBackgroundColor UI_COLOR
+
+package InformationPanels <> {
+Class PersonListPanel
+Class PersonCard
+Class DetailedContactPanel
+Class DetailedPersonCard
+Class ImageViewPanel
+Class ImageCard
+}
+
+package Model <> {
+Class HiddenModel #FFFFFF
+}
+
+Class MainWindow
+Class "{abstract}\nUiPart" as UiPart
+
+MainWindow*-down-> PersonListPanel
+MainWindow*-down-> DetailedContactPanel
+MainWindow*-down-> ImageViewPanel
+
+PersonListPanel -down-> "*" PersonCard
+DetailedContactPanel -down-> "0...1" DetailedPersonCard
+ImageViewPanel -down-> "*" ImageCard
+
+PersonListPanel --|> UiPart
+PersonCard -right-|> UiPart
+DetailedContactPanel --|> UiPart
+DetailedPersonCard -right-|> UiPart
+ImageViewPanel --|> UiPart
+ImageCard -right-|> UiPart
+
+PersonCard ..> Model
+DetailedPersonCard ..> Model
+ImageCard ..> Model
+
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..fcf8b1dae22 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,6 +5,7 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
+Class Positioning #FFFFFF
Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
Class "<>\nModel" as Model
@@ -12,6 +13,8 @@ Class AddressBook
Class ModelManager
Class UserPrefs
+Class UniqueTagList
+Class ActivatedTagList
Class UniquePersonList
Class Person
Class Address
@@ -25,7 +28,9 @@ Class Tag
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
+
AddressBook .up.|> ReadOnlyAddressBook
+ReadOnlyAddressBook -[hidden]left-> Positioning
ModelManager .up.|> Model
Model .right.> ReadOnlyUserPrefs
@@ -34,13 +39,19 @@ ModelManager -left-> "1" AddressBook
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
+AddressBook*-left->UniqueTagList
+AddressBook*-->ActivatedTagList
AddressBook *--> "1" UniquePersonList
+ActivatedTagList-[hidden]right-> UniquePersonList
+
UniquePersonList --> "~* all" Person
+UniqueTagList*--> Tag
+ActivatedTagList*--> Tag
+Person *--> "*" Tag
Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
-Person *--> "*" Tag
Name -[hidden]right-> Phone
Phone -[hidden]right-> Address
diff --git a/docs/diagrams/NoteSequenceExecution.puml b/docs/diagrams/NoteSequenceExecution.puml
new file mode 100644
index 00000000000..29e5919b13e
--- /dev/null
+++ b/docs/diagrams/NoteSequenceExecution.puml
@@ -0,0 +1,52 @@
+@startuml
+!include style.puml
+
+Box Logic LOGIC_COLOR_T1
+participant ":NoteCommand" as NoteCommand LOGIC_COLOR
+end box
+
+Box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+participant "personToEdit:Person" as OldPerson MODEL_COLOR
+participant "oldNotes:Notes" as OldNotes MODEL_COLOR
+participant "newNotes:Notes" as NewNotes MODEL_COLOR
+participant "editedPerson:Person" as NewPerson MODEL_COLOR
+end box
+
+[-> NoteCommand : execute(model)
+activate NoteCommand
+
+NoteCommand -> Model : getFilteredPersonList()
+
+NoteCommand -> NoteCommand : updateNotes(personToEdit, note)
+activate NoteCommand
+
+NoteCommand -> OldPerson : getNotes()
+
+NoteCommand -> OldNotes : updateNotes(note)
+activate OldNotes
+create NewNotes
+OldNotes -> NewNotes
+activate NewNotes
+NewNotes -> OldNotes
+deactivate NewNotes
+OldNotes -> NoteCommand : newNotes
+deactivate OldNotes
+'Hidden arrow to move destroy marker lower'
+OldNotes -[hidden]-> OldNotes
+destroy OldNotes
+
+create NewPerson
+NoteCommand -> NewPerson
+activate NewPerson
+NewPerson -> NoteCommand
+deactivate NewPerson
+NoteCommand -> NoteCommand : editedPerson
+deactivate NoteCommand
+
+NoteCommand -> Model : setPerson(personToEdit, editedPerson)
+
+Model -> OldPerson
+destroy OldPerson
+
+@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..b81efcc5fbf 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,10 +11,11 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
Class StatusBarFooter
Class CommandBox
+package InformationPanels <> {
+class HiddenPanels #FFFFFF
+}
}
package Model <> {
@@ -30,31 +31,29 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
+UiManager -[hidden]down-> MainWindow
+
+MainWindow *-right-> InformationPanels
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
-MainWindow --> "0..1" HelpWindow
-
-PersonListPanel -down-> "*" PersonCard
-
-MainWindow -left-|> UiPart
+MainWindow -down-> "0..1" HelpWindow
+MainWindow --|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
+InformationPanels --|> UiPart
-PersonCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
-PersonListPanel -[hidden]left- HelpWindow
-HelpWindow -[hidden]left- CommandBox
+HiddenModel -[hidden]up- HiddenLogic
+InformationPanels .right.> Model
+
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
-MainWindow -[hidden]-|> UiPart
+
@enduml
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
index 96e30744d24..34885420931 100644
--- a/docs/diagrams/UndoRedoState0.puml
+++ b/docs/diagrams/UndoRedoState0.puml
@@ -15,6 +15,6 @@ State2 -[hidden]right-> State3
hide State2
hide State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State1
@end
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
index 01fcb9b2b96..0e2c8c72d33 100644
--- a/docs/diagrams/UndoRedoState1.puml
+++ b/docs/diagrams/UndoRedoState1.puml
@@ -16,7 +16,7 @@ State2 -[hidden]right-> State3
hide State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State2
@end
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index bccc230a5d1..0ce7073e187 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -14,7 +14,7 @@ package States <> {
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State3
@end
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
index ea29c9483e4..50bf43b3f34 100644
--- a/docs/diagrams/UndoRedoState3.puml
+++ b/docs/diagrams/UndoRedoState3.puml
@@ -14,7 +14,7 @@ package States <> {
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State2
@end
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
index 1b784cece80..83cbe4c740c 100644
--- a/docs/diagrams/UndoRedoState4.puml
+++ b/docs/diagrams/UndoRedoState4.puml
@@ -14,7 +14,7 @@ package States <> {
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State2
@end
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
index 88927be32bc..fc89dd99d2d 100644
--- a/docs/diagrams/UndoRedoState5.puml
+++ b/docs/diagrams/UndoRedoState5.puml
@@ -14,7 +14,7 @@ package States <> {
State1 -[hidden]right-> State2
State2 -[hidden]right-> State3
-class Pointer as "Current State" #FFFFF
+class Pointer as "Current State" #FFFFFF
Pointer -up-> State3
note right on link: State ab2 deleted.
diff --git a/docs/diagrams/detailedview/DetailedViewExecutionState1.puml b/docs/diagrams/detailedview/DetailedViewExecutionState1.puml
new file mode 100644
index 00000000000..0862bd8cd10
--- /dev/null
+++ b/docs/diagrams/detailedview/DetailedViewExecutionState1.puml
@@ -0,0 +1,18 @@
+@startuml
+!include ../style.puml
+
+Box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+Box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+end box
+
+[-> MainWindow : executeCommand("note r/red")
+activate MainWindow
+
+MainWindow -> LogicManager : executeInDetailedViewMode("note r/red")
+activate LogicManager
+
+@enduml
diff --git a/docs/diagrams/detailedview/DetailedViewExecutionState2.puml b/docs/diagrams/detailedview/DetailedViewExecutionState2.puml
new file mode 100644
index 00000000000..ba2ba123530
--- /dev/null
+++ b/docs/diagrams/detailedview/DetailedViewExecutionState2.puml
@@ -0,0 +1,40 @@
+@startuml
+!include ../style.puml
+
+Box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as ABParser LOGIC_COLOR
+participant ":NoteCommandParser" as NoteCommandParser LOGIC_COLOR
+participant "noteCommand:NoteCommand" as NoteCommand LOGIC_COLOR
+end box
+
+activate LogicManager
+
+LogicManager -> ABParser : parseInDetailedViewContext("note r/red")
+activate ABParser
+
+create NoteCommandParser
+ABParser -> NoteCommandParser
+activate NoteCommandParser
+NoteCommandParser -> ABParser
+deactivate NoteCommandParser
+
+ABParser -> NoteCommandParser : parseInDetailedViewContext("r/red")
+activate NoteCommandParser
+
+create NoteCommand
+NoteCommandParser -> NoteCommand
+activate NoteCommand
+NoteCommand -> NoteCommandParser
+deactivate NoteCommand
+
+NoteCommandParser -> ABParser : noteCommand
+deactivate NoteCommandParser
+'Hidden arrow to move destroy marker lower'
+NoteCommandParser -[hidden]-> NoteCommandParser
+destroy NoteCommandParser
+
+ABParser -> LogicManager : noteCommand
+deactivate ABParser
+
+@enduml
diff --git a/docs/diagrams/detailedview/DetailedViewExecutionState3.puml b/docs/diagrams/detailedview/DetailedViewExecutionState3.puml
new file mode 100644
index 00000000000..62f083387df
--- /dev/null
+++ b/docs/diagrams/detailedview/DetailedViewExecutionState3.puml
@@ -0,0 +1,54 @@
+@startuml
+!include ../style.puml
+
+Box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "noteCommand:NoteCommand" as NoteCommand LOGIC_COLOR
+participant "commandResult:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+Box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+participant "personToEdit:Person" as OldPerson MODEL_COLOR
+participant "editedPerson:Person" as NewPerson MODEL_COLOR
+end box
+
+activate LogicManager
+
+LogicManager -> NoteCommand : executeInDetailedView
+activate NoteCommand
+
+NoteCommand -> Model : getDetailedContactViewPerson
+activate Model
+Model -> NoteCommand : personToEdit
+deactivate Model
+
+create NewPerson
+NoteCommand -> NewPerson
+activate NewPerson
+NewPerson -> NoteCommand
+deactivate NewPerson
+
+NoteCommand -> Model : setPerson(personToEdit, editedPerson)
+Model -> NoteCommand
+
+NoteCommand -> Model : setDetailedContactView(editedPerson)
+Model -> OldPerson
+OldPerson -[hidden]-> OldPerson
+destroy OldPerson
+Model -> NoteCommand
+
+create CommandResult
+NoteCommand -> CommandResult : SpecialCommandResult.DETAILED_VIEW
+activate CommandResult
+CommandResult -> NoteCommand
+deactivate CommandResult
+
+NoteCommand -> LogicManager : commandResult
+deactivate NoteCommand
+
+'Hidden arrow to move destroy marker lower'
+NoteCommand -[hidden]-> NoteCommand
+destroy NoteCommand
+
+@enduml
diff --git a/docs/diagrams/detailedview/DetailedViewExecutionState4.puml b/docs/diagrams/detailedview/DetailedViewExecutionState4.puml
new file mode 100644
index 00000000000..7c9e8aa678b
--- /dev/null
+++ b/docs/diagrams/detailedview/DetailedViewExecutionState4.puml
@@ -0,0 +1,29 @@
+@startuml
+!include ../style.puml
+
+Box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+Box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "commandResult:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+activate MainWindow
+
+LogicManager -> MainWindow : commandResult
+
+MainWindow -> CommandResult : getSpecialCommandResult
+
+CommandResult -> MainWindow : DETAILED_VIEW
+
+MainWindow -> MainWindow : handleDetailedView
+activate MainWindow
+deactivate MainWindow
+'Hidden arrow to move return arrow lower'
+MainWindow -[hidden]-> MainWindow
+[<- MainWindow
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/detailedview/DetailedViewGeneralExecution.puml b/docs/diagrams/detailedview/DetailedViewGeneralExecution.puml
new file mode 100644
index 00000000000..d28c7cca27d
--- /dev/null
+++ b/docs/diagrams/detailedview/DetailedViewGeneralExecution.puml
@@ -0,0 +1,84 @@
+@startuml
+!include ../style.puml
+
+Box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+Box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as ABParser LOGIC_COLOR
+participant ":DetailedViewExecutableParser" as DVEParser LOGIC_COLOR
+participant "command:DetailedViewExecutable" as DVECommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+Box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+end box
+
+[-> MainWindow : executeCommand
+activate MainWindow
+
+MainWindow -> LogicManager : executeInDetailedViewMode
+activate LogicManager
+
+LogicManager -> ABParser : parseInDetailedViewContext
+activate ABParser
+
+create DVEParser
+ABParser -> DVEParser
+activate DVEParser
+DVEParser -> ABParser
+deactivate DVEParser
+
+ABParser -> DVEParser : parseInDetailedViewContext
+activate DVEParser
+
+create DVECommand
+DVEParser -> DVECommand
+activate DVECommand
+DVECommand -> DVEParser
+deactivate DVECommand
+
+DVEParser -> ABParser : command
+deactivate DVEParser
+'Hidden arrow to move destroy marker lower'
+DVEParser -[hidden]-> DVEParser
+destroy DVEParser
+
+ABParser -> LogicManager : command
+deactivate ABParser
+
+LogicManager -> DVECommand : executeInDetailedView
+activate DVECommand
+
+DVECommand -> Model
+'Hidden arrow to extend activation box'
+DVECommand -[hidden]-> DVECommand
+create CommandResult
+DVECommand -> CommandResult
+activate CommandResult
+CommandResult -> DVECommand
+deactivate CommandResult
+DVECommand -> LogicManager : result
+deactivate DVECommand
+'Hidden arrow to move destroy marker lower'
+DVECommand -[hidden]-> DVECommand
+destroy DVECommand
+
+LogicManager -> MainWindow : result
+deactivate LogicManager
+
+MainWindow -> CommandResult : getSpecialCommandResult
+
+alt NONE SpecialCommandResult
+MainWindow -> MainWindow : handleListView
+else DETAILED_VIEW SpecialCommandResult
+MainWindow -> MainWindow : handleDetailedView
+end
+
+[<- MainWindow
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/favourite/FavouriteSequenceDiagram.puml b/docs/diagrams/favourite/FavouriteSequenceDiagram.puml
new file mode 100644
index 00000000000..c862eb59b1b
--- /dev/null
+++ b/docs/diagrams/favourite/FavouriteSequenceDiagram.puml
@@ -0,0 +1,79 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FavouriteCommandParser" as FavouriteCommandParser LOGIC_COLOR
+participant "f:FavouriteCommand" as FavouriteCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant "pl:List" as PersonList MODEL_COLOR
+end box
+[-> LogicManager : execute(fav 3)
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand(fav 3)
+activate AddressBookParser
+
+create FavouriteCommandParser
+AddressBookParser -> FavouriteCommandParser
+activate FavouriteCommandParser
+
+FavouriteCommandParser --> AddressBookParser
+deactivate FavouriteCommandParser
+
+AddressBookParser -> FavouriteCommandParser : parse(3)
+activate FavouriteCommandParser
+
+create FavouriteCommand
+FavouriteCommandParser -> FavouriteCommand
+activate FavouriteCommand
+
+FavouriteCommand --> FavouriteCommandParser
+deactivate FavouriteCommand
+
+FavouriteCommandParser --> AddressBookParser
+deactivate FavouriteCommandParser
+
+AddressBookParser --> LogicManager : f
+deactivate AddressBookParser
+
+destroy FavouriteCommandParser
+
+LogicManager -> FavouriteCommand : execute()
+activate FavouriteCommand
+
+FavouriteCommand -> Model : getFilteredPersonList()
+activate Model
+
+Model --> FavouriteCommand : pl
+deactivate Model
+
+FavouriteCommand -> PersonList : get(2)
+activate PersonList
+
+PersonList --> FavouriteCommand : targetPerson
+deactivate PersonList
+
+FavouriteCommand -> FavouriteCommand : createFavouritePerson(targetPerson, IS_FAVOURITE)
+activate FavouriteCommand
+
+FavouriteCommand --> FavouriteCommand : favouritedPerson
+deactivate FavouriteCommand
+
+FavouriteCommand -> Model : setPerson(targetPerson, favouritedPerson)
+activate Model
+
+Model --> FavouriteCommand
+deactivate Model
+
+FavouriteCommand --> LogicManager
+deactivate FavouriteCommand
+
+[<--LogicManager
+deactivate LogicManager
+destroy FavouriteCommand
+@enduml
diff --git a/docs/diagrams/favourite/FavouriteState0.puml b/docs/diagrams/favourite/FavouriteState0.puml
new file mode 100644
index 00000000000..f7ed7837b07
--- /dev/null
+++ b/docs/diagrams/favourite/FavouriteState0.puml
@@ -0,0 +1,25 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title Newly created person
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class P1 as "__p1:Person__"
+ class P2 as "__p2:Person__"
+ class Person as "__p3:Person__"
+ class NotFavourite as "__NOT_FAVOURITE:Favourite__"
+}
+
+PersonList -> Person
+PersonList -up-> P1
+PersonList -> P2
+Person -up-> NotFavourite
+P1 -> NotFavourite
+P2 -right-> NotFavourite
+
+P1 -[hidden]-> P2
+P2 -[hidden]-> Person
+
+@enduml
diff --git a/docs/diagrams/favourite/FavouriteState1.puml b/docs/diagrams/favourite/FavouriteState1.puml
new file mode 100644
index 00000000000..4a74b24bcb2
--- /dev/null
+++ b/docs/diagrams/favourite/FavouriteState1.puml
@@ -0,0 +1,20 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title During execution (other contacts omitted)
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class Person as "__p3:Person__"
+ class NotFavourite as "__NOT_FAVOURITE:Favourite__"
+ class PersonFavourited as "__p3Favourited:Person__"
+ class IsFavourite as "__IS_FAVOURITE:Favourite__"
+}
+
+PersonList -> Person
+Person -> NotFavourite
+Person -[hidden]-> PersonFavourited
+PersonFavourited -> IsFavourite
+
+@enduml
diff --git a/docs/diagrams/favourite/FavouriteState2.puml b/docs/diagrams/favourite/FavouriteState2.puml
new file mode 100644
index 00000000000..3876fa4e9d6
--- /dev/null
+++ b/docs/diagrams/favourite/FavouriteState2.puml
@@ -0,0 +1,27 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title After execution
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class NotFavourite as "__NOT_FAVOURITE:Favourite__"
+ class PersonFavourited as "__p3Favourited:Person__"
+ class P1 as "__p1:Person__"
+ class P2 as "__p2:Person__"
+ class IsFavourite as "__IS_FAVOURITE:Favourite__"
+}
+
+PersonList -> PersonFavourited
+PersonList -up-> P1
+PersonList -> P2
+PersonFavourited -right-> IsFavourite
+P1 -> NotFavourite
+P2 -right-> NotFavourite
+
+P1 -[hidden]-> P2
+P2 -[hidden]-> PersonFavourited
+NotFavourite -[hidden]-> IsFavourite
+
+@enduml
diff --git a/docs/diagrams/favourite/FavouriteState3.puml b/docs/diagrams/favourite/FavouriteState3.puml
new file mode 100644
index 00000000000..5a2d149ccbf
--- /dev/null
+++ b/docs/diagrams/favourite/FavouriteState3.puml
@@ -0,0 +1,18 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title List Favourites
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class PersonFavourited as "__p3Favourited:Person__"
+ class IsFavourite as "__IS_FAVOURITE:Favourite__"
+ class Predicate as "__:PersonIsFavouriteContactPredicate__"
+}
+
+PersonList -> PersonFavourited
+PersonFavourited -right-> IsFavourite
+PersonList ..> Predicate
+
+@enduml
diff --git a/docs/diagrams/high-importance-flag/HighImportanceSequenceDiagram.puml b/docs/diagrams/high-importance-flag/HighImportanceSequenceDiagram.puml
new file mode 100644
index 00000000000..db2df934cba
--- /dev/null
+++ b/docs/diagrams/high-importance-flag/HighImportanceSequenceDiagram.puml
@@ -0,0 +1,64 @@
+@startuml
+!include ../style.puml
+
+box Logic
+participant ":LogicManager" as LogicManager LOGIC_COLOR_T5
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR_T5
+participant ":HighImportanceCommandParser" as HighImportanceCommandParser LOGIC_COLOR_T5
+participant "h :HighImportanceCommand" as HighImportanceCommand LOGIC_COLOR_T5
+participant ":CommandResult" as CommandResult LOGIC_COLOR_T5
+
+-> LogicManager : execute("impt 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser: parseCommand("impt 1")
+activate AddressBookParser
+
+create HighImportanceCommandParser
+AddressBookParser -> HighImportanceCommandParser ++
+
+HighImportanceCommandParser --> AddressBookParser --
+
+AddressBookParser -> HighImportanceCommandParser ++ : \nparse("1")
+
+create HighImportanceCommand
+HighImportanceCommandParser -> HighImportanceCommand ++
+
+HighImportanceCommand --> HighImportanceCommandParser -- : h
+HighImportanceCommandParser --> AddressBookParser -- : h
+destroy HighImportanceCommandParser
+
+AddressBookParser --> LogicManager -- : h
+LogicManager -> HighImportanceCommand ++ : execute()
+
+box Model MODEL_COLOR_T1
+participant ":Model" as model MODEL_COLOR_T4
+participant "personList:List" as personList MODEL_COLOR_T4
+end box
+
+HighImportanceCommand -> model ++ : getSortedPersonList()
+model --> HighImportanceCommand : personList
+deactivate model
+
+HighImportanceCommand -> personList ++ : get(2)
+personList --> HighImportanceCommand : targetPerson
+deactivate personList
+
+HighImportanceCommand -> HighImportanceCommand ++ : createHighImportancePerson(targetPerson, HIGH_IMPORTANCE)
+HighImportanceCommand --> HighImportanceCommand : editedPerson
+deactivate HighImportanceCommand
+
+HighImportanceCommand -> model ++ : setPerson(targetPerson, editedPerson)
+model --> HighImportanceCommand
+deactivate model
+
+create CommandResult
+HighImportanceCommand -> CommandResult ++
+CommandResult --> HighImportanceCommand --
+
+HighImportanceCommand --> LogicManager
+deactivate HighImportanceCommand
+destroy HighImportanceCommand
+<-- LogicManager
+
+@enduml
diff --git a/docs/diagrams/high-importance-flag/ImportanceState0.puml b/docs/diagrams/high-importance-flag/ImportanceState0.puml
new file mode 100644
index 00000000000..19d813f8261
--- /dev/null
+++ b/docs/diagrams/high-importance-flag/ImportanceState0.puml
@@ -0,0 +1,21 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title Newly created persons (2 persons as example)
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class P1 as "__p1:Person__"
+ class P2 as "__p2:Person__"
+ class NotImportant as "__NOT_HIGH_IMPORTANCE:HighImportance__"
+}
+
+PersonList -right-> P1
+P1 -right-> NotImportant
+PersonList --> P2
+P2 -up-> NotImportant
+
+P1 -[hidden]down-> P2
+
+@enduml
diff --git a/docs/diagrams/high-importance-flag/ImportanceState1.puml b/docs/diagrams/high-importance-flag/ImportanceState1.puml
new file mode 100644
index 00000000000..4b7325bf1c0
--- /dev/null
+++ b/docs/diagrams/high-importance-flag/ImportanceState1.puml
@@ -0,0 +1,21 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title During Execution (p2 omitted)
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class P1 as "__p1:Person__"
+ class P1HighImportance as "__p1HighImportance:Person__"
+ class NotImportant as "__NOT_HIGH_IMPORTANCE:HighImportance__"
+ class Important as "__HIGH_IMPORTANCE:HighImportance__"
+}
+
+PersonList -right-> P1
+P1 -right-> NotImportant
+P1HighImportance -right-> Important
+
+P1HighImportance -[hidden]up-> P1
+
+@enduml
diff --git a/docs/diagrams/high-importance-flag/ImportanceState2.puml b/docs/diagrams/high-importance-flag/ImportanceState2.puml
new file mode 100644
index 00000000000..1e87ca31996
--- /dev/null
+++ b/docs/diagrams/high-importance-flag/ImportanceState2.puml
@@ -0,0 +1,22 @@
+@startuml
+!include ../style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+
+title After execution
+package ContactState {
+ class PersonList as "__personList:FilteredPersonList__"
+ class P1 as "__p1:Person__"
+ class P2 as "__p2:Person__"
+ class Important as "__HIGH_IMPORTANCE:HighImportance__"
+ class NotImportant as "__NOT_HIGH_IMPORTANCE:HighImportance__"
+}
+
+PersonList -right-> P1
+P1 -right-> Important
+PersonList --> P2
+P2 -right-> NotImportant
+
+P1 -[hidden]down-> P2
+
+@enduml
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..c0639daac10 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -13,11 +13,14 @@
!define UI_COLOR_T3 #166800
!define UI_COLOR_T4 #0E4100
+!define LOGIC_COLOR_HEADING #FFF
!define LOGIC_COLOR #3333C4
!define LOGIC_COLOR_T1 #C8C8FA
!define LOGIC_COLOR_T2 #6A6ADC
!define LOGIC_COLOR_T3 #1616B0
!define LOGIC_COLOR_T4 #101086
+!define LOGIC_COLOR_T5 #7777DB
+!define LOGIC_COLOR_T6 #5252CE
!define MODEL_COLOR #9D0012
!define MODEL_COLOR_T1 #F97181
diff --git a/docs/images/AddCommandParserEnhancement.png b/docs/images/AddCommandParserEnhancement.png
new file mode 100644
index 00000000000..966ca444c29
Binary files /dev/null and b/docs/images/AddCommandParserEnhancement.png differ
diff --git a/docs/images/AfterDeadlineCommand.png b/docs/images/AfterDeadlineCommand.png
new file mode 100644
index 00000000000..633189f482d
Binary files /dev/null and b/docs/images/AfterDeadlineCommand.png differ
diff --git a/docs/images/AfterDeleteDeadlineCommand.png b/docs/images/AfterDeleteDeadlineCommand.png
new file mode 100644
index 00000000000..014bbafe767
Binary files /dev/null and b/docs/images/AfterDeleteDeadlineCommand.png differ
diff --git a/docs/images/AfterDetailedViewDeadlineCommand.png b/docs/images/AfterDetailedViewDeadlineCommand.png
new file mode 100644
index 00000000000..c4ffaa8ffd9
Binary files /dev/null and b/docs/images/AfterDetailedViewDeadlineCommand.png differ
diff --git a/docs/images/BeforeDeadlineCommand.png b/docs/images/BeforeDeadlineCommand.png
new file mode 100644
index 00000000000..46540bbbe0b
Binary files /dev/null and b/docs/images/BeforeDeadlineCommand.png differ
diff --git a/docs/images/BeforeDeleteDeadlineCommand.png b/docs/images/BeforeDeleteDeadlineCommand.png
new file mode 100644
index 00000000000..b4075b35a28
Binary files /dev/null and b/docs/images/BeforeDeleteDeadlineCommand.png differ
diff --git a/docs/images/BeforeDetailedViewDeadlineCommand.png b/docs/images/BeforeDetailedViewDeadlineCommand.png
new file mode 100644
index 00000000000..d382a09605f
Binary files /dev/null and b/docs/images/BeforeDetailedViewDeadlineCommand.png differ
diff --git a/docs/images/DeadlineSequenceDiagram.png b/docs/images/DeadlineSequenceDiagram.png
new file mode 100644
index 00000000000..d149d96f291
Binary files /dev/null and b/docs/images/DeadlineSequenceDiagram.png differ
diff --git a/docs/images/DeadlineState1.png b/docs/images/DeadlineState1.png
new file mode 100644
index 00000000000..610c78ce444
Binary files /dev/null and b/docs/images/DeadlineState1.png differ
diff --git a/docs/images/DeadlineState2.png b/docs/images/DeadlineState2.png
new file mode 100644
index 00000000000..68dd3d3e8c7
Binary files /dev/null and b/docs/images/DeadlineState2.png differ
diff --git a/docs/images/DeadlineState3.png b/docs/images/DeadlineState3.png
new file mode 100644
index 00000000000..c73b0fe8506
Binary files /dev/null and b/docs/images/DeadlineState3.png differ
diff --git a/docs/images/DeadlineState4.png b/docs/images/DeadlineState4.png
new file mode 100644
index 00000000000..4a7177c1c11
Binary files /dev/null and b/docs/images/DeadlineState4.png differ
diff --git a/docs/images/FindTagSequenceDiagram.png b/docs/images/FindTagSequenceDiagram.png
new file mode 100644
index 00000000000..51db52e4750
Binary files /dev/null and b/docs/images/FindTagSequenceDiagram.png differ
diff --git a/docs/images/InformationPanels.png b/docs/images/InformationPanels.png
new file mode 100644
index 00000000000..029152a4b88
Binary files /dev/null and b/docs/images/InformationPanels.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..c0026239b65 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/NoteSequenceExecution.png b/docs/images/NoteSequenceExecution.png
new file mode 100644
index 00000000000..018843f9c63
Binary files /dev/null and b/docs/images/NoteSequenceExecution.png differ
diff --git a/docs/images/Sample1.png b/docs/images/Sample1.png
new file mode 100644
index 00000000000..6ec76658ef6
Binary files /dev/null and b/docs/images/Sample1.png differ
diff --git a/docs/images/Sample2.png b/docs/images/Sample2.png
new file mode 100644
index 00000000000..6b261b89037
Binary files /dev/null and b/docs/images/Sample2.png differ
diff --git a/docs/images/TutorialAdd.png b/docs/images/TutorialAdd.png
new file mode 100644
index 00000000000..d3c98084b48
Binary files /dev/null and b/docs/images/TutorialAdd.png differ
diff --git a/docs/images/TutorialFav.png b/docs/images/TutorialFav.png
new file mode 100644
index 00000000000..2c4791c37ac
Binary files /dev/null and b/docs/images/TutorialFav.png differ
diff --git a/docs/images/TutorialImpt.png b/docs/images/TutorialImpt.png
new file mode 100644
index 00000000000..e5e85da6236
Binary files /dev/null and b/docs/images/TutorialImpt.png differ
diff --git a/docs/images/TutorialView.png b/docs/images/TutorialView.png
new file mode 100644
index 00000000000..7f2395c5815
Binary files /dev/null and b/docs/images/TutorialView.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..2c85a1c5d74 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 785e04dbab4..4f14d89cefc 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/after_unfavourite_command.png b/docs/images/after_unfavourite_command.png
new file mode 100644
index 00000000000..9b792cd8efb
Binary files /dev/null and b/docs/images/after_unfavourite_command.png differ
diff --git a/docs/images/assign-tag/assign-1-friends.png b/docs/images/assign-tag/assign-1-friends.png
new file mode 100644
index 00000000000..ca093dcf3d7
Binary files /dev/null and b/docs/images/assign-tag/assign-1-friends.png differ
diff --git a/docs/images/assign-tag/assign-3-colleagues.png b/docs/images/assign-tag/assign-3-colleagues.png
new file mode 100644
index 00000000000..f8f22441a3f
Binary files /dev/null and b/docs/images/assign-tag/assign-3-colleagues.png differ
diff --git a/docs/images/assign-tag/before-assign-1-friends.png b/docs/images/assign-tag/before-assign-1-friends.png
new file mode 100644
index 00000000000..4307cd2eee2
Binary files /dev/null and b/docs/images/assign-tag/before-assign-1-friends.png differ
diff --git a/docs/images/assign-tag/before-assign-3-colleagues.png b/docs/images/assign-tag/before-assign-3-colleagues.png
new file mode 100644
index 00000000000..c15d014c939
Binary files /dev/null and b/docs/images/assign-tag/before-assign-3-colleagues.png differ
diff --git a/docs/images/assign-tag/view-1.png b/docs/images/assign-tag/view-1.png
new file mode 100644
index 00000000000..ebd1d62399c
Binary files /dev/null and b/docs/images/assign-tag/view-1.png differ
diff --git a/docs/images/assign-tag/view-assign-client.png b/docs/images/assign-tag/view-assign-client.png
new file mode 100644
index 00000000000..cb145ffe66d
Binary files /dev/null and b/docs/images/assign-tag/view-assign-client.png differ
diff --git a/docs/images/copyLinkSuccessMessage.png b/docs/images/copyLinkSuccessMessage.png
new file mode 100644
index 00000000000..cc4bbe7a398
Binary files /dev/null and b/docs/images/copyLinkSuccessMessage.png differ
diff --git a/docs/images/create-tag/create-tag-friends.png b/docs/images/create-tag/create-tag-friends.png
new file mode 100644
index 00000000000..8a7421f3b55
Binary files /dev/null and b/docs/images/create-tag/create-tag-friends.png differ
diff --git a/docs/images/del-tag/before-deltag-friends-colleagues-not-exist.png b/docs/images/del-tag/before-deltag-friends-colleagues-not-exist.png
new file mode 100644
index 00000000000..9a3a4471b2b
Binary files /dev/null and b/docs/images/del-tag/before-deltag-friends-colleagues-not-exist.png differ
diff --git a/docs/images/del-tag/before-deltag-friends-colleagues.png b/docs/images/del-tag/before-deltag-friends-colleagues.png
new file mode 100644
index 00000000000..9b23ea71c89
Binary files /dev/null and b/docs/images/del-tag/before-deltag-friends-colleagues.png differ
diff --git a/docs/images/del-tag/before-deltag-friends.png b/docs/images/del-tag/before-deltag-friends.png
new file mode 100644
index 00000000000..4d4d1151be5
Binary files /dev/null and b/docs/images/del-tag/before-deltag-friends.png differ
diff --git a/docs/images/del-tag/deltag-colleagues-not-exist.png b/docs/images/del-tag/deltag-colleagues-not-exist.png
new file mode 100644
index 00000000000..179a3865f2e
Binary files /dev/null and b/docs/images/del-tag/deltag-colleagues-not-exist.png differ
diff --git a/docs/images/del-tag/deltag-friends-colleagues-not-exist.png b/docs/images/del-tag/deltag-friends-colleagues-not-exist.png
new file mode 100644
index 00000000000..0f80381dbd9
Binary files /dev/null and b/docs/images/del-tag/deltag-friends-colleagues-not-exist.png differ
diff --git a/docs/images/del-tag/deltag-friends-colleagues.png b/docs/images/del-tag/deltag-friends-colleagues.png
new file mode 100644
index 00000000000..94a77056223
Binary files /dev/null and b/docs/images/del-tag/deltag-friends-colleagues.png differ
diff --git a/docs/images/del-tag/deltag-friends.png b/docs/images/del-tag/deltag-friends.png
new file mode 100644
index 00000000000..58edebb7f20
Binary files /dev/null and b/docs/images/del-tag/deltag-friends.png differ
diff --git a/docs/images/detailedview/DetailedViewExecutionState1.png b/docs/images/detailedview/DetailedViewExecutionState1.png
new file mode 100644
index 00000000000..500b3a46c37
Binary files /dev/null and b/docs/images/detailedview/DetailedViewExecutionState1.png differ
diff --git a/docs/images/detailedview/DetailedViewExecutionState2.png b/docs/images/detailedview/DetailedViewExecutionState2.png
new file mode 100644
index 00000000000..76530676007
Binary files /dev/null and b/docs/images/detailedview/DetailedViewExecutionState2.png differ
diff --git a/docs/images/detailedview/DetailedViewExecutionState3.png b/docs/images/detailedview/DetailedViewExecutionState3.png
new file mode 100644
index 00000000000..6a8491e8b96
Binary files /dev/null and b/docs/images/detailedview/DetailedViewExecutionState3.png differ
diff --git a/docs/images/detailedview/DetailedViewExecutionState4.png b/docs/images/detailedview/DetailedViewExecutionState4.png
new file mode 100644
index 00000000000..37978d00dc0
Binary files /dev/null and b/docs/images/detailedview/DetailedViewExecutionState4.png differ
diff --git a/docs/images/detailedview/DetailedViewGeneralExecution.png b/docs/images/detailedview/DetailedViewGeneralExecution.png
new file mode 100644
index 00000000000..4af836f28e8
Binary files /dev/null and b/docs/images/detailedview/DetailedViewGeneralExecution.png differ
diff --git a/docs/images/favourite/FavouriteSequenceDiagram.png b/docs/images/favourite/FavouriteSequenceDiagram.png
new file mode 100644
index 00000000000..168e78b5e19
Binary files /dev/null and b/docs/images/favourite/FavouriteSequenceDiagram.png differ
diff --git a/docs/images/favourite/FavouriteState0.png b/docs/images/favourite/FavouriteState0.png
new file mode 100644
index 00000000000..df80d2bab83
Binary files /dev/null and b/docs/images/favourite/FavouriteState0.png differ
diff --git a/docs/images/favourite/FavouriteState1.png b/docs/images/favourite/FavouriteState1.png
new file mode 100644
index 00000000000..7367346fe65
Binary files /dev/null and b/docs/images/favourite/FavouriteState1.png differ
diff --git a/docs/images/favourite/FavouriteState2.png b/docs/images/favourite/FavouriteState2.png
new file mode 100644
index 00000000000..cbb53a0acd6
Binary files /dev/null and b/docs/images/favourite/FavouriteState2.png differ
diff --git a/docs/images/favourite/FavouriteState3.png b/docs/images/favourite/FavouriteState3.png
new file mode 100644
index 00000000000..c5d817a21d2
Binary files /dev/null and b/docs/images/favourite/FavouriteState3.png differ
diff --git a/docs/images/favourite_command.png b/docs/images/favourite_command.png
new file mode 100644
index 00000000000..a126f6c6edb
Binary files /dev/null and b/docs/images/favourite_command.png differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
index 235da1c273e..44eb2d8040f 100644
Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ
diff --git a/docs/images/findtag/findtag-friends-inprogress-almostfinished.png b/docs/images/findtag/findtag-friends-inprogress-almostfinished.png
new file mode 100644
index 00000000000..893847a406c
Binary files /dev/null and b/docs/images/findtag/findtag-friends-inprogress-almostfinished.png differ
diff --git a/docs/images/findtag/findtag-friends.png b/docs/images/findtag/findtag-friends.png
new file mode 100644
index 00000000000..f69b9dfb6b4
Binary files /dev/null and b/docs/images/findtag/findtag-friends.png differ
diff --git a/docs/images/glennljw.png b/docs/images/glennljw.png
new file mode 100644
index 00000000000..ecb8e1fcc75
Binary files /dev/null and b/docs/images/glennljw.png differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
index b1f70470137..879e662e4e4 100644
Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ
diff --git a/docs/images/high-importance-flag/HighImportanceSequenceDiagram.png b/docs/images/high-importance-flag/HighImportanceSequenceDiagram.png
new file mode 100644
index 00000000000..295ec203e04
Binary files /dev/null and b/docs/images/high-importance-flag/HighImportanceSequenceDiagram.png differ
diff --git a/docs/images/high-importance-flag/ImportanceState0.png b/docs/images/high-importance-flag/ImportanceState0.png
new file mode 100644
index 00000000000..9071e2b0028
Binary files /dev/null and b/docs/images/high-importance-flag/ImportanceState0.png differ
diff --git a/docs/images/high-importance-flag/ImportanceState1.png b/docs/images/high-importance-flag/ImportanceState1.png
new file mode 100644
index 00000000000..0f52f4f4274
Binary files /dev/null and b/docs/images/high-importance-flag/ImportanceState1.png differ
diff --git a/docs/images/high-importance-flag/ImportanceState2.png b/docs/images/high-importance-flag/ImportanceState2.png
new file mode 100644
index 00000000000..a1bf6ea4fcd
Binary files /dev/null and b/docs/images/high-importance-flag/ImportanceState2.png differ
diff --git a/docs/images/high-importance-flag/add_importance_flag.png b/docs/images/high-importance-flag/add_importance_flag.png
new file mode 100644
index 00000000000..59c0cd1d17e
Binary files /dev/null and b/docs/images/high-importance-flag/add_importance_flag.png differ
diff --git a/docs/images/high-importance-flag/add_note_for_reason.png b/docs/images/high-importance-flag/add_note_for_reason.png
new file mode 100644
index 00000000000..98ff93189ed
Binary files /dev/null and b/docs/images/high-importance-flag/add_note_for_reason.png differ
diff --git a/docs/images/high-importance-flag/before_command.png b/docs/images/high-importance-flag/before_command.png
new file mode 100644
index 00000000000..882924e4d17
Binary files /dev/null and b/docs/images/high-importance-flag/before_command.png differ
diff --git a/docs/images/high-importance-flag/command_result.png b/docs/images/high-importance-flag/command_result.png
new file mode 100644
index 00000000000..20558e2da33
Binary files /dev/null and b/docs/images/high-importance-flag/command_result.png differ
diff --git a/docs/images/high-importance-flag/impts_command_result.png b/docs/images/high-importance-flag/impts_command_result.png
new file mode 100644
index 00000000000..f6f47b0b469
Binary files /dev/null and b/docs/images/high-importance-flag/impts_command_result.png differ
diff --git a/docs/images/image_index.png b/docs/images/image_index.png
new file mode 100644
index 00000000000..70bf6883d1c
Binary files /dev/null and b/docs/images/image_index.png differ
diff --git a/docs/images/images_file_chooser.png b/docs/images/images_file_chooser.png
new file mode 100644
index 00000000000..a276d9184fa
Binary files /dev/null and b/docs/images/images_file_chooser.png differ
diff --git a/docs/images/takufunkai.png b/docs/images/takufunkai.png
new file mode 100644
index 00000000000..db3d82e30c4
Binary files /dev/null and b/docs/images/takufunkai.png differ
diff --git a/docs/images/tehkokhoe.png b/docs/images/tehkokhoe.png
new file mode 100644
index 00000000000..3ce3be63c9f
Binary files /dev/null and b/docs/images/tehkokhoe.png differ
diff --git a/docs/images/unassign-tag/before-unassign-1-friends.png b/docs/images/unassign-tag/before-unassign-1-friends.png
new file mode 100644
index 00000000000..367d16becc9
Binary files /dev/null and b/docs/images/unassign-tag/before-unassign-1-friends.png differ
diff --git a/docs/images/unassign-tag/unassign-1-friends.png b/docs/images/unassign-tag/unassign-1-friends.png
new file mode 100644
index 00000000000..111907592a7
Binary files /dev/null and b/docs/images/unassign-tag/unassign-1-friends.png differ
diff --git a/docs/images/unassign-tag/view-1.png b/docs/images/unassign-tag/view-1.png
new file mode 100644
index 00000000000..e41e438cb89
Binary files /dev/null and b/docs/images/unassign-tag/view-1.png differ
diff --git a/docs/images/unassign-tag/view-unassign-client.png b/docs/images/unassign-tag/view-unassign-client.png
new file mode 100644
index 00000000000..0fdd272efd9
Binary files /dev/null and b/docs/images/unassign-tag/view-unassign-client.png differ
diff --git a/docs/images/weijuey.png b/docs/images/weijuey.png
new file mode 100644
index 00000000000..46c9a505878
Binary files /dev/null and b/docs/images/weijuey.png differ
diff --git a/docs/images/xsaints19x.png b/docs/images/xsaints19x.png
new file mode 100644
index 00000000000..b84a71351f8
Binary files /dev/null and b/docs/images/xsaints19x.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..a8c663ef09e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,20 @@
---
layout: page
-title: AddressBook Level-3
+title: d'Intérieur
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+[](https://github.com/AY2122S2-CS2103T-T12-2/tp/actions/workflows/gradle.yml)
+[](https://codecov.io/gh/AY2122S2-CS2103T-T12-2/tp)

-**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).
+**d'Intérieur 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.
+* If you are interested in using d'Intérieur, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested in developing d'Intérieur, 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)
+* Icons used: [Red Flag by Flaticon](https://www.flaticon.com/free-icon/finish_2164620), [White Flag by Flaticon](https://www.flaticon.com/free-icon/finish_2164598), [Check mark by Flaticon](https://www.flaticon.com/free-icon/checked_190411)
diff --git a/docs/team/glennljw.md b/docs/team/glennljw.md
new file mode 100644
index 00000000000..edd25b7483b
--- /dev/null
+++ b/docs/team/glennljw.md
@@ -0,0 +1,69 @@
+---
+layout: page
+title: Glenn Lim's Project Portfolio Page
+---
+
+### Project: d'Intérieur
+
+d'Intérieur is a desktop address book application, designed with interior designers in mind. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.
+
+Interior designers can use d'Intérieur to improve contact management and take on the needs of an ever-growing client base, so that they can focus on what matters most - delivering quality service for clients.
+
+Given below are my contributions to the project.
+
+* **New Feature**: `Assign Tag`
+ * What it does: Assigns a tag to a contact.
+ * Justification: This feature allows different contacts to be grouped at different points of time, such as labeling them as `InProgress` and `Completed` according to which stages they are at in the design cycle.'
+ * Highlights:
+ * Credits: This feature can be done when a contact profile is currently in view, which was done by [Wei Jue](weijuey.md)
+
+* **New Feature**: `Create Tag`
+ * What it does: Creates a tag with a meaningful name to categorise and group relevant contacts.
+ * Justification: This feature is essential as it allows tags to be created, then assigned to contacts whenever they reach a certain stage in the design cycle, or for personal use.
+
+* **New Feature**: `Delete Tag`
+ * What it does: Deletes a tag stored in the data, and unassigns any contact that are assigned to the tag to be deleted.
+ * Justification: This feature helps interior designers manage unused tags, or do a mass unassignment at once.
+
+
+* **New Feature**: `Find Tag`
+ * What it does: Acts as a filter to locate contacts with a certain tag.
+ * Justification: This feature helps interior designers locate contacts that are grouped by their tags.
+ * Highlights: This enhancement was made through the use of an `ActivatedTagList` which acts as a list of selected criteria/category just like how an eCommerce store would select filter to apply.
+
+* **New Feature**: `Unassign Tag`
+ * What it does: Unassigns a tag from a contact.
+ * Justification: This feature allows contacts who reach different stages of the design cycle can have their older tags (which specifies their older stage) removed from them.
+ * Credits: This feature can be done when a contact profile is currently in view, which was done by [Wei Jue](weijuey.md)
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=glennljw&breakdown=true)
+
+* **Project management**:
+ * Managed releases `1.2` - `1.4` (4 releases) on GitHub
+ * Added photos and details of team members [\#11](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/11)
+
+* **Enhancements to existing features**:
+ * Enhanced existing `Tag` to be a class of its own instead of a `String`.
+ * Allowed `add` feature to create and assign `Tag` upon adding a new contact. [\#83](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/83)
+ * Removed the editing tag functionality in the `edit` feature, abstracting this feature into an `assign` and `unassign` feature for tags. [\#83](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/83)
+ * Modified the help window by improving the GUI for displaying useful links to different sections of the User Guide. [\#190](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/190)
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `assign tag`, `create tag`, `delete tag`, `find tag` and `unassign tag` (Pull requests [\#26](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/26), [\#90](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/90), [\#95](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/95), [\#115](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/115), [\#189](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/189))
+ * Edited existing documentations for `edit` feature [\#90](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/90)
+ * Updated the UI images for the help window. [\#190](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/190)
+
+ * Developer Guide:
+ * Added implementation details of the following features:
+ * Find Tag
+
+ * Added sequence diagram in the implementation details of Find Tag.
+ * Added instructions for manual testing for Create Tag feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#87](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/87), [\#89](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/89), [\#196](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/196)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/84#issuecomment-1028047189), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/84#issuecomment-1028530974))
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/glennljw/ped/issues/4), [2](https://github.com/glennljw/ped/issues/3), [3](https://github.com/glennljw/ped/issues/2))
+
+
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
index 773a07794e2..057dad573be 100644
--- a/docs/team/johndoe.md
+++ b/docs/team/johndoe.md
@@ -3,9 +3,10 @@ layout: page
title: John Doe's Project Portfolio Page
---
-### Project: AddressBook Level 3
+### Project: d'Intérieur
+
+d'Intérieur is a desktop address book application, designed with interior designers in mind. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-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.
diff --git a/docs/team/takufunkai.md b/docs/team/takufunkai.md
new file mode 100644
index 00000000000..fff0aa11710
--- /dev/null
+++ b/docs/team/takufunkai.md
@@ -0,0 +1,67 @@
+---
+layout: page
+title: Ezekiel Toh's Project Portfolio Page
+---
+
+### Project: d'Intérieur
+
+d'Intérieur is a desktop app for interior designers to manage their contacts and projects. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.
+
+Interior designers can use d'Intèrieur to improve contact management and take on the needs of an ever-growing client base, so that they can focus on what matters most - delivering quality service for clients.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added images feature that allows user to add, delete and view images of a contact.
+ * What it does: Allows the user to add, delete, and view images associated with each of their contacts.
+ * Justification: Interior designers frequently work with images. Their clients' preferences, project designs and
+ other details are often best represented through an image and not merely through words. Having these images
+ can greatly improve their productivity, organization and help with attention to detail.
+ * Highlights: In addition to understanding the fundamentals of the application such as how a contact's information is
+ encapsulated and the interactions between the UI, Logic and Model, this feature requires understanding of how data
+ should be saved and retrieved locally, as well as handling situations of corrupted/missing images. Displaying images
+ in a responsive and user-friendly manner, as well as being able to click on the images, requires a decent level of
+ understanding of the UI.
+ * Credits: Final look and design of the UI was discussed and agreed on as a team.
+
+* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
+ * What it does: Allows the user to cycle through old and latest input histories using their up/down keys.
+ * Justification: It is common for certain commands to be repeatedly inputted with slight modifications to the command.
+ For example, a designer might want to favourite a bunch of users in quick succession. They can just press the up
+ key, and replace the index value with the correct value, saving them the time to retype `fav ` repeatedly.
+ * Highlights: Implementing the feature would require understanding of how the command input and processing flow works,
+ beginning from how commands are being received by the UI and how they are being sent to the logic for processing.
+ To save the history, it had to pass from the UI to the logic and then to the model, requiring understanding of the
+ UI, logic and model's implementations. Pros and cons had to be weighed as well as to how best to encapsulate the
+ history of a user. Understanding of how keystrokes can be recorded and kick-in a method through the UI was also required.
+
+* **New Feature**: Added favourites feature to set contacts as favourites and to filter them.
+ * What it does: Allows the user to set and unset their contacts as favourites, and to list these favourite contacts.
+ * Justification: Certain sets of contacts may be referenced repeatedly during different phases of a project. This
+ feature allows the designers to quickly pull up a truncated list of contacts-of-interest to reduce on search time and
+ the need to recall contact names for searching.
+ * Highlights: This feature involved changes to the model manager, and the person model. This requires understanding of
+ the underlying implementation of the person as the favourite status was added as a field of the person, as well as
+ the implementation of the model and its interactions with the logic (and indirectly, the UI) through the use of
+ predicates and filtered list.
+ * Credits: Position and look of the star representation of the Favourite status was discussed and finalized as a team.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=takufunkai&breakdown=true)
+
+* **Project management**:
+ * Managed releases `v1.2` - `v1.4` (5 releases) on GitHub
+
+* **Enhancements to existing features**:
+ * Improved the detailed view interface to be more responsive and user-friendly.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `favourite`, `images` and `commandhistory` [\#23](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/23), [\#103](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/103), [\#206](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/206)
+ * Improved the introduction paragraph of the User Guide and updated help command documentation [\#89](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/89).
+ * Added tutorial section to User Guide and updated sample images [\#206](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/206)
+ * Developer Guide:
+ * Added implementation details of the `favourite` and `images` feature [\#76](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/76) [\#210](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/210)
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#68](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/68), [\#99](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/99), [\#93](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/93)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/81#issuecomment-1027890350), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/193#issuecomment-1055513252), [3](https://github.com/nus-cs2103-AY2122S2/forum/issues/187#issuecomment-1059743036), [4](https://github.com/nus-cs2103-AY2122S2/forum/issues/29#issuecomment-1020795493), [5](https://github.com/nus-cs2103-AY2122S2/forum/issues/85#issuecomment-1028099491))
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/192), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/100#issuecomment-1029240062), [3](https://github.com/nus-cs2103-AY2122S2/forum/issues/83#issuecomment-1028106761))
diff --git a/docs/team/tehkokhoe.md b/docs/team/tehkokhoe.md
new file mode 100644
index 00000000000..43cc5474ffe
--- /dev/null
+++ b/docs/team/tehkokhoe.md
@@ -0,0 +1,35 @@
+---
+layout: page
+title: Teh Kok Hoe's Project Portfolio Page
+---
+
+### Project: d'Intérieur
+d'Intérieur is a desktop app for interior designers to manage their contacts and projects. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.
+
+Interior designers can use d'Intérieur to improve contact management and take on the needs of an ever-growing client base, so that they can focus on what matters most - delivering quality service for clients.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to add deadlines to contact.
+ * Justification: This feature is essential because interior designer can keep track of project deadlines.
+ * Highlights: This enhancement involves the Logic, Model and UI objects, which require a good understanding in how these objects interact with each other.
+ * Credits: Adding deadlines and removing deadlines without refreshing
+ the list of deadlines was implemented by Wei Jue.
+
+* **New Feature**: Added a sort command.
+ * Justification: This feature is essential as contact list grows larger, it is essential for interior designers to find top contacts based on a certain criteria.
+ * Highlights: This enhancement involves implementing multiple
+ Comparators for the different models, it also involved some changes to
+ UI to display a sorted list.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=T12&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-02-18&tabOpen=true&tabType=authorship&tabAuthor=tehkokhoe&tabRepo=AY2122S2-CS2103T-T12-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+* **Project management**:
+ Active participation in project with consistent feedback.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for [`sort*`](https://ay2122s2-cs2103t-t12-2.github.io/tp/UserGuide.html#prioritising-relevant-contacts-to-you--sort) and [`deadline*`](https://ay2122s2-cs2103t-t12-2.github.io/tp/UserGuide.html#adding-deadlines-to-meet-in-relation-to-a-contact--deadline) [#92](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/92) [#24](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/24)
+ * Developer Guide:
+ * Added implementation details of the [`deadline*`](https://ay2122s2-cs2103t-t12-2.github.io/tp/DeveloperGuide.html#deadline-feature) feature. [#74](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/74)
+
diff --git a/docs/team/weijuey.md b/docs/team/weijuey.md
new file mode 100644
index 00000000000..d4ca0c513d0
--- /dev/null
+++ b/docs/team/weijuey.md
@@ -0,0 +1,47 @@
+---
+layout: page
+title: Yoong Wei Jue's Project Portfolio Page
+---
+
+### Project: d'Intérieur
+
+d'Intérieur is a desktop app for interior designers to manage their contacts and projects. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.
+
+Interior designers can use d'Intèrieur to improve contact management and take on the needs of an ever-growing client base, so that they can focus on what matters most - delivering quality service for clients.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added notes feature
+ * What it does: allows the user to add and delete notes to each contact, to be referenced in the future.
+ * Justification: This feature is flexible in utility, allowing the user to save any information they deem important, thus enhancing the product.
+ * Highlights: Implementing the feature required understanding how modification of internal data is done. Integrating a variably-sized UI element also required careful consideration of pros and cons of different solutions.
+ * Credits: UI design decisions were discussed and finalised as a team.
+
+* **New Feature**: Added detailed view feature
+ * What it does: allows the user to view a contact in detail, which the app will switch to a more focused view.
+ * Justification: This feature enables the user to do more focused work with one particular contact, especially because some information is only fully available to view by using this feature. It can allow adding as many new information to a contact as desired.
+ * Highlights: The feature involved multiple components of the application and adding new interfaces to each of them to interact, thus it required a good understanding of their current interactions. Adding new capabilities to current commands also required understanding their existing behaviour.
+ * Credits: Idea to communicate to MainWindow the panel view to show was first implemented by Ezekiel for his Images feature, and was adapted from his implementation. Final design of the detailed contact view was also refined by Ezekiel. Normal command execution path of new features were first implemented by team members.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=weijuey&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Managed releases `v1.2` - `v1.4` (5 releases) on GitHub
+ * Added target user profile, value proposition and glossary to Developer Guide
+
+* **Enhancements to existing features**:
+ * Update existing commands to work in the new detailed view feature mentioned above, with simpler command formats.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the new features `note` and `view` [\#28](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/28), [\#93](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/93)
+ * Reorganised commands to sections and added table of contents for more flexible linking to sections. [\#183](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/183)
+ * Added information to each existing commands on compatibility with new detailed view feature, and update command summary table to capture the information succinctly. [\#99](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/99)
+ * Developer Guide:
+ * Added implementation details and diagrams for the `note` and `view` features. [#72](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/72), [\#203](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/203)
+ * Update existing class diagrams to match new architecture. [\#203](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/203)
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#44](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/44), [\#74](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/74), [\#85](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/85), [\#92](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/92)
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/weijuey/ped/issues/1), [2](https://github.com/weijuey/ped/issues/3), [3](https://github.com/weijuey/ped/issues/5)
+
diff --git a/docs/team/xsaints19x.md b/docs/team/xsaints19x.md
new file mode 100644
index 00000000000..9064ac04c23
--- /dev/null
+++ b/docs/team/xsaints19x.md
@@ -0,0 +1,47 @@
+---
+layout: page
+title: David Limantara's Project Portfolio Page
+---
+
+### Project: d'Intérieur
+
+d'Intérieur is a desktop address book application, designed with interior designers in mind. The designer interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 26 kLoC.
+
+Interior designers can use d'Intèrieur to improve contact management and take on the needs of an ever-growing client base, so that they can focus on what matters most - delivering quality service for clients.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to add address as an optional field.
+ * Justification: This feature was done because it is not critical for an interior designer to know a client's address.
+
+* **New Feature**: Added a high importance flag icon which appears beside the contact name
+ * What it does: Each contact will have an unlit flag beside the contact name by default. When activated, it turns red.
+ * Justification: This feature is used to indicate which contacts are of high importance, whereby there are some important concerns that the interior designers should take note of for each particular contact.
+ * Highlights: This feature should be used in tandem with the `note` feature for best results as the `note` feature can be used to write down key concerns that the interior designers should take note of for a particular contact.
+
+* **New Feature**: Added a list important contacts command.
+ * What it does: It acts like a filter to only display contacts who have the red flag lit up beside their name .
+ * Justification: This feature will help interior designers pay special attention to those contacts listed so that their needs or concerns will be met.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=xsaints19x&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-02-18&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Managed releases `v1.2` - `v1.4` (5 releases) on GitHub
+
+* **Enhancements to existing features**:
+ * Updated the help window by adding a link to the command summary (Pull request [\#111](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/111))
+ * Added an alert to inform users that either links has been copied successfully for the help window (Pull request [\#111](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/111))
+
+* **Documentation**:
+ * User Guide:
+ * Updated description for the commands `add`, `impt`, `impts` and `help`: [\#29](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/29), [\#88](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/88), [\#110](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/110), [\#196](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/196)
+ * Updated command summary for the commands `add`, `impt` and `impts`: [\#29](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/29), [\#88](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/88)
+ * Updated UG throughout to standardise styling and emphasis on key points: [\#196](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/196)
+ * Developer Guide:
+ * Added description for the enhanced `add` command and added code snippet
+ * Added implementation details and UML diagram for the `high importance flag` feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [\#20](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/20), [\#99](https://github.com/AY2122S2-CS2103T-T12-2/tp/pull/99)
+ * Contributed to forum discussions (examples: [1](https://github.com/nus-cs2103-AY2122S2/forum/issues/244), [2](https://github.com/nus-cs2103-AY2122S2/forum/issues/111))
+ * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/xSaints19x/ped/issues/1), [2](https://github.com/xSaints19x/ped/issues/2), [3](https://github.com/xSaints19x/ped/issues/3))
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..e19227eeb5f 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -36,7 +36,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 0, true);
+ public static final Version VERSION = new Version(1, 4, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..e3a38082af8 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -9,5 +9,9 @@ public class Messages {
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_CRITERIA = "%1$s is an invalid criteria!";
+ public static final String MESSAGE_SORTED = "List sorted by %1$s";
+ public static final String MESSAGE_INCOMPATIBLE_VIEW_MODE = "This command cannot be run in this view mode!";
+ public static final String MESSAGE_INDEX_OUT_OF_BOUND = "The given index is out of bound!";
}
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java
index b1e2767cdd9..af8d3febf66 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/seedu/address/commons/util/FileUtil.java
@@ -5,6 +5,9 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.logging.Logger;
+
+import seedu.address.MainApp;
/**
* Writes and reads files
@@ -12,6 +15,7 @@
public class FileUtil {
private static final String CHARSET = "UTF-8";
+ private static Logger logger = Logger.getLogger(String.valueOf(MainApp.class));
public static boolean isFileExists(Path file) {
return Files.exists(file) && Files.isRegularFile(file);
@@ -80,4 +84,13 @@ public static void writeToFile(Path file, String content) throws IOException {
Files.write(file, content.getBytes(CHARSET));
}
+ /**
+ * Deletes the file at the given path.
+ */
+ public static void deleteFile(Path file) {
+ if (!file.toFile().delete()) {
+ logger.warning(String.format("File was not deleted: %s", file));
+ }
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..99581d06ead 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -3,12 +3,16 @@
import java.nio.file.Path;
import javafx.collections.ObservableList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.commandhistory.CommandHistoryEntry;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* API of the Logic component
@@ -16,6 +20,7 @@
public interface Logic {
/**
* Executes the command and returns the result.
+ *
* @param commandText The command as entered by the user.
* @return the result of the command execution.
* @throws CommandException If an error occurs during command execution.
@@ -23,6 +28,19 @@ public interface Logic {
*/
CommandResult execute(String commandText) throws CommandException, ParseException;
+ CommandResult executeInDetailedViewMode(String commandText) throws CommandException, ParseException;
+
+ /**
+ * Caches raw user input.
+ */
+ void cacheCommandText(String commandText);
+
+ /**
+ * Retrieves the command text
+ * @param i the number of commands to backstep to
+ */
+ CommandHistoryEntry getCommandText(int i);
+
/**
* Returns the AddressBook.
*
@@ -30,9 +48,18 @@ public interface Logic {
*/
ReadOnlyAddressBook getAddressBook();
- /** Returns an unmodifiable view of the filtered list of persons */
+ /**
+ * Returns an unmodifiable view of the filtered list of persons
+ */
ObservableList getFilteredPersonList();
+ ObservableList getActivatedTagList();
+
+ SortedList getSortedPersonList();
+
+ /** Returns the detailed view of a contact */
+ ObservableList getDetailedContactView();
+
/**
* Returns the user prefs' address book file path.
*/
@@ -47,4 +74,10 @@ public interface Logic {
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Gets images to be displayed.
+ */
+ ImageDetailsList getImagesToView();
+
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..78576a59002 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -5,16 +5,21 @@
import java.util.logging.Logger;
import javafx.collections.ObservableList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.DetailedViewExecutable;
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.commandhistory.CommandHistoryEntry;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
import seedu.address.storage.Storage;
/**
@@ -54,6 +59,23 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
return commandResult;
}
+ @Override
+ public CommandResult executeInDetailedViewMode(String commandText) throws CommandException, ParseException {
+ logger.info("----------------[USER COMMAND][" + commandText + "]");
+
+ CommandResult commandResult;
+ DetailedViewExecutable command = addressBookParser.parseDetailedViewCommand(commandText);
+ commandResult = command.executeInDetailedView(model);
+
+ try {
+ storage.saveAddressBook(model.getAddressBook());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
@Override
public ReadOnlyAddressBook getAddressBook() {
return model.getAddressBook();
@@ -64,11 +86,31 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getActivatedTagList() {
+ return model.getActivatedTagList();
+ }
+
+ @Override
+ public SortedList getSortedPersonList() {
+ return model.getSortedPersonList();
+ }
+
+ @Override
+ public ObservableList getDetailedContactView() {
+ return model.getDetailedContactView();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
}
+ @Override
+ public ImageDetailsList getImagesToView() {
+ return model.getImagesToView();
+ }
+
@Override
public GuiSettings getGuiSettings() {
return model.getGuiSettings();
@@ -78,4 +120,14 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ @Override
+ public void cacheCommandText(String commandText) {
+ model.updateCommandHistory(commandText);
+ }
+
+ @Override
+ public CommandHistoryEntry getCommandText(int i) {
+ return model.getCommandHistory(i);
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..12e948129a7 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -7,14 +7,17 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.Set;
+
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* Adds a person to the address book.
*/
-public class AddCommand extends Command {
+public class AddCommand extends Command implements DetailedViewExecutable {
public static final String COMMAND_WORD = "add";
@@ -26,12 +29,11 @@ public class AddCommand extends Command {
+ 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";
+ + PREFIX_NAME + "Mary Jane "
+ + PREFIX_PHONE + "12345678 "
+ + PREFIX_EMAIL + "maryJ@example.com "
+ + PREFIX_ADDRESS + "Bukit Timah "
+ + PREFIX_TAG + "completed ";
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";
@@ -49,15 +51,29 @@ public AddCommand(Person person) {
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
+ Set tagList = toAdd.getTags();
if (model.hasPerson(toAdd)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
model.addPerson(toAdd);
+
+ for (Tag tag : tagList) {
+ if (!model.hasTag(tag)) {
+ Command createTagCommand = new CreateTagCommand(tag.tagName);
+ createTagCommand.execute(model);
+ }
+ }
+
return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
}
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ return execute(model);
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
diff --git a/src/main/java/seedu/address/logic/commands/AddImageCommand.java b/src/main/java/seedu/address/logic/commands/AddImageCommand.java
new file mode 100644
index 00000000000..8c8b229c960
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddImageCommand.java
@@ -0,0 +1,178 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.commands.CommandResult.SpecialCommandResult.VIEW_IMAGES;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+import javafx.stage.FileChooser;
+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.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.image.util.ImageUtil;
+import seedu.address.model.person.Person;
+
+public class AddImageCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "addimg";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Adds an image to 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 ADD_IMAGE_SUCCESS = "Added %d image(s) to person: %s";
+ public static final String ADD_IMAGE_NONE_SELECTED = "No images were selected to be added";
+ public static final String ADD_IMAGE_FAIL = "Failed to add image(s)";
+ public static final String DUPLICATE_IMAGES = "An image with the name: \"%s\" already exists";
+
+ private static final Logger logger = Logger.getLogger(String.valueOf(AddImageCommand.class));
+
+ private final Index targetIndex;
+
+ /**
+ * Constructs a new add image command object.
+ *
+ * @param targetIndex the index of the person to add images to.
+ */
+ public AddImageCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ public AddImageCommand() {
+ this.targetIndex = null;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(targetIndex);
+
+ List lastShownList = model.getSortedPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ List images = openImageChooser();
+
+ if (images.isEmpty()) {
+ return new CommandResult(ADD_IMAGE_NONE_SELECTED);
+ }
+
+ StringBuilder resultStringBuilder = new StringBuilder();
+ List imagesToAdd = new ArrayList<>();
+ for (File imgFile : images) {
+ Path destPath = model.getContactImagesFilePath().resolve(imgFile.getName());
+ if (ImageUtil.fileExists(imgFile, model.getContactImagesFilePath())) {
+ resultStringBuilder
+ .append(String.format(DUPLICATE_IMAGES, imgFile.getName()))
+ .append("\n");
+ continue;
+ }
+ ImageDetails copiedImage;
+ try {
+ copiedImage = ImageUtil.copyTo(imgFile, destPath);
+ } catch (IOException ioe) {
+ throw new CommandException(ADD_IMAGE_FAIL);
+ }
+
+ imagesToAdd.add(copiedImage);
+ }
+
+ Person personToEdit = lastShownList.get(targetIndex.getZeroBased());
+ Person editedPerson = addImages(personToEdit, imagesToAdd);
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.setImagesToView(editedPerson.getImageDetailsList());
+ resultStringBuilder.append(String.format(ADD_IMAGE_SUCCESS, imagesToAdd.size(), editedPerson));
+
+ return new CommandResult(resultStringBuilder.toString(), VIEW_IMAGES);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List images = openImageChooser();
+
+ if (images.isEmpty()) {
+ return new CommandResult(ADD_IMAGE_NONE_SELECTED, CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ StringBuilder resultStringBuilder = new StringBuilder();
+ List imagesToAdd = new ArrayList<>();
+ for (File imgFile : images) {
+ Path destPath = model.getContactImagesFilePath().resolve(imgFile.getName());
+ if (ImageUtil.fileExists(imgFile, model.getContactImagesFilePath())) {
+ resultStringBuilder
+ .append(String.format(DUPLICATE_IMAGES, imgFile.getName()))
+ .append("\n");
+ continue;
+ }
+ ImageDetails copiedImage;
+ try {
+ copiedImage = ImageUtil.copyTo(imgFile, destPath);
+ } catch (IOException ioe) {
+ throw new CommandException(ADD_IMAGE_FAIL);
+ }
+
+ imagesToAdd.add(copiedImage);
+ }
+
+ Person personToEdit = model.getDetailedContactViewPerson();
+ Person editedPerson = addImages(personToEdit, imagesToAdd);
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ resultStringBuilder.append(String.format(ADD_IMAGE_SUCCESS, imagesToAdd.size(), editedPerson));
+
+ return new CommandResult(resultStringBuilder.toString(),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ private static Person addImages(Person personToEdit, List images) {
+ ImageDetailsList oldImages = personToEdit.getImageDetailsList();
+ ImageDetailsList newImages = oldImages.appendImageDetails(images);
+ return new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getDeadlines(), personToEdit.getNotes(),
+ personToEdit.getTags(), personToEdit.getFavouriteStatus(),
+ personToEdit.getHighImportanceStatus(), newImages);
+ }
+
+ private List openImageChooser() {
+ logger.info("Launching file chooser");
+ FileChooser fileChooser = ImageUtil.openImageFileChooser();
+
+ List selectedFiles = fileChooser.showOpenMultipleDialog(null);
+ if (selectedFiles == null) {
+ return new ArrayList<>();
+ }
+ return selectedFiles;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof AddImageCommand)) {
+ return false;
+ }
+
+ AddImageCommand e = (AddImageCommand) other;
+
+ return Objects.equals(this.targetIndex, e.targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AssignTagCommand.java b/src/main/java/seedu/address/logic/commands/AssignTagCommand.java
new file mode 100644
index 00000000000..a3c768ce5f3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AssignTagCommand.java
@@ -0,0 +1,170 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Assigns a tag to a contact in the address book.
+ */
+public class AssignTagCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "assign";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Assigns a tag with a given tag name "
+ + "(case-insensitive) to a contact identified by a given index number used in the displayed "
+ + "contacts list.\n"
+ + "Parameters: INDEX (must be a positive integer), TAGNAME"
+ + "Example: " + COMMAND_WORD + " 1 friends";
+
+ public static final String MESSAGE_SUCCESS = "Tag successfully assigned: %1$s";
+ public static final String MESSAGE_DUPLICATE_TAG = "This tag was already assigned to this person previously";
+ public static final String MESSAGE_UNKNOWN_TAG = "Tag '%1$s' has not been created yet.";
+
+ private final String tagName;
+ private final Index targetIndex;
+
+ /**
+ * Creates an AssignTagCommand to assign a {@code Tag} to a {@code Person}.
+ * @param targetIndex the index of the contact specified.
+ * @param tagName the name of the Tag.
+ */
+ public AssignTagCommand(Index targetIndex, String tagName) {
+ this.tagName = tagName;
+ this.targetIndex = targetIndex;
+ }
+
+ /**
+ * Creates an AssignTagCommand to assign a {@code Tag} to the {@code Person}
+ * in detailed view.
+ * @param tagName the name of the Tag.
+ */
+ public AssignTagCommand(String tagName) {
+ this.tagName = tagName;
+ this.targetIndex = null;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag newTag = new Tag(tagName);
+ boolean tagHasBeenCreated = model.hasTag(newTag);
+
+ if (!tagHasBeenCreated) {
+ throw new CommandException(String.format(MESSAGE_UNKNOWN_TAG, tagName));
+ }
+
+ List lastShownList = model.getSortedPersonList();
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(targetIndex.getZeroBased());
+
+ if (!canAddTag(personToEdit, newTag)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+
+ Person editedPerson = addTagToNewPerson(personToEdit, newTag);
+ model.setPerson(personToEdit, editedPerson);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, editedPerson));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag newTag = new Tag(tagName);
+ boolean tagHasBeenCreated = model.hasTag(newTag);
+
+ if (!tagHasBeenCreated) {
+ throw new CommandException(String.format(MESSAGE_UNKNOWN_TAG, tagName));
+ }
+
+ Person personToEdit = model.getDetailedContactViewPerson();
+
+ if (!canAddTag(personToEdit, newTag)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+
+ Person editedPerson = addTagToNewPerson(personToEdit, newTag);
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ /**
+ * Adds a specific {@code Tag} to the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to add the tag to.
+ * @param newTag the tag to add.
+ * @return the person with the added tag.
+ */
+ private static Person addTagToNewPerson(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ Set tags = personToEdit.getTags();
+ Favourite favourite = personToEdit.getFavouriteStatus();
+ HighImportance highImportance = personToEdit.getHighImportanceStatus();
+ ImageDetailsList images = personToEdit.getImageDetailsList();
+
+ Set newTags = new HashSet<>(tags);
+ newTags.add(newTag);
+
+ return new Person(name, phone, email, address, deadlines, notes, newTags, favourite, highImportance, images);
+ }
+
+ /**
+ * Checks if a specific {@code Tag} can be added to the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to add the tag to.
+ * @param newTag the tag to add.
+ * @return boolean value of whether the tag can be added.
+ */
+ private static boolean canAddTag(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+ Set tags = personToEdit.getTags();
+ Set newTags = new HashSet<>(tags);
+ return !newTags.contains(newTag);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof AssignTagCommand)) {
+ return false;
+ }
+
+ AssignTagCommand e = (AssignTagCommand) other;
+ return Objects.equals(targetIndex, e.targetIndex)
+ && this.tagName.equals(e.tagName);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..b021139adb4 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -17,6 +17,7 @@ public class ClearCommand extends Command {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
+ model.clearDetailedContactView();
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..b001e747ce5 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -11,19 +11,23 @@ public class CommandResult {
private final String feedbackToUser;
- /** Help information should be shown to the user. */
- private final boolean showHelp;
+ public enum SpecialCommandResult {
+ SHOW_HELP, // Help information should be shown to the user
+ EXIT, // The application should exit
+ VIEW_IMAGES, // Should load up the contact's images
+ DETAILED_VIEW, // Should show the detailed contact view
+ LIST_VIEW, // Should show the entire list of contacts
+ NONE
+ }
- /** The application should exit. */
- private final boolean exit;
+ private final SpecialCommandResult specialCommandResult;
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, SpecialCommandResult specialCommandResult) {
this.feedbackToUser = requireNonNull(feedbackToUser);
- this.showHelp = showHelp;
- this.exit = exit;
+ this.specialCommandResult = requireNonNull(specialCommandResult);
}
/**
@@ -31,19 +35,15 @@ 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, SpecialCommandResult.NONE);
}
public String getFeedbackToUser() {
return feedbackToUser;
}
- public boolean isShowHelp() {
- return showHelp;
- }
-
- public boolean isExit() {
- return exit;
+ public SpecialCommandResult getSpecialCommandResult() {
+ return specialCommandResult;
}
@Override
@@ -57,15 +57,13 @@ public boolean equals(Object other) {
return false;
}
- CommandResult otherCommandResult = (CommandResult) other;
- return feedbackToUser.equals(otherCommandResult.feedbackToUser)
- && showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ return feedbackToUser.equals(((CommandResult) other).feedbackToUser)
+ && specialCommandResult == ((CommandResult) other).specialCommandResult;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, specialCommandResult);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/CreateTagCommand.java b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java
new file mode 100644
index 00000000000..b38a7a3d050
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/CreateTagCommand.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.tag.Tag;
+
+public class CreateTagCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "tag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Creates a tag with a given tag name.\n"
+ + "Parameters: TAG_NAME (case insensitive)\n"
+ + "Example: " + COMMAND_WORD + " Friends";
+
+ public static final String MESSAGE_CREATE_TAG_SUCCESS = "Created tag: %1$s";
+ public static final String MESSAGE_DUPLICATE_TAG = "This tag already exists in the address book.";
+
+ private final String tagName;
+
+ /**
+ * Creates a CreateTagCommand to add to {@code UniqueTagList}.
+ * @param tagName the name of the Tag.
+ */
+ public CreateTagCommand(String tagName) {
+ requireNonNull(tagName);
+ this.tagName = tagName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag createdTag = new Tag(this.tagName);
+ if (model.hasTag(createdTag)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+ model.addTag(createdTag);
+ return new CommandResult(String.format(MESSAGE_CREATE_TAG_SUCCESS, this.tagName));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag createdTag = new Tag(this.tagName);
+ if (model.hasTag(createdTag)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TAG);
+ }
+ model.addTag(createdTag);
+ return new CommandResult(String.format(MESSAGE_CREATE_TAG_SUCCESS, this.tagName),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CreateTagCommand // instanceof handles nulls
+ && tagName.equals(((CreateTagCommand) other).tagName));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeadlineCommand.java b/src/main/java/seedu/address/logic/commands/DeadlineCommand.java
new file mode 100644
index 00000000000..354228b0ace
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeadlineCommand.java
@@ -0,0 +1,120 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
+
+import java.util.List;
+import java.util.Objects;
+
+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.DeadlineList;
+import seedu.address.model.person.Person;
+
+public class DeadlineCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "deadline";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Gives a deadline with a description to person identified\n"
+ + "by the index number used in the displayed person list.\n"
+ + "Parameters: INDEX (must be a positive integer)"
+ + PREFIX_DEADLINE + "DESCRIPTION DATE (DATE should be in dd/mm/yyyy)\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_DEADLINE + "windows 01/01/2022";
+
+ public static final String MESSAGE_ADD_DEADLINE_SUCCESS = "Added deadline for: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_NO_DEADLINES_ADDED = "No deadlines given.";
+
+ private final Index targetIndex;
+ private final DeadlineList deadlines;
+
+ /**
+ * Creates a DeadlineCommand to add to specified {@code Person}.
+ *
+ * @param targetIndex the index of the person specified.
+ * @param deadlines the date of the deadline.
+ */
+ public DeadlineCommand(Index targetIndex, DeadlineList deadlines) {
+ requireNonNull(targetIndex);
+ requireNonNull(deadlines);
+ this.targetIndex = targetIndex;
+ this.deadlines = deadlines;
+ }
+
+ /**
+ * Creates a DeadlineCommand to add to {@code Person} in detailed view.
+ *
+ * @param deadlines the date of the deadline.
+ */
+ public DeadlineCommand(DeadlineList deadlines) {
+ requireNonNull(deadlines);
+ this.targetIndex = null;
+ this.deadlines = deadlines;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getSortedPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToAddDeadline = lastShownList.get(targetIndex.getZeroBased());
+ DeadlineList newDeadlineList = personToAddDeadline.getDeadlines().appendDeadlines(deadlines);
+ Person editedPerson = new Person(
+ personToAddDeadline.getName(), personToAddDeadline.getPhone(), personToAddDeadline.getEmail(),
+ personToAddDeadline.getAddress(), newDeadlineList, personToAddDeadline.getNotes(),
+ personToAddDeadline.getTags(), personToAddDeadline.getFavouriteStatus(),
+ personToAddDeadline.getHighImportanceStatus(), personToAddDeadline.getImageDetailsList());
+
+ if (!personToAddDeadline.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ model.setPerson(personToAddDeadline, editedPerson);
+ return new CommandResult(String.format(MESSAGE_ADD_DEADLINE_SUCCESS, personToAddDeadline));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ requireNonNull(model);
+
+ Person personToAddDeadline = model.getDetailedContactViewPerson();
+ DeadlineList newDeadlineList = personToAddDeadline.getDeadlines().appendDeadlines(deadlines);
+ Person editedPerson = new Person(
+ personToAddDeadline.getName(), personToAddDeadline.getPhone(), personToAddDeadline.getEmail(),
+ personToAddDeadline.getAddress(), newDeadlineList, personToAddDeadline.getNotes(),
+ personToAddDeadline.getTags(), personToAddDeadline.getFavouriteStatus(),
+ personToAddDeadline.getHighImportanceStatus(), personToAddDeadline.getImageDetailsList());
+
+ model.setPerson(personToAddDeadline, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_ADD_DEADLINE_SUCCESS, personToAddDeadline),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeadlineCommand)) {
+ return false;
+ }
+
+ // state check
+ DeadlineCommand e = (DeadlineCommand) other;
+ return Objects.equals(this.targetIndex, e.targetIndex)
+ && deadlines.equals(e.deadlines);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..b8826a488d5 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -33,7 +33,7 @@ public DeleteCommand(Index targetIndex) {
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ List lastShownList = model.getSortedPersonList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
diff --git a/src/main/java/seedu/address/logic/commands/DeleteDeadlineCommand.java b/src/main/java/seedu/address/logic/commands/DeleteDeadlineCommand.java
new file mode 100644
index 00000000000..64353746c84
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteDeadlineCommand.java
@@ -0,0 +1,58 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+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.DeadlineList;
+import seedu.address.model.person.Person;
+
+public class DeleteDeadlineCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "deldl";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the deadline identified by its index number in the list of"
+ + " deadlines of this person.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_DEADLINE_SUCCESS = "Deleted deadline";
+
+ private final Index index;
+
+ /**
+ * @param index index of the deadline in the list to delete.
+ */
+ public DeleteDeadlineCommand(Index index) {
+ requireNonNull(index);
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(Messages.MESSAGE_INCOMPATIBLE_VIEW_MODE);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+ Person personToEdit = model.getDetailedContactViewPerson();
+ if (index.getZeroBased() >= personToEdit.getDeadlines().size()) {
+ throw new CommandException(Messages.MESSAGE_INDEX_OUT_OF_BOUND);
+ }
+ DeadlineList newDeadlineList = personToEdit.getDeadlines().delete(index.getZeroBased());
+ Person editedPerson = new Person(
+ personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), newDeadlineList, personToEdit.getNotes(),
+ personToEdit.getTags(), personToEdit.getFavouriteStatus(),
+ personToEdit.getHighImportanceStatus(), personToEdit.getImageDetailsList());
+
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(MESSAGE_DELETE_DEADLINE_SUCCESS,
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteImageCommand.java b/src/main/java/seedu/address/logic/commands/DeleteImageCommand.java
new file mode 100644
index 00000000000..0e24ed61433
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteImageCommand.java
@@ -0,0 +1,153 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.commands.CommandResult.SpecialCommandResult.VIEW_IMAGES;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.image.util.ImageUtil;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+public class DeleteImageCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "delimg";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the image identified by the index number used in the displayed images list of "
+ + "the person identified by the index number used in the displayed person list.\n"
+ + "Parameters: PERSON_INDEX (must be a positive integer) i/IMAGE_INDEX\n"
+ + "Example: " + COMMAND_WORD + " 1 i/2";
+
+ public static final String MESSAGE_INVALID_IMAGE_DISPLAYED_INDEX = "The image index provided is invalid";
+ public static final String MESSAGE_DELETE_IMAGE_SUCCESSFUL = "Image %d has been deleted for person %s";
+
+ private final Index personIndex;
+ private final Index imageIndex;
+
+ /**
+ * Constructs a command to delete an image.
+ *
+ * @param personIndex of the person to delete.
+ * @param imageIndex of the image to delete, relative to the person to delete.
+ */
+ public DeleteImageCommand(Index personIndex, Index imageIndex) {
+ requireNonNull(personIndex);
+ requireNonNull(imageIndex);
+
+ this.personIndex = personIndex;
+ this.imageIndex = imageIndex;
+ }
+
+ /**
+ * Constructs a command to delete the image of the {@code Person} in detailed view.
+ * @param imageIndex of the image to delete.
+ */
+ public DeleteImageCommand(Index imageIndex) {
+ this.personIndex = null;
+ this.imageIndex = imageIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(personIndex);
+
+ List lastShownList = model.getSortedPersonList();
+
+ if (personIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(personIndex.getZeroBased());
+ ImageDetailsList images = personToEdit.getImageDetailsList();
+
+ if (imageIndex.getZeroBased() >= images.size()) {
+ throw new CommandException(MESSAGE_INVALID_IMAGE_DISPLAYED_INDEX);
+ }
+
+ ImageDetails imageToDelete = images.get(imageIndex.getZeroBased());
+ ImageUtil.removeFile(imageToDelete);
+ ImageDetailsList sanitizedList = ImageUtil.sanitizeList(images, model.getContactImagesFilePath());
+ Person editedPerson = createImageDeletedPerson(personToEdit, sanitizedList);
+
+ model.setPerson(personToEdit, editedPerson);
+ model.setImagesToView(editedPerson.getImageDetailsList());
+
+ return new CommandResult(
+ String.format(MESSAGE_DELETE_IMAGE_SUCCESSFUL, imageIndex.getOneBased(), editedPerson), VIEW_IMAGES);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Person personToEdit = model.getDetailedContactViewPerson();
+ ImageDetailsList images = personToEdit.getImageDetailsList();
+
+ if (imageIndex.getZeroBased() >= images.size()) {
+ throw new CommandException(MESSAGE_INVALID_IMAGE_DISPLAYED_INDEX);
+ }
+
+ ImageDetails imageToDelete = images.get(imageIndex.getZeroBased());
+ ImageUtil.removeFile(imageToDelete);
+ ImageDetailsList sanitizedList = ImageUtil.sanitizeList(images, model.getContactImagesFilePath());
+ Person editedPerson = createImageDeletedPerson(personToEdit, sanitizedList);
+
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+
+ return new CommandResult(
+ String.format(MESSAGE_DELETE_IMAGE_SUCCESSFUL, imageIndex.getOneBased(), editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ private static Person createImageDeletedPerson(Person personToEdit, ImageDetailsList sanitizedList) {
+ assert personToEdit != null;
+ assert sanitizedList != null;
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ HighImportance highImportanceStatus = personToEdit.getHighImportanceStatus();
+ Favourite favouriteStatus = personToEdit.getFavouriteStatus();
+ Set tags = personToEdit.getTags();
+
+ return new Person(name, phone, email, address, deadlines,
+ notes, tags, favouriteStatus, highImportanceStatus, sanitizedList);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof DeleteImageCommand)) {
+ return false;
+ }
+
+ DeleteImageCommand e = (DeleteImageCommand) other;
+ return Objects.equals(this.personIndex, e.personIndex)
+ && Objects.equals(this.imageIndex, e.imageIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteNoteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteNoteCommand.java
new file mode 100644
index 00000000000..75137275ac3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteNoteCommand.java
@@ -0,0 +1,61 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+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.Notes;
+import seedu.address.model.person.Person;
+
+public class DeleteNoteCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "delnote";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the note identified by its index number in the list of"
+ + " notes of this person.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_NOTE_SUCCESS = "Deleted note";
+
+ private final Index index;
+
+ /**
+ * @param index index of the note in the notes list to delete.
+ */
+ public DeleteNoteCommand(Index index) {
+ requireNonNull(index);
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(Messages.MESSAGE_INCOMPATIBLE_VIEW_MODE);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+ Person personToEdit = model.getDetailedContactViewPerson();
+ if (index.getZeroBased() >= personToEdit.getNotes().value.size()) {
+ throw new CommandException(Messages.MESSAGE_INDEX_OUT_OF_BOUND);
+ }
+ Person editedPerson = updateNotes(personToEdit, index.getZeroBased());
+
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(MESSAGE_DELETE_NOTE_SUCCESS,
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ private static Person updateNotes(Person personToEdit, int index) {
+ Notes oldNotes = personToEdit.getNotes();
+ Notes newNotes = oldNotes.delete(index);
+ return new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getDeadlines(), newNotes,
+ personToEdit.getTags(), personToEdit.getFavouriteStatus(), personToEdit.getHighImportanceStatus(),
+ personToEdit.getImageDetailsList());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
new file mode 100644
index 00000000000..61af44c9b83
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteTagCommand.java
@@ -0,0 +1,149 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javafx.collections.ObservableList;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Deletes tags that contain any of the argument keywords, and unassigns the tag from any contacts.
+ * Keyword matching is case insensitive.
+ */
+public class DeleteTagCommand extends Command {
+
+ public static final String COMMAND_WORD = "deltag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes tags that contain any of "
+ + "the specified keywords (case-insensitive), automatically unassigns the tag from any contacts "
+ + "and displays a list of contacts who had the tags unassigned from them."
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " friends colleague";
+
+ public static final String MESSAGE_TAG_NOT_EXIST = "One or more of the specified tag(s) does not exist: %s";
+ public static final String MESSAGE_DELETE_SUCCESS = "Deleted tags: %s";
+ public static final String MESSAGE_DELETE_FAIL = "All specified tag(s) do not exist.";
+
+ private final List keywords;
+
+ public DeleteTagCommand(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.clearActivatedTagList();
+ model.clearDetailedContactView();
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ ObservableList allPersons = model.getFilteredPersonList();
+
+ Set tagsToDelete = new HashSet<>();
+ Set tagsNotExist = new HashSet<>();
+
+ boolean hasUncreatedTags = false;
+
+ for (String keyword : keywords) {
+ Tag toDelete = new Tag(keyword);
+ if (!model.hasTag(toDelete)) {
+ hasUncreatedTags = true;
+ tagsNotExist.add(keyword);
+ continue;
+ }
+
+ for (Person person : allPersons) {
+ if (canRemoveTag(person, toDelete)) {
+ Person editedPerson = removeTagFromNewPerson(person, toDelete);
+ model.setPerson(person, editedPerson);
+ }
+ }
+
+ tagsToDelete.add(toDelete);
+ }
+
+ for (Tag tag : tagsToDelete) {
+ model.deleteTag(tag);
+ }
+
+
+
+ Set tagNamesToDelete = tagsToDelete.stream().map(tag -> tag.tagName).collect(Collectors.toSet());
+
+ if (hasUncreatedTags) {
+ if (tagNamesToDelete.size() >= 1) {
+ String result = MESSAGE_TAG_NOT_EXIST + "\n" + MESSAGE_DELETE_SUCCESS;
+ return new CommandResult(String.format(result, tagsNotExist, tagNamesToDelete));
+ } else {
+ throw new CommandException(MESSAGE_DELETE_FAIL);
+ }
+ }
+
+ return new CommandResult(String.format(MESSAGE_DELETE_SUCCESS, tagNamesToDelete));
+ }
+
+
+ /**
+ * Removes a specific {@code Tag} from the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to remove the tag from.
+ * @param newTag the tag to remove.
+ * @return the person with the removed tag.
+ */
+ private static Person removeTagFromNewPerson(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ Set tags = personToEdit.getTags();
+ Favourite favourite = personToEdit.getFavouriteStatus();
+ HighImportance highImportance = personToEdit.getHighImportanceStatus();
+ ImageDetailsList images = personToEdit.getImageDetailsList();
+
+ Set newTags = new HashSet<>(tags);
+ newTags.remove(newTag);
+
+ return new Person(name, phone, email, address, deadlines, notes, newTags, favourite, highImportance, images);
+ }
+
+ /**
+ * Checks if a specific {@code Tag} can be removed from the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to remove the tag from.
+ * @param newTag the tag to remove.
+ * @return boolean value of whether the tag can be removed.
+ */
+ private static boolean canRemoveTag(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+ Set tags = personToEdit.getTags();
+ Set newTags = new HashSet<>(tags);
+ return newTags.contains(newTag);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteTagCommand // instanceof handles nulls
+ && keywords.equals(((DeleteTagCommand) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DetailedViewExecutable.java b/src/main/java/seedu/address/logic/commands/DetailedViewExecutable.java
new file mode 100644
index 00000000000..e6a96a3aca1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DetailedViewExecutable.java
@@ -0,0 +1,21 @@
+package seedu.address.logic.commands;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Represents the ability to execute on Model when a Person is in DetailedContactView
+ */
+public interface DetailedViewExecutable {
+ /**
+ * Executes the command in the detailed view context and returns the result. Commands that
+ * execute with this method should operate on the Person in detailed view and return
+ * a {@code CommandResult} that has the {@code SpecialCommandResult} of {@code DETAILED_VIEW},
+ * unless it is a command that changes the view.
+ *
+ * @param model {@code} Model that the command should operate on.
+ * @return feedback message of the operation result to display.
+ * @throws CommandException If an error occurs during command execution.
+ */
+ CommandResult executeInDetailedView(Model model) throws CommandException;
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 7e36114902f..74adac553b0 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -2,15 +2,14 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE;
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.Objects;
import java.util.Optional;
import java.util.Set;
@@ -19,9 +18,14 @@
import seedu.address.commons.util.CollectionUtil;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -29,7 +33,7 @@
/**
* Edits the details of an existing person in the address book.
*/
-public class EditCommand extends Command {
+public class EditCommand extends Command implements DetailedViewExecutable {
public static final String COMMAND_WORD = "edit";
@@ -41,6 +45,7 @@ public class EditCommand extends Command {
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_DEADLINE + "DEADLINE] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
@@ -65,10 +70,20 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
}
+ /**
+ * Constructs an EditCommand for {@code Person} in detailed view
+ * @param editPersonDescriptor details to edit the person with
+ */
+ public EditCommand(EditPersonDescriptor editPersonDescriptor) {
+ requireNonNull(editPersonDescriptor);
+ this.index = null;
+ this.editPersonDescriptor = editPersonDescriptor;
+ }
+
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ List lastShownList = model.getSortedPersonList();
if (index.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
@@ -82,10 +97,26 @@ public CommandResult execute(Model model) throws CommandException {
}
model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
}
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Person personToEdit = model.getDetailedContactViewPerson();
+ Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+
+ if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
/**
* Creates and returns a {@code Person} with the details of {@code personToEdit}
* edited with {@code editPersonDescriptor}.
@@ -97,9 +128,16 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
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);
+ DeadlineList oldDeadlines = personToEdit.getDeadlines();
+ Notes oldNotes = personToEdit.getNotes();
+ Set oldTags = personToEdit.getTags();
+ Favourite favouriteStatus = personToEdit.getFavouriteStatus();
+ HighImportance highImportanceStatus = personToEdit.getHighImportanceStatus();
+ ImageDetailsList imageDetailsList = personToEdit.getImageDetailsList();
+
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, oldDeadlines,
+ oldNotes, oldTags, favouriteStatus, highImportanceStatus, imageDetailsList);
}
@Override
@@ -116,7 +154,7 @@ public boolean equals(Object other) {
// state check
EditCommand e = (EditCommand) other;
- return index.equals(e.index)
+ return Objects.equals(this.index, e.index)
&& editPersonDescriptor.equals(e.editPersonDescriptor);
}
@@ -129,7 +167,6 @@ public static class EditPersonDescriptor {
private Phone phone;
private Email email;
private Address address;
- private Set tags;
public EditPersonDescriptor() {}
@@ -142,14 +179,13 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
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);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address);
}
public void setName(Name name) {
@@ -184,23 +220,6 @@ 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
@@ -219,8 +238,7 @@ public boolean equals(Object other) {
return getName().equals(e.getName())
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
+ && getAddress().equals(e.getAddress());
}
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..dd2e910a5be 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -5,7 +5,7 @@
/**
* Terminates the program.
*/
-public class ExitCommand extends Command {
+public class ExitCommand extends Command implements DetailedViewExecutable {
public static final String COMMAND_WORD = "exit";
@@ -13,7 +13,11 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, CommandResult.SpecialCommandResult.EXIT);
}
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ return execute(model);
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/FavouriteCommand.java b/src/main/java/seedu/address/logic/commands/FavouriteCommand.java
new file mode 100644
index 00000000000..99bdd2a933d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FavouriteCommand.java
@@ -0,0 +1,122 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+public class FavouriteCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "fav";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Favourites 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_FAVOURITE_PERSON_SUCCESS = "Changed Person's Favourite Status: %1$s";
+
+ private final Index targetIndex;
+
+ /**
+ * @param targetIndex of the person in the filtered person list to favourite.
+ */
+ public FavouriteCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+
+ this.targetIndex = targetIndex;
+ }
+
+ public FavouriteCommand() {
+ this.targetIndex = null;
+ }
+
+ /**
+ * Executes the Favourite command based on the {@code targetIndex} given.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return the result of the command.
+ * @throws CommandException if the index given is out of bounds.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getSortedPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToFavourite = lastShownList.get(targetIndex.getZeroBased());
+ Favourite newFavouriteStatus = personToFavourite.isFavourite()
+ ? Favourite.NOT_FAVOURITE
+ : Favourite.IS_FAVOURITE;
+ Person editedPerson = createFavouritedPerson(personToFavourite, newFavouriteStatus);
+
+ model.setPerson(personToFavourite, editedPerson);
+ return new CommandResult(String.format(MESSAGE_FAVOURITE_PERSON_SUCCESS, editedPerson));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ requireNonNull(model);
+
+ Person personToFavourite = model.getDetailedContactViewPerson();
+ Favourite newFavouriteStatus = personToFavourite.isFavourite()
+ ? Favourite.NOT_FAVOURITE
+ : Favourite.IS_FAVOURITE;
+ Person editedPerson = createFavouritedPerson(personToFavourite, newFavouriteStatus);
+
+ model.setPerson(personToFavourite, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_FAVOURITE_PERSON_SUCCESS, editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ private static Person createFavouritedPerson(Person personToEdit, Favourite newFavouriteStatus) {
+ assert personToEdit != null;
+ assert newFavouriteStatus != null;
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ HighImportance highImportanceStatus = personToEdit.getHighImportanceStatus();
+ Set tags = personToEdit.getTags();
+ ImageDetailsList imageDetailsList = personToEdit.getImageDetailsList();
+
+ return new Person(name, phone, email, address, deadlines,
+ notes, tags, newFavouriteStatus, highImportanceStatus, imageDetailsList);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof FavouriteCommand)) {
+ return false;
+ }
+
+ FavouriteCommand e = (FavouriteCommand) other;
+ return Objects.equals(targetIndex, e.targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index d6b19b0a0de..91c06c43884 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -28,6 +28,8 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
+ model.clearDetailedContactView();
+ model.clearActivatedTagList();
model.updateFilteredPersonList(predicate);
return new CommandResult(
String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
diff --git a/src/main/java/seedu/address/logic/commands/FindTagCommand.java b/src/main/java/seedu/address/logic/commands/FindTagCommand.java
new file mode 100644
index 00000000000..fb9d3a4a5c6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindTagCommand.java
@@ -0,0 +1,86 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.TagContainsKeywordsPredicate;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Finds and lists all contacts in address book whose tag contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindTagCommand extends Command {
+
+ public static final String COMMAND_WORD = "findtag";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts whose tags 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 + " friends colleague";
+
+ public static final String TAG_NOT_EXIST = "One or more tags do not exist";
+
+ public static final String TAG_ALREADY_ACTIVATED = "One or more tags have already been selected";
+
+ private final List keywords;
+
+ public FindTagCommand(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.clearDetailedContactView();
+
+ boolean hasAllTag = true;
+ List tagsToAdd = new ArrayList<>();
+ for (String keyword : keywords) {
+ Tag tagToAdd = new Tag(keyword);
+ hasAllTag = hasAllTag && model.hasTag(new Tag(keyword));
+ boolean tagActivated = model.getActivatedTagList().contains(tagToAdd);
+
+ if (!hasAllTag) {
+ throw new CommandException(TAG_NOT_EXIST);
+ }
+
+ if (tagActivated) {
+ throw new CommandException(TAG_ALREADY_ACTIVATED);
+ }
+ tagsToAdd.add(tagToAdd);
+ }
+
+ for (Tag tag : tagsToAdd) {
+ model.addActivatedTag(tag);
+ }
+
+ if (keywords.size() == 0) {
+ throw new CommandException(MESSAGE_USAGE);
+ }
+ ObservableList activatedTagList = model.getActivatedTagList();
+
+ TagContainsKeywordsPredicate predicate =
+ new TagContainsKeywordsPredicate(activatedTagList.stream().map(tag -> tag.tagName)
+ .collect(Collectors.toList()));
+ 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 FindTagCommand // instanceof handles nulls
+ // && predicate.equals(((FindTagCommand) other).predicate)); // state check
+ && keywords.equals(((FindTagCommand) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..4c36823b7d2 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -5,7 +5,7 @@
/**
* Format full help instructions for every command for display.
*/
-public class HelpCommand extends Command {
+public class HelpCommand extends Command implements DetailedViewExecutable {
public static final String COMMAND_WORD = "help";
@@ -16,6 +16,11 @@ 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, CommandResult.SpecialCommandResult.SHOW_HELP);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ return execute(model);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/HighImportanceCommand.java b/src/main/java/seedu/address/logic/commands/HighImportanceCommand.java
new file mode 100644
index 00000000000..5cbe862fb2f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/HighImportanceCommand.java
@@ -0,0 +1,129 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+
+public class HighImportanceCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "impt";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Adds a high importance tag to 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_CHANGE_HIGH_IMPORTANCE_SUCCESS = "Changed Person's Importance Status: %1$s";
+
+ private final Index index;
+
+ /**
+ * @param index of the person in the filtered person list to favourite.
+ */
+ public HighImportanceCommand(Index index) {
+ requireNonNull(index);
+
+ this.index = index;
+ }
+
+ public HighImportanceCommand() {
+ this.index = null;
+ }
+
+ /**
+ * Executes the HighImportance command based on the {@code index} given.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return the result of the command.
+ * @throws CommandException if the index given is out of bounds.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getSortedPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person highImportancePerson = lastShownList.get(index.getZeroBased());
+ // Checks if person is already of high importance
+ HighImportance highImportanceStatus =
+ highImportancePerson.hasHighImportance()
+ ? HighImportance.NOT_HIGH_IMPORTANCE
+ : HighImportance.HIGH_IMPORTANCE;
+ Person editedPerson = createHighImportancePerson(highImportancePerson, highImportanceStatus);
+
+ model.setPerson(highImportancePerson, editedPerson);
+ return new CommandResult(String.format(MESSAGE_CHANGE_HIGH_IMPORTANCE_SUCCESS, editedPerson));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ requireNonNull(model);
+
+ Person highImportancePerson = model.getDetailedContactViewPerson();
+ // Checks if person is already of high importance
+ HighImportance highImportanceStatus =
+ highImportancePerson.hasHighImportance()
+ ? HighImportance.NOT_HIGH_IMPORTANCE
+ : HighImportance.HIGH_IMPORTANCE;
+ Person editedPerson = createHighImportancePerson(highImportancePerson, highImportanceStatus);
+
+ model.setPerson(highImportancePerson, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_CHANGE_HIGH_IMPORTANCE_SUCCESS, editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ private static Person createHighImportancePerson(Person highImportancePerson,
+ HighImportance newHighImportanceStatus) {
+ assert highImportancePerson != null;
+ assert newHighImportanceStatus != null;
+
+ Name name = highImportancePerson.getName();
+ Phone phone = highImportancePerson.getPhone();
+ Email email = highImportancePerson.getEmail();
+ Address address = highImportancePerson.getAddress();
+ DeadlineList deadlines = highImportancePerson.getDeadlines();
+ Notes notes = highImportancePerson.getNotes();
+ Favourite favouriteStatus = highImportancePerson.getFavouriteStatus();
+ Set tags = highImportancePerson.getTags();
+ ImageDetailsList images = highImportancePerson.getImageDetailsList();
+
+ return new Person(name, phone, email, address, deadlines,
+ notes, tags, favouriteStatus, newHighImportanceStatus, images);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof HighImportanceCommand)) {
+ return false;
+ }
+
+ HighImportanceCommand e = (HighImportanceCommand) other;
+ return Objects.equals(index, e.index);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ImagesCommand.java b/src/main/java/seedu/address/logic/commands/ImagesCommand.java
new file mode 100644
index 00000000000..d73a216c3b3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ImagesCommand.java
@@ -0,0 +1,152 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import seedu.address.MainApp;
+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.image.ImageDetailsList;
+import seedu.address.model.image.util.ImageUtil;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+public class ImagesCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "images";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows the images of 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\n";
+
+ public static final String MESSAGE_IMAGES_SUCCESS = "%d Images for Person [%s]:\n %s";
+
+ private static final Logger logger = Logger.getLogger(String.valueOf(MainApp.class));
+
+ private final Index index;
+
+ /**
+ * Creates the command to show all images of a person.
+ *
+ * @param index of the persons whose images are to be displayed.
+ */
+ public ImagesCommand(Index index) {
+ requireNonNull(index);
+ this.index = index;
+ }
+
+ public ImagesCommand() {
+ this.index = null;
+ }
+
+ /**
+ * Executes the command and returns the result message.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return feedback message of the operation result for display
+ * @throws CommandException If an error occurs during command execution.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(index);
+
+ List lastShownList = model.getSortedPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person targetPerson = lastShownList.get(index.getZeroBased());
+ logger.info(String.format("Sanitizing images of person at index %d, at file path: %s", index.getZeroBased(),
+ model.getContactImagesFilePath()));
+
+ ImageDetailsList originalList = targetPerson.getImageDetailsList();
+ ImageDetailsList sanitizedList = ImageUtil.sanitizeList(originalList, model.getContactImagesFilePath());
+ Person sanitizedPerson = createImageDeletedPerson(targetPerson, sanitizedList);
+ logger.info(String.format("Result of sanitization: %d -> %d", originalList.size(), sanitizedList.size()));
+
+ if (originalList.size() != sanitizedList.size()) {
+ model.setPerson(targetPerson, sanitizedPerson);
+ }
+
+ model.setImagesToView(sanitizedList);
+
+ String result = String.format(MESSAGE_IMAGES_SUCCESS, sanitizedList.size(),
+ index.getOneBased(), sanitizedList);
+ return new CommandResult(result, CommandResult.SpecialCommandResult.VIEW_IMAGES);
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ requireNonNull(model);
+
+ Person targetPerson = model.getDetailedContactViewPerson();
+ logger.info("Sanitizing images of person in detailed view");
+
+ ImageDetailsList originalList = targetPerson.getImageDetailsList();
+ ImageDetailsList sanitizedList = ImageUtil.sanitizeList(originalList, model.getContactImagesFilePath());
+ Person sanitizedPerson = createImageDeletedPerson(targetPerson, sanitizedList);
+ logger.info(String.format("Result of sanitization: %d -> %d", originalList.size(), sanitizedList.size()));
+
+ if (originalList.size() != sanitizedList.size()) {
+ model.setPerson(targetPerson, sanitizedPerson);
+ }
+
+ model.setImagesToView(sanitizedList);
+ model.clearDetailedContactView();
+
+ String result =
+ String.format(MESSAGE_IMAGES_SUCCESS, originalList.size(), targetPerson.getName(), sanitizedList);
+ return new CommandResult(result, CommandResult.SpecialCommandResult.VIEW_IMAGES);
+ }
+
+ private static Person createImageDeletedPerson(Person personToEdit, ImageDetailsList sanitizedList) {
+ assert personToEdit != null;
+ assert sanitizedList != null;
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ HighImportance highImportanceStatus = personToEdit.getHighImportanceStatus();
+ Favourite favouriteStatus = personToEdit.getFavouriteStatus();
+ Set tags = personToEdit.getTags();
+
+ return new Person(name, phone, email, address, deadlines,
+ notes, tags, favouriteStatus, highImportanceStatus, sanitizedList);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ImagesCommand)) {
+ return false;
+ }
+
+ ImagesCommand e = (ImagesCommand) other;
+
+ return Objects.equals(this.index, e.index);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..4d6b184f10b 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -8,17 +8,28 @@
/**
* Lists all persons in the address book to the user.
*/
-public class ListCommand extends Command {
+public class ListCommand extends Command implements DetailedViewExecutable {
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.clearDetailedContactView();
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.clearActivatedTagList();
return new CommandResult(MESSAGE_SUCCESS);
}
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ return execute(model);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ListCommand;
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java b/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java
new file mode 100644
index 00000000000..4555a03dd44
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListFavouritesCommand.java
@@ -0,0 +1,37 @@
+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.PersonIsFavouriteContactPredicate;
+
+/**
+ * Finds and lists all persons in address book who are favourite contacts.
+ */
+public class ListFavouritesCommand extends Command {
+
+ public static final String COMMAND_WORD = "favourites";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all person who have been set as a "
+ + "favourite contact and displays them as a list with index numbers.\n"
+ + "Example: " + COMMAND_WORD;
+
+ private static final PersonIsFavouriteContactPredicate predicate = new PersonIsFavouriteContactPredicate();
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.clearDetailedContactView();
+ model.clearActivatedTagList();
+ 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 ListFavouritesCommand); // instanceof handles nulls
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListImportantCommand.java b/src/main/java/seedu/address/logic/commands/ListImportantCommand.java
new file mode 100644
index 00000000000..b4cb3124eb2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListImportantCommand.java
@@ -0,0 +1,35 @@
+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.PersonHasHighImportancePredicate;
+
+/**
+ * Finds and lists all persons in address book who are of high importance contacts.
+ */
+public class ListImportantCommand extends Command {
+
+ public static final String COMMAND_WORD = "impts";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts who have been set as a "
+ + "contact with high importance and displays them as a list with index numbers.\n";
+
+ private static final PersonHasHighImportancePredicate predicate = new PersonHasHighImportancePredicate();
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.clearActivatedTagList();
+ 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 ListImportantCommand); // instanceof handles nulls
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/NoteCommand.java b/src/main/java/seedu/address/logic/commands/NoteCommand.java
new file mode 100644
index 00000000000..e179ce32528
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/NoteCommand.java
@@ -0,0 +1,136 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+
+import java.util.List;
+import java.util.Objects;
+
+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.Notes;
+import seedu.address.model.person.Person;
+
+
+/**
+ * Updates the notes of a person
+ */
+public class NoteCommand extends Command implements DetailedViewExecutable {
+ public static final String COMMAND_WORD = "note";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Adds the given note to the person identified "
+ + "by the index number used in the last person listing.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_NOTE + "NOTES\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_NOTE + "Likes to swim.";
+
+ public static final String MESSAGE_UPDATE_NOTE_SUCCESS = "Added note to Person: %1$s";
+
+ public static final String MESSAGE_NO_UPDATE_TO_NOTES = "No note added to Person: %1$s";
+
+ private final Index index;
+ private final String note;
+
+ /**
+ * @param index index of the person in the filtered list to edit
+ * @param note new note to update with
+ */
+ public NoteCommand(Index index, String note) {
+ requireAllNonNull(index, note);
+
+ this.index = index;
+ this.note = note;
+ }
+
+ /**
+ * Constructs NoteCommand without Index that operates on Person in detailed view.
+ * @param note new note to update with
+ */
+ public NoteCommand(String note) {
+ requireNonNull(note);
+
+ this.index = null;
+ this.note = note;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getSortedPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ if (!Notes.isValidNote(note)) {
+ return new CommandResult(generateNoChangeMessage(personToEdit));
+ }
+ Person editedPerson = updateNotes(personToEdit, note);
+ model.setPerson(personToEdit, editedPerson);
+ return new CommandResult(generateSuccessMessage(editedPerson));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) {
+ requireNonNull(model);
+ Person personToEdit = model.getDetailedContactViewPerson();
+ if (!Notes.isValidNote(note)) {
+ return new CommandResult(generateNoChangeMessage(personToEdit),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+ Person editedPerson = updateNotes(personToEdit, note);
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(generateSuccessMessage(editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ /**
+ * Creates the message to be printed for the result of this NoteCommand
+ * @param personToEdit the person edited by this NoteCommand
+ * @return message to be printed
+ */
+ public String generateSuccessMessage(Person personToEdit) {
+ return String.format(MESSAGE_UPDATE_NOTE_SUCCESS, personToEdit);
+ }
+
+ public String generateNoChangeMessage(Person person) {
+ return String.format(MESSAGE_NO_UPDATE_TO_NOTES, person);
+ }
+
+ /**
+ * Creates new Person object with the note added
+ * @param personToEdit person to add note to
+ * @param note note to add
+ * @return new Person with note added
+ */
+ private static Person updateNotes(Person personToEdit, String note) {
+ Notes oldNotes = personToEdit.getNotes();
+ Notes newNotes = oldNotes.updateNotes(note);
+ return new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
+ personToEdit.getAddress(), personToEdit.getDeadlines(), newNotes,
+ personToEdit.getTags(), personToEdit.getFavouriteStatus(), personToEdit.getHighImportanceStatus(),
+ personToEdit.getImageDetailsList());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof NoteCommand)) {
+ return false;
+ }
+
+ NoteCommand e = (NoteCommand) other;
+ return Objects.equals(this.index, e.index)
+ && note.equals(e.note);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/SortsCommand.java b/src/main/java/seedu/address/logic/commands/SortsCommand.java
new file mode 100644
index 00000000000..664828f90af
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/SortsCommand.java
@@ -0,0 +1,83 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+
+/**
+ * Sorts list based on criteria given.
+ * Criteria matching is case sensitive.
+ */
+public class SortsCommand extends Command {
+
+ public static final String COMMAND_WORD = "sort";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts the list of people by specified "
+ + "criteria and displays them as a list with index numbers.\n"
+ + "Parameters: CRITERIA\n"
+ + "Example: " + COMMAND_WORD + " name\n"
+ + "Valid criteria: %1$s";
+
+ private final String criteria;
+
+ public SortsCommand(String criteria) {
+ this.criteria = criteria;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ switch (criteria) {
+
+ case Name.MODEL_NAME:
+ model.sortFilteredPersonListByName();
+ break;
+
+ case Address.MODEL_NAME:
+ model.sortFilteredPersonListByAddress();
+ break;
+
+ case DeadlineList.MODEL_NAME:
+ model.sortFilteredPersonListByDeadlineList();
+ break;
+
+ case Email.MODEL_NAME:
+ model.sortFilteredPersonListByEmail();
+ break;
+
+ case Phone.MODEL_NAME:
+ model.sortFilteredPersonListByPhone();
+ break;
+
+ case Favourite.MODEL_NAME:
+ model.sortFilteredPersonListByFavourite();
+ break;
+
+ case HighImportance.MODEL_NAME:
+ model.sortFilteredPersonListByHighImportance();
+ break;
+
+ default:
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_CRITERIA, criteria));
+ }
+ return new CommandResult(
+ String.format(Messages.MESSAGE_SORTED, criteria));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SortsCommand // instanceof handles nulls
+ && criteria.equals(((SortsCommand) other).criteria)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnassignTagCommand.java b/src/main/java/seedu/address/logic/commands/UnassignTagCommand.java
new file mode 100644
index 00000000000..17033aa7384
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnassignTagCommand.java
@@ -0,0 +1,170 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Unassigns a tag to a contact in the address book.
+ */
+public class UnassignTagCommand extends Command implements DetailedViewExecutable {
+
+ public static final String COMMAND_WORD = "unassign";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Unassigns a tag with a given tag name "
+ + "(case-insensitive) to a contact identified by a given index number used in the displayed "
+ + "contacts list.\n"
+ + "Parameters: INDEX (must be a positive integer), TAGNAME"
+ + "Example: " + COMMAND_WORD + " 1 friends";
+
+ public static final String MESSAGE_SUCCESS = "Tag successfully unassigned: %1$s";
+ public static final String MESSAGE_NOT_TAGGED = "This tag was not assigned to this person previously";
+ public static final String MESSAGE_UNKNOWN_TAG = "Tag '%1$s' has not been created yet.";
+
+ private final String tagName;
+ private final Index targetIndex;
+
+ /**
+ * Creates an UnassignTagCommand to un-assign a {@code Tag} from a {@code Person}.
+ * @param targetIndex the index of the contact specified.
+ * @param tagName the name of the Tag.
+ */
+ public UnassignTagCommand(Index targetIndex, String tagName) {
+ this.tagName = tagName;
+ this.targetIndex = targetIndex;
+ }
+
+ /**
+ * Creates an UnassignTagCommand to un-assign a {@code Tag} from the {@code Person}
+ * in detailed view.
+ * @param tagName the name of the Tag.
+ */
+ public UnassignTagCommand(String tagName) {
+ this.tagName = tagName;
+ this.targetIndex = null;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag newTag = new Tag(tagName);
+ boolean tagHasBeenCreated = model.hasTag(newTag);
+
+ if (!tagHasBeenCreated) {
+ throw new CommandException(String.format(MESSAGE_UNKNOWN_TAG, tagName));
+ }
+
+ List lastShownList = model.getSortedPersonList();
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(targetIndex.getZeroBased());
+
+ if (!canRemoveTag(personToEdit, newTag)) {
+ throw new CommandException(MESSAGE_NOT_TAGGED);
+ }
+
+ Person editedPerson = removeTagFromNewPerson(personToEdit, newTag);
+ model.setPerson(personToEdit, editedPerson);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, editedPerson));
+ }
+
+ @Override
+ public CommandResult executeInDetailedView(Model model) throws CommandException {
+ requireNonNull(model);
+ Tag newTag = new Tag(tagName);
+ boolean tagHasBeenCreated = model.hasTag(newTag);
+
+ if (!tagHasBeenCreated) {
+ throw new CommandException(String.format(MESSAGE_UNKNOWN_TAG, tagName));
+ }
+
+ Person personToEdit = model.getDetailedContactViewPerson();
+
+ if (!canRemoveTag(personToEdit, newTag)) {
+ throw new CommandException(MESSAGE_NOT_TAGGED);
+ }
+
+ Person editedPerson = removeTagFromNewPerson(personToEdit, newTag);
+ model.setPerson(personToEdit, editedPerson);
+ model.setDetailedContactView(editedPerson);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, editedPerson),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ /**
+ * Removes a specific {@code Tag} from the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to remove the tag from.
+ * @param newTag the tag to remove.
+ * @return the person with the removed tag.
+ */
+ private static Person removeTagFromNewPerson(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+
+ Name name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ DeadlineList deadlines = personToEdit.getDeadlines();
+ Notes notes = personToEdit.getNotes();
+ Set tags = personToEdit.getTags();
+ Favourite favourite = personToEdit.getFavouriteStatus();
+ HighImportance highImportance = personToEdit.getHighImportanceStatus();
+ ImageDetailsList images = personToEdit.getImageDetailsList();
+
+ Set newTags = new HashSet<>(tags);
+ newTags.remove(newTag);
+
+ return new Person(name, phone, email, address, deadlines, notes, newTags, favourite, highImportance, images);
+ }
+
+ /**
+ * Checks if a specific {@code Tag} can be removed from the {@code Set} of a {@code Person}.
+ * @param personToEdit the person to remove the tag from.
+ * @param newTag the tag to remove.
+ * @return boolean value of whether the tag can be removed.
+ */
+ private static boolean canRemoveTag(Person personToEdit, Tag newTag) {
+ requireNonNull(personToEdit);
+ requireNonNull(newTag);
+ Set tags = personToEdit.getTags();
+ Set newTags = new HashSet<>(tags);
+ return newTags.contains(newTag);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof UnassignTagCommand)) {
+ return false;
+ }
+
+ UnassignTagCommand e = (UnassignTagCommand) other;
+ return Objects.equals(targetIndex, e.targetIndex)
+ && this.tagName.equals(e.tagName);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java
new file mode 100644
index 00000000000..c98df9c1d2b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java
@@ -0,0 +1,60 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Objects;
+
+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;
+
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Opens a more detailed view of the contact 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 VIEW_SUCCESS = "Viewing %1$s";
+
+ private final Index index;
+
+ public ViewCommand(Index index) {
+ this.index = index;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ List lastShownList = model.getSortedPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToView = lastShownList.get(index.getZeroBased());
+ model.setDetailedContactView(personToView);
+ return new CommandResult(String.format(VIEW_SUCCESS, personToView.getName().fullName),
+ CommandResult.SpecialCommandResult.DETAILED_VIEW);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ViewCommand)) {
+ return false;
+ }
+
+ ViewCommand e = (ViewCommand) other;
+ return Objects.equals(this.index, e.index);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 3b8bfa035e8..a63cfc31961 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -7,14 +7,20 @@
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.Optional;
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.image.ImageDetailsList;
import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -33,7 +39,7 @@ 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)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
@@ -41,10 +47,22 @@ public AddCommand parse(String args) throws ParseException {
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);
+ Optional optionalAddress = argMultimap.getValue(PREFIX_ADDRESS);
+ String addressString = "";
+ Address address = Address.EMPTY_ADDRESS;
+ if (optionalAddress.isPresent()) {
+ addressString = optionalAddress.get();
+ address = ParserUtil.parseAddress(addressString);
+ }
+
+ DeadlineList deadlines = new DeadlineList();
+ Notes notes = Notes.getNewNotes();
+ ImageDetailsList emptyImageList = new ImageDetailsList();
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Person person = new Person(name, phone, email, address, deadlines, notes,
+ tagList, Favourite.NOT_FAVOURITE, HighImportance.NOT_HIGH_IMPORTANCE,
+ emptyImageList);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddImageCommandParser.java b/src/main/java/seedu/address/logic/parser/AddImageCommandParser.java
new file mode 100644
index 00000000000..c8c5e6d4e06
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddImageCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddImageCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new AddImageCommand object.
+ */
+public class AddImageCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddImageCommand
+ * and returns a AddImageCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public AddImageCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new AddImageCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddImageCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..19fc666af5e 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -1,5 +1,6 @@
package seedu.address.logic.parser;
+import static seedu.address.commons.core.Messages.MESSAGE_INCOMPATIBLE_VIEW_MODE;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
@@ -7,14 +8,33 @@
import java.util.regex.Pattern;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddImageCommand;
+import seedu.address.logic.commands.AssignTagCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CreateTagCommand;
+import seedu.address.logic.commands.DeadlineCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteDeadlineCommand;
+import seedu.address.logic.commands.DeleteImageCommand;
+import seedu.address.logic.commands.DeleteNoteCommand;
+import seedu.address.logic.commands.DeleteTagCommand;
+import seedu.address.logic.commands.DetailedViewExecutable;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
+import seedu.address.logic.commands.FavouriteCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindTagCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.HighImportanceCommand;
+import seedu.address.logic.commands.ImagesCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListFavouritesCommand;
+import seedu.address.logic.commands.ListImportantCommand;
+import seedu.address.logic.commands.NoteCommand;
+import seedu.address.logic.commands.SortsCommand;
+import seedu.address.logic.commands.UnassignTagCommand;
+import seedu.address.logic.commands.ViewCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -68,6 +88,151 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case NoteCommand.COMMAND_WORD:
+ return new NoteCommandParser().parse(arguments);
+
+ case FavouriteCommand.COMMAND_WORD:
+ return new FavouriteCommandParser().parse(arguments);
+
+ case ListFavouritesCommand.COMMAND_WORD:
+ return new ListFavouritesCommand();
+
+ case FindTagCommand.COMMAND_WORD:
+ return new FindTagCommandParser().parse(arguments);
+
+ case DeadlineCommand.COMMAND_WORD:
+ return new DeadlineCommandParser().parse(arguments);
+
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
+ case HighImportanceCommand.COMMAND_WORD:
+ return new HighImportanceCommandParser().parse(arguments);
+
+ case ListImportantCommand.COMMAND_WORD:
+ return new ListImportantCommand();
+
+ case CreateTagCommand.COMMAND_WORD:
+ return new CreateTagCommandParser().parse(arguments);
+
+ case SortsCommand.COMMAND_WORD:
+ return new SortCommandParser().parse(arguments);
+
+ case AssignTagCommand.COMMAND_WORD:
+ return new AssignTagCommandParser().parse(arguments);
+
+ case UnassignTagCommand.COMMAND_WORD:
+ return new UnassignTagCommandParser().parse(arguments);
+
+ case AddImageCommand.COMMAND_WORD:
+ return new AddImageCommandParser().parse(arguments);
+
+ case ImagesCommand.COMMAND_WORD:
+ return new ImagesCommandParser().parse(arguments);
+
+ case DeleteImageCommand.COMMAND_WORD:
+ return new DeleteImageCommandParser().parse(arguments);
+
+ case DeleteTagCommand.COMMAND_WORD:
+ return new DeleteTagCommandParser().parse(arguments);
+
+ case DeleteDeadlineCommand.COMMAND_WORD:
+ // Fallthrough
+ case DeleteNoteCommand.COMMAND_WORD:
+ throw new ParseException(MESSAGE_INCOMPATIBLE_VIEW_MODE);
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+ /**
+ * Parses user input for a command to execute in detailed view mode.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DetailedViewExecutable parseDetailedViewCommand(String userInput) throws ParseException {
+ final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+
+ final String commandWord = matcher.group("commandWord");
+ final String arguments = matcher.group("arguments");
+
+ switch (commandWord) {
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+
+ case DeadlineCommand.COMMAND_WORD:
+ return new DeadlineCommandParser().parseInDetailedViewContext(arguments);
+
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parseInDetailedViewContext(arguments);
+
+ case NoteCommand.COMMAND_WORD:
+ return new NoteCommandParser().parseInDetailedViewContext(arguments);
+
+ case FavouriteCommand.COMMAND_WORD:
+ return new FavouriteCommand();
+
+ case HighImportanceCommand.COMMAND_WORD:
+ return new HighImportanceCommand();
+
+ case CreateTagCommand.COMMAND_WORD:
+ return new CreateTagCommandParser().parse(arguments);
+
+ case AssignTagCommand.COMMAND_WORD:
+ return new AssignTagCommandParser().parseInDetailedViewContext(arguments);
+
+ case UnassignTagCommand.COMMAND_WORD:
+ return new UnassignTagCommandParser().parseInDetailedViewContext(arguments);
+
+ case DeleteNoteCommand.COMMAND_WORD:
+ return new DeleteNoteCommandParser().parseInDetailedViewContext(arguments);
+
+ case DeleteDeadlineCommand.COMMAND_WORD:
+ return new DeleteDeadlineCommandParser().parseInDetailedViewContext(arguments);
+
+ case AddImageCommand.COMMAND_WORD:
+ return new AddImageCommand();
+
+ case DeleteImageCommand.COMMAND_WORD:
+ return new DeleteImageCommandParser().parseInDetailedViewContext(arguments);
+
+ case ImagesCommand.COMMAND_WORD:
+ return new ImagesCommand();
+
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ case ClearCommand.COMMAND_WORD:
+ // Fallthrough
+ case DeleteCommand.COMMAND_WORD:
+ // Fallthrough
+ case DeleteTagCommand.COMMAND_WORD:
+ // Fallthrough
+ case FindCommand.COMMAND_WORD:
+ // Fallthrough
+ case FindTagCommand.COMMAND_WORD:
+ // Fallthrough
+ case ListFavouritesCommand.COMMAND_WORD:
+ // Fallthrough
+ case ListImportantCommand.COMMAND_WORD:
+ // Fallthrough
+ case SortsCommand.COMMAND_WORD:
+ // Fallthrough
+ case ViewCommand.COMMAND_WORD:
+ throw new ParseException(MESSAGE_INCOMPATIBLE_VIEW_MODE);
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/AssignTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AssignTagCommandParser.java
new file mode 100644
index 00000000000..38d19615379
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AssignTagCommandParser.java
@@ -0,0 +1,56 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AssignTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new AssignTagCommand object
+ */
+public class AssignTagCommandParser implements Parser,
+ DetailedViewExecutableParser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AssignTagCommand
+ * and returns a AssignTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AssignTagCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignTagCommand.MESSAGE_USAGE));
+ }
+
+ String[] inputs = trimmedArgs.split("\\s+");
+ if (inputs.length <= 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AssignTagCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(inputs[0]);
+ String tagName = inputs[1];
+ return new AssignTagCommand(index, tagName);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignTagCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ @Override
+ public AssignTagCommand parseInDetailedViewContext(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ String[] inputs = trimmedArgs.split("\\s+");
+ String tag = inputs[inputs.length - 1];
+
+ if (tag.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignTagCommand.MESSAGE_USAGE));
+ }
+
+ return new AssignTagCommand(tag);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..0c7ee9a6240 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -10,6 +10,9 @@ public class CliSyntax {
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_DEADLINE = new Prefix("d/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_NOTE = new Prefix("r/");
+ public static final Prefix PREFIX_IMAGE = new Prefix("i/");
}
diff --git a/src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java b/src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java
new file mode 100644
index 00000000000..a51d3909011
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/CreateTagCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.model.tag.Tag.VALIDATION_REGEX;
+
+import seedu.address.logic.commands.CreateTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new CreateTagCommand object
+ */
+public class CreateTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the CreateTagCommand
+ * and returns a CreateTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CreateTagCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE));
+ }
+
+ if (!trimmedArgs.matches(VALIDATION_REGEX)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateTagCommand.MESSAGE_USAGE));
+ }
+
+ return new CreateTagCommand(trimmedArgs);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/DeadlineCommandParser.java
new file mode 100644
index 00000000000..62adcce153b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeadlineCommandParser.java
@@ -0,0 +1,57 @@
+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_DEADLINE;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.DeadlineCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.DeadlineList;
+
+/**
+ * Parses input arguments and creates a new DeadlineCommand object
+ */
+public class DeadlineCommandParser implements Parser, DetailedViewExecutableParser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeadlineCommand
+ * and returns a DeadlineCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeadlineCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_DEADLINE);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ DeadlineCommand.MESSAGE_USAGE), ive);
+ }
+ DeadlineList deadlines = ParserUtil.parseDeadlines(argMultimap.getAllValues(PREFIX_DEADLINE));
+ if (deadlines.size() == 0) {
+ throw new ParseException(DeadlineCommand.MESSAGE_NO_DEADLINES_ADDED);
+ }
+
+ return new DeadlineCommand(index, deadlines);
+ }
+
+ @Override
+ public DeadlineCommand parseInDetailedViewContext(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_DEADLINE);
+
+ DeadlineList deadlines = ParserUtil.parseDeadlines(argMultimap.getAllValues(PREFIX_DEADLINE));
+ if (deadlines.size() == 0) {
+ throw new ParseException(DeadlineCommand.MESSAGE_NO_DEADLINES_ADDED);
+ }
+
+ return new DeadlineCommand(deadlines);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteDeadlineCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteDeadlineCommandParser.java
new file mode 100644
index 00000000000..447f7455a5c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteDeadlineCommandParser.java
@@ -0,0 +1,21 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteDeadlineCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class DeleteDeadlineCommandParser implements DetailedViewExecutableParser {
+
+ @Override
+ public DeleteDeadlineCommand parseInDetailedViewContext(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteDeadlineCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteDeadlineCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteImageCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteImageCommandParser.java
new file mode 100644
index 00000000000..0364dfe97a9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteImageCommandParser.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_IMAGE;
+
+import java.util.stream.Stream;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteImageCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class DeleteImageCommandParser implements Parser,
+ DetailedViewExecutableParser {
+
+ /**
+ * Parses {@code args} into a command and returns it.
+ *
+ * @param args the user input to parse
+ * @throws ParseException if {@code userInput} does not conform the expected format
+ */
+ @Override
+ public DeleteImageCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_IMAGE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_IMAGE) || argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteImageCommand.MESSAGE_USAGE));
+ }
+
+ Index personIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
+ Index imageIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_IMAGE).get());
+
+ return new DeleteImageCommand(personIndex, imageIndex);
+ }
+
+ @Override
+ public DeleteImageCommand parseInDetailedViewContext(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_IMAGE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_IMAGE)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteImageCommand.MESSAGE_USAGE));
+ }
+
+ Index imageIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_IMAGE).get());
+
+ return new DeleteImageCommand(imageIndex);
+ }
+
+ /**
+ * 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/DeleteNoteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteNoteCommandParser.java
new file mode 100644
index 00000000000..02bc16c0eba
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteNoteCommandParser.java
@@ -0,0 +1,21 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteNoteCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class DeleteNoteCommandParser implements DetailedViewExecutableParser {
+
+ @Override
+ public DeleteNoteCommand parseInDetailedViewContext(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteNoteCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteNoteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
new file mode 100644
index 00000000000..228a5472f01
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTagCommandParser.java
@@ -0,0 +1,41 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.model.tag.Tag.VALIDATION_REGEX;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.DeleteTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteTagCommand object
+ */
+public class DeleteTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a DeleteTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteTagCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+ boolean isValidArg = true;
+ for (String keyword : nameKeywords) {
+ isValidArg = isValidArg && keyword.matches(VALIDATION_REGEX);
+ }
+
+ if (!isValidArg) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTagCommand.MESSAGE_USAGE));
+ }
+
+ return new DeleteTagCommand(Arrays.asList(nameKeywords));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/DetailedViewExecutableParser.java b/src/main/java/seedu/address/logic/parser/DetailedViewExecutableParser.java
new file mode 100644
index 00000000000..0b4bef927bd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DetailedViewExecutableParser.java
@@ -0,0 +1,18 @@
+package seedu.address.logic.parser;
+
+import seedu.address.logic.commands.DetailedViewExecutable;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Represents a Parser that is able to parse user input to create a
+ * {@code DetailViewExecutable} of type {@code T}.
+ */
+public interface DetailedViewExecutableParser {
+
+ /**
+ * Parses {@code userInput} into a command that can execute in detailed view mode
+ * and returns it.
+ * @throws ParseException if {@code userInput} does not conform the expected format
+ */
+ T parseInDetailedViewContext(String userInput) throws ParseException;
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..f4941cc445b 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -6,23 +6,16 @@
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 {
+public class EditCommandParser implements Parser, DetailedViewExecutableParser {
/**
* Parses the given {@code String} of arguments in the context of the EditCommand
@@ -32,7 +25,7 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
Index index;
@@ -55,7 +48,6 @@ public EditCommand parse(String args) throws ParseException {
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);
@@ -64,19 +56,31 @@ public EditCommand parse(String args) throws ParseException {
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;
+ @Override
+ public EditCommand parseInDetailedViewContext(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+
+ 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()));
+ }
- if (tags.isEmpty()) {
- return Optional.empty();
+ if (!editPersonDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
+
+ return new EditCommand(editPersonDescriptor);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java b/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java
new file mode 100644
index 00000000000..f3aa22b6bf6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FavouriteCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.FavouriteCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class FavouriteCommandParser implements Parser {
+
+ /**
+ * Parses {@code args} into a FavouriteCommand and returns it.
+ *
+ * @param args Index of the person to favourite.
+ * @throws ParseException if {@code userInput} does not conform the expected format
+ */
+ @Override
+ public FavouriteCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new FavouriteCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FavouriteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindTagCommandParser.java b/src/main/java/seedu/address/logic/parser/FindTagCommandParser.java
new file mode 100644
index 00000000000..ea968fff9c8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindTagCommandParser.java
@@ -0,0 +1,32 @@
+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.FindTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new FindTagCommand object
+ */
+public class FindTagCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a FindTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindTagCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTagCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ return new FindTagCommand(Arrays.asList(nameKeywords));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/HighImportanceCommandParser.java b/src/main/java/seedu/address/logic/parser/HighImportanceCommandParser.java
new file mode 100644
index 00000000000..38f12da583c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/HighImportanceCommandParser.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.HighImportanceCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class HighImportanceCommandParser implements Parser {
+ /**
+ * Parses {@code args} into a FavouriteCommand and returns it.
+ *
+ * @param args Index of the person to favourite.
+ * @throws ParseException if {@code userInput} does not conform the expected format
+ */
+ @Override
+ public HighImportanceCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new HighImportanceCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, HighImportanceCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ImagesCommandParser.java b/src/main/java/seedu/address/logic/parser/ImagesCommandParser.java
new file mode 100644
index 00000000000..471aef9cd07
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ImagesCommandParser.java
@@ -0,0 +1,26 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ImagesCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class ImagesCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ImagesCommand
+ * and returns a ImagesCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ImagesCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ImagesCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImagesCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/NoteCommandParser.java b/src/main/java/seedu/address/logic/parser/NoteCommandParser.java
new file mode 100644
index 00000000000..3b8555a90f5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/NoteCommandParser.java
@@ -0,0 +1,41 @@
+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_NOTE;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.NoteCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class NoteCommandParser implements Parser, DetailedViewExecutableParser {
+ @Override
+ public NoteCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_NOTE);
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ NoteCommand.MESSAGE_USAGE), ive);
+ }
+
+ String note = argMultimap.getValue(PREFIX_NOTE).orElse("");
+
+ return new NoteCommand(index, note);
+ }
+
+ @Override
+ public NoteCommand parseInDetailedViewContext(String args) {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
+ PREFIX_NOTE);
+ String note = argMultimap.getValue(PREFIX_NOTE).orElse("");
+
+ return new NoteCommand(note);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..92c4210247a 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -10,6 +11,8 @@
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Deadline;
+import seedu.address.model.person.DeadlineList;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
@@ -25,6 +28,7 @@ public class ParserUtil {
/**
* Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
* trimmed.
+ *
* @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
*/
public static Index parseIndex(String oneBasedIndex) throws ParseException {
@@ -95,6 +99,54 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String deadline} into a {@code Deadline}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @param deadline the date of the deadline.
+ * @return the resulting {@code Deadline} object.
+ * @throws ParseException if the given {@code deadline} is invalid.
+ */
+ public static Deadline parseDeadline(String deadline) throws ParseException {
+ requireNonNull(deadline);
+ String trimmedDeadline = deadline.trim();
+ StringBuilder originalInput = new StringBuilder(trimmedDeadline);
+ originalInput.reverse();
+ String[] reversedAndSplit = originalInput.toString().split("\\s+", 2);
+ if (reversedAndSplit.length < 2) {
+ throw new ParseException(Deadline.MESSAGE_CONSTRAINTS);
+ }
+ String description = new StringBuilder(reversedAndSplit[1]).reverse().toString();
+ String date = new StringBuilder(reversedAndSplit[0]).reverse().toString();
+ if (!Deadline.isValidDeadline(description, date)) {
+ throw new ParseException(Deadline.MESSAGE_CONSTRAINTS);
+ }
+
+ return new Deadline(description, date);
+ }
+
+ /**
+ * parses {@code String deadlines} into a {@code DeadlineList}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @param deadlines the string representing deadline/s
+ * @return the resulting {@code DeadlineList} object
+ * @throws ParseException if one of the given {@code deadlines} is invalid.
+ */
+ public static DeadlineList parseDeadlines(Collection deadlines) throws ParseException {
+ requireNonNull(deadlines);
+ ArrayList deadlineList = new ArrayList<>();
+
+ if (deadlines.size() < 1) {
+ deadlineList.add(new Deadline());
+ }
+
+ for (String deadline : deadlines) {
+ deadlineList.add(parseDeadline(deadline));
+ }
+ return new DeadlineList(deadlineList);
+ }
+
/**
* 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/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java
new file mode 100644
index 00000000000..64a7b8d6425
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java
@@ -0,0 +1,72 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_CRITERIA;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.SortsCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.DeadlineList;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+
+/**
+ * Parses input arguments and creates a new SortsCommand object
+ */
+public class SortCommandParser implements Parser {
+ public static final String[] MODEL_NAMES = {
+ Name.MODEL_NAME,
+ Phone.MODEL_NAME,
+ Email.MODEL_NAME,
+ Address.MODEL_NAME,
+ DeadlineList.MODEL_NAME,
+ Favourite.MODEL_NAME,
+ HighImportance.MODEL_NAME
+ };
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the SortsCommand
+ * and returns a SortsCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SortsCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ String.format(SortsCommand.MESSAGE_USAGE, criteriaListForUsageMessage())));
+ }
+
+ if (!Arrays.asList(MODEL_NAMES).contains(trimmedArgs)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_CRITERIA, trimmedArgs));
+ }
+
+ return new SortsCommand(trimmedArgs);
+ }
+
+ /**
+ * Creates a list of criteria to be printed
+ * @return the String representing a list of valid criteria
+ */
+ public static String criteriaListForUsageMessage() {
+ String result = "";
+
+ for (int i = 0; i < MODEL_NAMES.length; i++) {
+ if (i == MODEL_NAMES.length - 1) {
+ result += MODEL_NAMES[i];
+ break;
+ }
+
+ result += MODEL_NAMES[i] + ", ";
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/UnassignTagCommandParser.java b/src/main/java/seedu/address/logic/parser/UnassignTagCommandParser.java
new file mode 100644
index 00000000000..87364a5811f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnassignTagCommandParser.java
@@ -0,0 +1,56 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnassignTagCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UnassignTagCommand object
+ */
+public class UnassignTagCommandParser implements Parser,
+ DetailedViewExecutableParser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnassignTagCommand
+ * and returns a UnassignTagCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnassignTagCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignTagCommand.MESSAGE_USAGE));
+ }
+
+ String[] inputs = trimmedArgs.split("\\s+");
+ if (inputs.length <= 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ UnassignTagCommand.MESSAGE_USAGE));
+ }
+
+ try {
+ Index index = ParserUtil.parseIndex(inputs[0]);
+ String tagName = inputs[1];
+ return new UnassignTagCommand(index, tagName);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignTagCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ @Override
+ public UnassignTagCommand parseInDetailedViewContext(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ String[] inputs = trimmedArgs.split("\\s+");
+ String tag = inputs[inputs.length - 1];
+
+ if (tag.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignTagCommand.MESSAGE_USAGE));
+ }
+
+ return new UnassignTagCommand(tag);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
new file mode 100644
index 00000000000..bde521e7d2f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ViewCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class ViewCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ViewCommand
+ * and returns a ViewCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.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..36354013bf9 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -3,10 +3,14 @@
import static java.util.Objects.requireNonNull;
import java.util.List;
+import java.util.Objects;
import javafx.collections.ObservableList;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.tag.ActivatedTagList;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.tag.UniqueTagList;
/**
* Wraps all data at the address-book level
@@ -15,6 +19,8 @@
public class AddressBook implements ReadOnlyAddressBook {
private final UniquePersonList persons;
+ private final UniqueTagList tags;
+ private final ActivatedTagList activatedTags;
/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
@@ -25,6 +31,8 @@ public class AddressBook implements ReadOnlyAddressBook {
*/
{
persons = new UniquePersonList();
+ tags = new UniqueTagList();
+ activatedTags = new ActivatedTagList();
}
public AddressBook() {}
@@ -47,6 +55,14 @@ public void setPersons(List persons) {
this.persons.setPersons(persons);
}
+ /**
+ * Replaces the contents of the tag list with {@code tags}.
+ * {@code tags} must not contain duplicate tags.
+ */
+ public void setTags(List tags) {
+ this.tags.setTags(tags);
+ }
+
/**
* Resets the existing data of this {@code AddressBook} with {@code newData}.
*/
@@ -54,6 +70,7 @@ public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
setPersons(newData.getPersonList());
+ setTags(newData.getTagList());
}
//// person-level operations
@@ -93,6 +110,49 @@ public void removePerson(Person key) {
persons.remove(key);
}
+ /**
+ * Returns true if a tag with the same identity as {@code tag} exists in the address book.
+ */
+ public boolean hasTag(Tag tag) {
+ requireNonNull(tag);
+ return tags.contains(tag);
+ }
+
+ /**
+ * Adds a tag to the address book.
+ * The tag must not already exist in the address book.
+ */
+ public void addTag(Tag p) {
+ tags.add(p);
+ }
+
+ /**
+ * Replaces the given tag {@code target} in the list with {@code editedTag}.
+ * {@code target} must exist in the address book.
+ * The tag identity of {@code editedTag} must not be the same as another existing tag in the address book.
+ */
+ public void setTag(Tag target, Tag editedTag) {
+ requireNonNull(editedTag);
+
+ tags.setTag(target, editedTag);
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeTag(Tag key) {
+ tags.remove(key);
+ }
+
+ public void addActivatedTag(Tag tag) {
+ activatedTags.add(tag);
+ }
+
+ public void clearActivatedTagList() {
+ activatedTags.clear();
+ }
+
//// util methods
@Override
@@ -106,15 +166,27 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ @Override
+ public ObservableList getTagList() {
+ return tags.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public ObservableList getActivatedTagList() {
+ return activatedTags.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));
+ && persons.equals(((AddressBook) other).persons)
+ && tags.equals(((AddressBook) other).tags))
+ && activatedTags.equals(((AddressBook) other).activatedTags);
}
@Override
public int hashCode() {
- return persons.hashCode();
+ return Objects.hash(persons, tags);
}
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..7cae5f713b0 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -4,16 +4,25 @@
import java.util.function.Predicate;
import javafx.collections.ObservableList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.commandhistory.CommandHistoryEntry;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* The API of the Model component.
*/
public interface Model {
- /** {@code Predicate} that always evaluate to true */
+ /**
+ * {@code Predicate} that always evaluate to true
+ */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ /** {@code Predicate} that always evaluates to false */
+ Predicate PREDICATE_HIDE_ALL_PERSONS = unused -> false;
+
/**
* Replaces user prefs data with the data in {@code userPrefs}.
*/
@@ -44,12 +53,24 @@ public interface Model {
*/
void setAddressBookFilePath(Path addressBookFilePath);
+ /**
+ * Returns the user prefs' contact images file path.
+ */
+ Path getContactImagesFilePath();
+
+ /**
+ * Sets the user prefs' contact images file path.
+ */
+ void setContactImagesFilePath(Path contactImagesFilePath);
+
/**
* Replaces address book data with the data in {@code addressBook}.
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
- /** Returns the AddressBook */
+ /**
+ * Returns the AddressBook
+ */
ReadOnlyAddressBook getAddressBook();
/**
@@ -76,12 +97,111 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
- /** Returns an unmodifiable view of the filtered person list */
+ /**
+ * Returns true if a person with the same identity as {@code tag} exists in the address book.
+ */
+ boolean hasTag(Tag tag);
+
+ /**
+ * Adds the given tag
+ * {@code tag} must not already exist in the address book.
+ */
+ void addTag(Tag tag);
+
+ /**
+ * Replaces the given tag {@code target} with {@code editedTag}.
+ * {@code target} must exist in the address book.
+ * The tag identity of {@code editedTag} must not be the same as another existing tag in the address book.
+ */
+ void setTag(Tag target, Tag editedTag);
+
+ /**
+ * Deletes the given tag.
+ * The tag must exist in the address book.
+ */
+ void deleteTag(Tag target);
+
+ /**
+ * Adds the given tag to the {@code ActivatedTagList}
+ * {@code tag} must already exist in the address book.
+ */
+ void addActivatedTag(Tag tag);
+
+ /**
+ * Returns an unmodifiable view of the filtered person list
+ */
ObservableList getFilteredPersonList();
+ /**
+ * Returns an unmodifiable view of the activated tag list
+ */
+ ObservableList getActivatedTagList();
+
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ *
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ void sortFilteredPersonListByName();
+
+ void sortFilteredPersonListByAddress();
+
+ void sortFilteredPersonListByDeadlineList();
+
+ void sortFilteredPersonListByEmail();
+
+ void sortFilteredPersonListByPhone();
+
+ void sortFilteredPersonListByFavourite();
+
+ void sortFilteredPersonListByHighImportance();
+
+ SortedList getSortedPersonList();
+
+ /**
+ * Clears the {@code tags} within the {@code ActivatedTagList}
+ */
+ void clearActivatedTagList();
+
+ /** Returns the contact that is being viewed in detail */
+ ObservableList getDetailedContactView();
+
+ /**
+ * Sets the given {@code Person} as the contact to be viewed in detail.
+ * @param person person to be viewed
+ */
+ void setDetailedContactView(Person person);
+
+ /** Removes the contact that is being viewed in detail */
+ void clearDetailedContactView();
+
+ /**
+ * Gets the {@code Person} in detailed view.
+ */
+ Person getDetailedContactViewPerson();
+
+ /**
+ * Updates the images to be displayed.
+ */
+ void setImagesToView(ImageDetailsList images);
+
+ /**
+ * Gets the images to be displayed.
+ */
+ ImageDetailsList getImagesToView();
+
+ /**
+ * Updates the commandText history
+ */
+ void updateCommandHistory(String commandText);
+
+ /**
+ * Retrieves the i-th latest command history
+ * @param i the number of commands we should backstep to retrieve
+ * @return the command text
+ */
+ CommandHistoryEntry getCommandHistory(int i);
+
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..361a62cb648 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -7,11 +7,24 @@
import java.util.function.Predicate;
import java.util.logging.Logger;
+import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.commandhistory.CommandHistory;
+import seedu.address.model.commandhistory.CommandHistoryEntry;
+import seedu.address.model.comparator.AddressComparator;
+import seedu.address.model.comparator.DeadlineListComparator;
+import seedu.address.model.comparator.EmailComparator;
+import seedu.address.model.comparator.FavouriteComparator;
+import seedu.address.model.comparator.HighImportanceComparator;
+import seedu.address.model.comparator.NameComparator;
+import seedu.address.model.comparator.PhoneComparator;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* Represents the in-memory model of the address book data.
@@ -22,6 +35,13 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final SortedList sortedPersons;
+ private final ObservableList detailedContactView;
+ private final CommandHistory commandHistory;
+ private final ObservableList activatedTags;
+
+ private ImageDetailsList imagesToView;
+
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -34,6 +54,11 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ sortedPersons = new SortedList<>(filteredPersons);
+ detailedContactView = FXCollections.observableArrayList();
+ activatedTags = new FilteredList<>(this.addressBook.getActivatedTagList());
+ this.imagesToView = new ImageDetailsList();
+ this.commandHistory = new CommandHistory();
}
public ModelManager() {
@@ -75,6 +100,17 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
userPrefs.setAddressBookFilePath(addressBookFilePath);
}
+ @Override
+ public Path getContactImagesFilePath() {
+ return userPrefs.getContactImagesFilePath();
+ }
+
+ @Override
+ public void setContactImagesFilePath(Path contactImagesFilePath) {
+ requireNonNull(contactImagesFilePath);
+ userPrefs.setContactImagesFilePath(contactImagesFilePath);
+ }
+
//=========== AddressBook ================================================================================
@Override
@@ -111,6 +147,35 @@ public void setPerson(Person target, Person editedPerson) {
addressBook.setPerson(target, editedPerson);
}
+ @Override
+ public boolean hasTag(Tag tag) {
+ requireNonNull(tag);
+ return addressBook.hasTag(tag);
+ }
+
+ @Override
+ public void addTag(Tag tag) {
+ addressBook.addTag(tag);
+ }
+
+ @Override
+ public void setTag(Tag target, Tag editedTag) {
+ requireAllNonNull(target, editedTag);
+
+ addressBook.setTag(target, editedTag);
+ }
+
+ @Override
+ public void deleteTag(Tag target) {
+ addressBook.removeTag(target);
+ }
+
+ @Override
+ public void addActivatedTag(Tag tag) {
+ requireNonNull(tag);
+ addressBook.addActivatedTag(tag);
+ }
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -122,12 +187,122 @@ public ObservableList getFilteredPersonList() {
return filteredPersons;
}
+ @Override
+ public SortedList getSortedPersonList() {
+ return sortedPersons;
+ }
+
@Override
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
filteredPersons.setPredicate(predicate);
}
+ @Override
+ public ObservableList getActivatedTagList() {
+ return this.activatedTags;
+ }
+
+ @Override
+ public void clearActivatedTagList() {
+ logger.fine("Clearing activated tag lists");
+ addressBook.clearActivatedTagList();
+ }
+
+ //=========== Detailed Contact View methods =============================================================
+
+ @Override
+ public ObservableList getDetailedContactView() {
+ return detailedContactView.filtered(PREDICATE_SHOW_ALL_PERSONS);
+ }
+
+ @Override
+ public void setDetailedContactView(Person person) {
+ if (detailedContactView.size() > 0) {
+ clearDetailedContactView();
+ }
+ logger.fine("Setting " + person.getName().fullName + " in detailed view");
+ detailedContactView.add(person);
+ assert(detailedContactView.size() == 1);
+ }
+
+ @Override
+ public void clearDetailedContactView() {
+ logger.fine("Clearing detailed view");
+ detailedContactView.clear();
+ }
+
+ @Override
+ public Person getDetailedContactViewPerson() {
+ assert detailedContactView.size() == 1;
+ return detailedContactView.get(0);
+ }
+
+ //=========== Person Images to View ==============================================================================
+ @Override
+ public void setImagesToView(ImageDetailsList images) {
+ this.imagesToView = images;
+ }
+
+ @Override
+ public ImageDetailsList getImagesToView() {
+ return this.imagesToView;
+ }
+
+ /**
+ * Updates the commandText history
+ *
+ * @param commandText the text to cache
+ */
+ @Override
+ public void updateCommandHistory(String commandText) {
+ commandHistory.cacheCommand(commandText);
+ }
+
+ /**
+ * Retrieves the i-th latest command text
+ * @return
+ */
+ @Override
+ public CommandHistoryEntry getCommandHistory(int i) {
+ return commandHistory.retrieveCommand(i);
+ }
+
+ @Override
+ public void sortFilteredPersonListByName() {
+ sortedPersons.setComparator(new NameComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByAddress() {
+ sortedPersons.setComparator(new AddressComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByDeadlineList() {
+ sortedPersons.setComparator(new DeadlineListComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByEmail() {
+ sortedPersons.setComparator(new EmailComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByPhone() {
+ sortedPersons.setComparator(new PhoneComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByFavourite() {
+ sortedPersons.setComparator(new FavouriteComparator());
+ }
+
+ @Override
+ public void sortFilteredPersonListByHighImportance() {
+ sortedPersons.setComparator(new HighImportanceComparator());
+ }
+
@Override
public boolean equals(Object obj) {
// short circuit if same object
@@ -144,7 +319,10 @@ public boolean equals(Object obj) {
ModelManager other = (ModelManager) obj;
return addressBook.equals(other.addressBook)
&& userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
+ && filteredPersons.equals(other.filteredPersons)
+ && detailedContactView.equals(other.detailedContactView)
+ && imagesToView.equals(other.imagesToView)
+ && commandHistory.equals(other.commandHistory)
+ && activatedTags.equals(other.activatedTags);
}
-
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..08f7fcb548e 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -2,6 +2,7 @@
import javafx.collections.ObservableList;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* Unmodifiable view of an address book
@@ -14,4 +15,15 @@ public interface ReadOnlyAddressBook {
*/
ObservableList getPersonList();
+ /**
+ * Returns an unmodifiable view of the tags list.
+ * This list will not contain any duplicate tags.
+ */
+ ObservableList getTagList();
+
+ /**
+ * Returns an unmodifiable view of the currently activated tag list from the find tag command.
+ * This list will not contain any duplicate tags
+ */
+ ObservableList getActivatedTagList();
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..e8f682bff65 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -13,4 +13,5 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ Path getContactImagesFilePath();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..75c22cf96d5 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path contactImagesFilePath = Paths.get("data", "images");
/**
* Creates a {@code UserPrefs} with default values.
@@ -36,6 +37,7 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setContactImagesFilePath(newUserPrefs.getContactImagesFilePath());
}
public GuiSettings getGuiSettings() {
@@ -56,6 +58,16 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
this.addressBookFilePath = addressBookFilePath;
}
+ @Override
+ public Path getContactImagesFilePath() {
+ return contactImagesFilePath;
+ }
+
+ public void setContactImagesFilePath(Path contactImagesFilePath) {
+ requireNonNull(contactImagesFilePath);
+ this.contactImagesFilePath = contactImagesFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/commandhistory/CommandHistory.java b/src/main/java/seedu/address/model/commandhistory/CommandHistory.java
new file mode 100644
index 00000000000..e66bc8bd6e5
--- /dev/null
+++ b/src/main/java/seedu/address/model/commandhistory/CommandHistory.java
@@ -0,0 +1,58 @@
+package seedu.address.model.commandhistory;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Iterator;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+public class CommandHistory implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+
+ /**
+ * Caches the command into the history for future retrieval.
+ * @param commandText the command text to be cached.
+ */
+ public void cacheCommand(String commandText) {
+ requireNonNull(commandText);
+
+ if (commandText.isBlank()) {
+ return;
+ }
+
+ this.internalList.add(new CommandHistoryEntry(commandText));
+ }
+
+ /**
+ * Retrieves the command at (size - offset) position.
+ * If offset is out of bounds, returns an empty history entry.
+ *
+ * @param offset the number of commands to step back from
+ * @return the resulting entry
+ */
+ public CommandHistoryEntry retrieveCommand(int offset) {
+ if (offset > internalList.size() || offset <= 0) {
+ return CommandHistoryEntry.getEmptyHistory();
+ }
+ return internalList.get(internalList.size() - offset);
+ }
+
+ /**
+ * Returns an iterator over elements of type {@code T}.
+ *
+ * @return an Iterator.
+ */
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CommandHistory // instanceof handles nulls
+ && internalList.equals(((CommandHistory) other).internalList));
+ }
+}
diff --git a/src/main/java/seedu/address/model/commandhistory/CommandHistoryEntry.java b/src/main/java/seedu/address/model/commandhistory/CommandHistoryEntry.java
new file mode 100644
index 00000000000..48392f49dd6
--- /dev/null
+++ b/src/main/java/seedu/address/model/commandhistory/CommandHistoryEntry.java
@@ -0,0 +1,64 @@
+package seedu.address.model.commandhistory;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.model.commandhistory.exceptions.HistoryDoesNotExistException;
+
+public class CommandHistoryEntry {
+
+ private static final CommandHistoryEntry EMPTY_COMMAND_HISTORY = new CommandHistoryEntrySentinel();
+
+ private final String commandText;
+
+ // hidden default constructor
+ private CommandHistoryEntry() {
+ commandText = "";
+ }
+
+ /**
+ * Creates a command history entry. Encapsulates the information of a past command made by the user.
+ *
+ * @param commandText the raw input provided by the user.
+ */
+ public CommandHistoryEntry(String commandText) {
+ requireNonNull(commandText);
+ this.commandText = commandText;
+ }
+
+ public static CommandHistoryEntry getEmptyHistory() {
+ return EMPTY_COMMAND_HISTORY;
+ }
+
+ public boolean exists() {
+ return true;
+ }
+
+ public String getCommandText() {
+ return this.commandText;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CommandHistoryEntry // instanceof handles nulls
+ && commandText.equals(((CommandHistoryEntry) other).commandText));
+ }
+
+ private static class CommandHistoryEntrySentinel extends CommandHistoryEntry {
+
+ @Override
+ public boolean exists() {
+ return false;
+ }
+
+ @Override
+ public String getCommandText() {
+ throw new HistoryDoesNotExistException();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof CommandHistoryEntrySentinel;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/commandhistory/exceptions/HistoryDoesNotExistException.java b/src/main/java/seedu/address/model/commandhistory/exceptions/HistoryDoesNotExistException.java
new file mode 100644
index 00000000000..c545be669f8
--- /dev/null
+++ b/src/main/java/seedu/address/model/commandhistory/exceptions/HistoryDoesNotExistException.java
@@ -0,0 +1,4 @@
+package seedu.address.model.commandhistory.exceptions;
+
+public class HistoryDoesNotExistException extends RuntimeException {
+}
diff --git a/src/main/java/seedu/address/model/comparator/AddressComparator.java b/src/main/java/seedu/address/model/comparator/AddressComparator.java
new file mode 100644
index 00000000000..f332e3b2c47
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/AddressComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class AddressComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getAddress().compareTo(p2.getAddress());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/DeadlineComparator.java b/src/main/java/seedu/address/model/comparator/DeadlineComparator.java
new file mode 100644
index 00000000000..4a2bafe989e
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/DeadlineComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Deadline;
+
+public class DeadlineComparator implements Comparator {
+ @Override
+ public int compare(Deadline d1, Deadline d2) {
+ return d1.compareTo(d2);
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/DeadlineListComparator.java b/src/main/java/seedu/address/model/comparator/DeadlineListComparator.java
new file mode 100644
index 00000000000..ed14aa67d2f
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/DeadlineListComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class DeadlineListComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getDeadlines().compareTo(p2.getDeadlines());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/EmailComparator.java b/src/main/java/seedu/address/model/comparator/EmailComparator.java
new file mode 100644
index 00000000000..7f5cefe10dc
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/EmailComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class EmailComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getEmail().compareTo(p2.getEmail());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/FavouriteComparator.java b/src/main/java/seedu/address/model/comparator/FavouriteComparator.java
new file mode 100644
index 00000000000..ea2dc06817d
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/FavouriteComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class FavouriteComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getFavouriteStatus().compareTo(p2.getFavouriteStatus());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/HighImportanceComparator.java b/src/main/java/seedu/address/model/comparator/HighImportanceComparator.java
new file mode 100644
index 00000000000..53e465a79a3
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/HighImportanceComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class HighImportanceComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getHighImportanceStatus().compareTo(p2.getHighImportanceStatus());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/NameComparator.java b/src/main/java/seedu/address/model/comparator/NameComparator.java
new file mode 100644
index 00000000000..8bc2855cef0
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/NameComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class NameComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getName().compareTo(p2.getName());
+ }
+}
diff --git a/src/main/java/seedu/address/model/comparator/PhoneComparator.java b/src/main/java/seedu/address/model/comparator/PhoneComparator.java
new file mode 100644
index 00000000000..6f5125f075e
--- /dev/null
+++ b/src/main/java/seedu/address/model/comparator/PhoneComparator.java
@@ -0,0 +1,12 @@
+package seedu.address.model.comparator;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.Person;
+
+public class PhoneComparator implements Comparator {
+ @Override
+ public int compare(Person p1, Person p2) {
+ return p1.getPhone().compareTo(p2.getPhone());
+ }
+}
diff --git a/src/main/java/seedu/address/model/image/ImageDetails.java b/src/main/java/seedu/address/model/image/ImageDetails.java
new file mode 100644
index 00000000000..8242cc9878e
--- /dev/null
+++ b/src/main/java/seedu/address/model/image/ImageDetails.java
@@ -0,0 +1,59 @@
+package seedu.address.model.image;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+
+public class ImageDetails {
+ public final File imageFile;
+
+ /**
+ * Creates an image details object that encapsulates the information of an image file.
+ *
+ * @param imageFile the file that is being encapsulated.
+ */
+ public ImageDetails(File imageFile) {
+ requireNonNull(imageFile);
+ this.imageFile = imageFile;
+ }
+
+ public String getName() {
+ return this.imageFile.getName();
+ }
+
+ public File getImageFile() {
+ return this.imageFile;
+ }
+
+ /**
+ * The string representation of the path returned is relative to the project root.
+ *
+ * @return path of the image file, relative to the project root.
+ */
+ public String getPath() {
+ return this.imageFile.getPath();
+ }
+
+ public String getJavaFxImageUrl() {
+ return String.format("file:%s", this.imageFile.getPath());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ImageDetails // instanceof handles nulls
+ && getName().equals(((ImageDetails) other).getName())); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return '[' + getName() + ']';
+ }
+}
diff --git a/src/main/java/seedu/address/model/image/ImageDetailsList.java b/src/main/java/seedu/address/model/image/ImageDetailsList.java
new file mode 100644
index 00000000000..73c63e5d2ce
--- /dev/null
+++ b/src/main/java/seedu/address/model/image/ImageDetailsList.java
@@ -0,0 +1,85 @@
+package seedu.address.model.image;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ImageDetailsList implements Iterable {
+ private final List images;
+
+ public ImageDetailsList() {
+ this.images = new ArrayList<>();
+ }
+
+ public ImageDetailsList(List images) {
+ this.images = images;
+ }
+
+ /**
+ * Creates an iterable list of image details, appended to this current list.
+ * The returned list is a new list.
+ *
+ * @param appendedImages the images to append to this list.
+ * @return the newly appended list.
+ */
+ public ImageDetailsList appendImageDetails(List appendedImages) {
+ List newImages = new ArrayList<>();
+ newImages.addAll(images);
+ newImages.addAll(appendedImages);
+ return new ImageDetailsList(newImages);
+ }
+
+ public List getImages() {
+ return this.images;
+ }
+
+ public ImageDetails get(int i) {
+ return this.images.get(i);
+ }
+
+ public boolean isEmpty() {
+ return this.images.isEmpty();
+ }
+
+ /**
+ * Returns the size of the list, indicated by the number of ImageDetails within the list.
+ *
+ * @return size of the list.
+ */
+ public int size() {
+ return this.images.size();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < images.size(); i++) {
+ sb.append(i + 1).append(": ").append(images.get(i)).append("\n");
+ }
+ return sb.toString();
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ImageDetailsList // instanceof handles nulls
+ && images.equals(((ImageDetailsList) other).images));
+ }
+
+ @Override
+ public int hashCode() {
+ return images.hashCode();
+ }
+
+
+ /**
+ * Returns an iterator over elements of type {@code Images}.
+ *
+ * @return an Iterator.
+ */
+ @Override
+ public Iterator iterator() {
+ return this.images.iterator();
+ }
+}
diff --git a/src/main/java/seedu/address/model/image/exceptions/ImageDetailsNotFoundException.java b/src/main/java/seedu/address/model/image/exceptions/ImageDetailsNotFoundException.java
new file mode 100644
index 00000000000..83ef7f4e4bf
--- /dev/null
+++ b/src/main/java/seedu/address/model/image/exceptions/ImageDetailsNotFoundException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.image.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified image.
+ */
+public class ImageDetailsNotFoundException extends RuntimeException {
+ public ImageDetailsNotFoundException() {
+ super("Image not found.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/image/util/ImageUtil.java b/src/main/java/seedu/address/model/image/util/ImageUtil.java
new file mode 100644
index 00000000000..a62b9376997
--- /dev/null
+++ b/src/main/java/seedu/address/model/image/util/ImageUtil.java
@@ -0,0 +1,120 @@
+package seedu.address.model.image.util;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import javax.imageio.ImageIO;
+
+import javafx.stage.FileChooser;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
+
+public class ImageUtil {
+ private static final String JPG_EXTENSION = "jpg";
+ private static final String PNG_EXTENSION = "png";
+ private static final FileChooser.ExtensionFilter IMAGE_FILE_FILTERS = new FileChooser.ExtensionFilter(
+ "Image Files", "*." + PNG_EXTENSION, "*." + JPG_EXTENSION);
+
+ private static FileChooser fileChooser;
+
+ /**
+ * Opens a FileChooser configured to view and select only image files.
+ * @return the FileChooser that can be opened
+ */
+ public static FileChooser openImageFileChooser() {
+ if (fileChooser == null) {
+ fileChooser = new FileChooser();
+ fileChooser.getExtensionFilters().add(IMAGE_FILE_FILTERS);
+ }
+ return fileChooser;
+ }
+
+ /**
+ * Checks if the {@code file} exists in the {@code directoryDirectory}.
+ * @param file the file to check
+ * @param directoryPath path of the directory to check if file exists within
+ * @return true if the file exists
+ */
+ public static boolean fileExists(File file, Path directoryPath) {
+ File directory = directoryPath.toFile();
+ File[] directoryFiles = directory.listFiles();
+
+ if (directoryFiles == null) {
+ // Directory doesn't exist yet
+ return false;
+ }
+
+ for (File f : directoryFiles) {
+ if (f.getName().equals(file.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Copies the image specified at the source file, to the destination path specified.
+ *
+ * @param src the image to be copied
+ * @param destPath the location that the newly copied image should be saved to
+ * @return the image details of the copied image
+ * @throws IOException if reading or writing the image failed
+ */
+ public static ImageDetails copyTo(File src, Path destPath) throws IOException {
+ BufferedImage srcImg = ImageIO.read(src);
+
+ FileUtil.createIfMissing(destPath);
+ File destFile = destPath.toFile();
+
+ String imgExtension = getImageExtension(src.getName());
+
+ ImageIO.write(srcImg, imgExtension, destFile);
+ return new ImageDetails(destFile);
+ }
+
+ private static String getImageExtension(String fileName) {
+ String[] splitFileName = fileName.split("\\.");
+ // FileChooser should have already ensured that the file must have _some_ valid extension
+ assert splitFileName.length > 1;
+
+ String extension = splitFileName[splitFileName.length - 1];
+ // FileChooser should have already ensured that the file must have some _valid_ extension
+ assert extension.equals(JPG_EXTENSION) || extension.equals(PNG_EXTENSION);
+
+ return extension;
+ }
+
+ /**
+ * Removes from this list images that no longer exist within the data/images directory.
+ *
+ * @return a sanitized {@code ImageDetailsList} object.
+ */
+ public static ImageDetailsList sanitizeList(ImageDetailsList listToSanitize, Path pathToSanitize) {
+ List sanitizedList = new ArrayList<>();
+ for (ImageDetails img : listToSanitize) {
+ if (fileExists(img.getImageFile(), pathToSanitize)) {
+ sanitizedList.add(img);
+ }
+ }
+ return new ImageDetailsList(sanitizedList);
+ }
+
+ /**
+ * Deletes the file from the specified filepath.
+ *
+ * @param imageToDelete the details of the image to delete.
+ */
+ public static void removeFile(ImageDetails imageToDelete) {
+ Path filePath = imageToDelete.getImageFile().toPath();
+ if (!FileUtil.isFileExists(filePath)) {
+ return;
+ }
+
+ FileUtil.deleteFile(filePath);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 60472ca22a0..555284d55f0 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -7,9 +7,10 @@
* Represents a Person's address in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
*/
-public class Address {
-
+public class Address implements Comparable {
+ public static final String MODEL_NAME = "address";
public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
+ public static final Address EMPTY_ADDRESS = new Address("*No Address Specified*");
/*
* The first character of the address must not be a whitespace,
@@ -54,4 +55,9 @@ public int hashCode() {
return value.hashCode();
}
+ @Override
+ public int compareTo(Address other) {
+ return this.toString().compareToIgnoreCase(other.toString());
+ }
+
}
diff --git a/src/main/java/seedu/address/model/person/Deadline.java b/src/main/java/seedu/address/model/person/Deadline.java
new file mode 100644
index 00000000000..83de7cff773
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Deadline.java
@@ -0,0 +1,123 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Represents a Person's deadline in the address book.
+ */
+public class Deadline implements Comparable {
+ public static final String MESSAGE_CONSTRAINTS = "Deadlines can only take dd/mm/yyyy and must have a description";
+ public static final String NO_DEADLINE_PLACEHOLDER = "*No deadline specified*";
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+ public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("dd/MM/yyyy");
+ public static final String STORAGE_PARSE_ERROR = "Deadline in storage could not be parsed properly";
+
+ public final String description;
+ public final String value;
+
+ /**
+ * Constructs a {@code Deadline}.
+ *
+ * @param dateAsString a valid date.
+ */
+ public Deadline(String description, String dateAsString) {
+ requireNonNull(dateAsString);
+ checkArgument(isValidDeadline(description, dateAsString), MESSAGE_CONSTRAINTS);
+ if (dateAsString.equals(NO_DEADLINE_PLACEHOLDER)) {
+ new Deadline();
+ }
+ this.description = description;
+ value = dateAsString;
+ }
+
+ /**
+ * Constructs a {@code Deadline} object that is empty.
+ */
+ public Deadline() {
+ description = "";
+ value = NO_DEADLINE_PLACEHOLDER;
+ }
+
+ private Date getDate() throws ParseException {
+ if (value.equals(NO_DEADLINE_PLACEHOLDER)) {
+ return new Date(Long.MAX_VALUE);
+ }
+
+ FORMATTER.setLenient(false);
+ Date date;
+
+ try {
+ date = FORMATTER.parse(value);
+ } catch (java.text.ParseException e) {
+ throw new ParseException(STORAGE_PARSE_ERROR);
+ }
+
+ return date;
+ }
+
+ @Override
+ public String toString() {
+ if (!description.equals("")) {
+ return description + ": " + value;
+ }
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Deadline // instanceof handles nulls
+ && value.equals(((Deadline) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(Deadline other) throws ClassCastException {
+ try {
+ return this.getDate().compareTo(other.getDate());
+ } catch (ParseException e) {
+ throw new ClassCastException(e.getMessage());
+ }
+ }
+
+ /**
+ * Check if given string is a valid date.
+ *
+ * @param dateAsString the given string.
+ * @return true if given string is a valid date.
+ */
+ public static boolean isValidDeadline(String description, String dateAsString) {
+ if (description.equals("") && dateAsString.equals(NO_DEADLINE_PLACEHOLDER)) {
+ return true;
+ }
+
+ if (description.equals("")) {
+ return false;
+ }
+
+ if (!description.matches(VALIDATION_REGEX)) {
+ return false;
+ }
+
+ FORMATTER.setLenient(false);
+ Date date;
+
+ try {
+ date = FORMATTER.parse(dateAsString);
+
+ return date.before(FORMATTER.parse("01/01/10000"));
+ } catch (java.text.ParseException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/DeadlineList.java b/src/main/java/seedu/address/model/person/DeadlineList.java
new file mode 100644
index 00000000000..98f1e973df1
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/DeadlineList.java
@@ -0,0 +1,113 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.model.comparator.DeadlineComparator;
+
+public class DeadlineList implements Comparable {
+ public static final String MODEL_NAME = "deadline";
+ private static final Deadline EMPTY_DEADLINE = new Deadline();
+
+ private ArrayList deadlines = new ArrayList<>();
+
+ public DeadlineList() {
+ this.deadlines.add(new Deadline());
+ }
+
+ /**
+ * Constructs a {@code DeadlineList} object.
+ *
+ * @param deadlines the list of the deadlines to be put into {@code DeadlineList}.
+ */
+ public DeadlineList(List deadlines) {
+ requireNonNull(deadlines);
+ this.deadlines.addAll(deadlines);
+ }
+
+ public ArrayList getDeadlines() {
+ return deadlines;
+ }
+
+ /**
+ * Adds the given deadlines to this list of deadlines and returns it in a new
+ * {@code DeadlineList}.
+ * @param deadlinesToAppend deadlines to add to this list.
+ * @return new DeadlineList with the result.
+ */
+ public DeadlineList appendDeadlines(DeadlineList deadlinesToAppend) {
+ if (this.size() == 0) {
+ return deadlinesToAppend;
+ }
+ DeadlineList newDeadlines = new DeadlineList(this.deadlines);
+ newDeadlines.deadlines.addAll(deadlinesToAppend.deadlines);
+ return newDeadlines;
+ }
+
+ /**
+ * Deletes the deadline at the given index of this list of deadlines and return it in
+ * a new {@code DeadlineList}.
+ * @param index index of deadline to delete.
+ * @return new DeadlineList with the result.
+ */
+ public DeadlineList delete(int index) {
+ DeadlineList newDeadlineList = new DeadlineList(this.deadlines);
+ newDeadlineList.deadlines.remove(index);
+ if (newDeadlineList.deadlines.isEmpty()) {
+ return new DeadlineList();
+ }
+ return newDeadlineList;
+ }
+
+ /**
+ * Produces a string representation of this {@code Deadline} object for printing to GUI.
+ *
+ * @return list view of deadlines in this {@code Deadline} object.
+ */
+ public String listFormat() {
+ if (deadlines.size() == 0) {
+ return "";
+ }
+ StringBuilder result = new StringBuilder();
+ for (int i = 1; i <= deadlines.size(); i++) {
+ result.append(deadlines.get(i - 1)).append("\n");
+ }
+ return result.toString();
+ }
+
+ /**
+ * Gives the size of this deadline list, considering only deadlines given by user.
+ */
+ public int size() {
+ return this.deadlines.get(0).equals(EMPTY_DEADLINE) ? 0
+ : this.deadlines.size();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < deadlines.size(); i++) {
+ result.append(deadlines.get(i));
+ if (i < deadlines.size() - 1) {
+ result.append(", ");
+ }
+ }
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeadlineList // instanceof handles nulls
+ && deadlines.equals(((DeadlineList) other).deadlines)); // state check
+ }
+
+ @Override
+ public int compareTo(DeadlineList other) {
+ return Collections.min(this.deadlines, new DeadlineComparator())
+ .compareTo(Collections.min(other.getDeadlines(), new DeadlineComparator()));
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index f866e7133de..1164856f13a 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -7,8 +7,8 @@
* Represents a Person's email in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
*/
-public class Email {
-
+public class Email implements Comparable {
+ public static final String MODEL_NAME = "email";
private static final String SPECIAL_CHARACTERS = "+_.-";
public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
+ "and adhere to the following constraints:\n"
@@ -68,4 +68,9 @@ public int hashCode() {
return value.hashCode();
}
+ @Override
+ public int compareTo(Email other) {
+ return this.toString().compareToIgnoreCase(other.toString());
+ }
+
}
diff --git a/src/main/java/seedu/address/model/person/Favourite.java b/src/main/java/seedu/address/model/person/Favourite.java
new file mode 100644
index 00000000000..bd0a7e1d692
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Favourite.java
@@ -0,0 +1,89 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+public class Favourite implements Comparable {
+ public static final String MODEL_NAME = "fav";
+ public static final Favourite IS_FAVOURITE = new Favourite(true);
+ public static final Favourite NOT_FAVOURITE = new Favourite(false);
+ public static final String MESSAGE_CONSTRAINTS =
+ "Favourite should be either true or false, and should not be blank.";
+
+ /*
+ * The first character of the favourite status must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Favourite} status object.
+ *
+ * @param isFavourite The favourite status.
+ */
+ private Favourite(Boolean isFavourite) {
+ requireNonNull(isFavourite);
+ value = isFavourite.toString();
+ }
+
+ /**
+ * Takes the string representation of the boolean and returns the correct {@code Favourite} object.
+ *
+ * @param isFavourite is the String representation provided.
+ * @return the {@code Favourite} object that corresponds to the String provided.
+ */
+ public static Favourite valueOf(String isFavourite) throws IllegalArgumentException {
+ if (!("true".equals(isFavourite) || "false".equals(isFavourite))) {
+ throw new IllegalArgumentException("Favourite can only be true or false.");
+ }
+
+ return "true".equals(isFavourite)
+ ? IS_FAVOURITE
+ : NOT_FAVOURITE;
+ }
+
+ /**
+ * Returns true if a given string is a valid favourite status.
+ */
+ public static boolean isValidFavourite(String test) {
+ return test.matches(VALIDATION_REGEX)
+ && ("true".equals(test) || "false".equals(test));
+ }
+
+
+ public boolean isFavourite() {
+ return this.equals(IS_FAVOURITE);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Favourite // instanceof handles nulls
+ && value.equals(((Favourite) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(Favourite other) {
+ if (this.isFavourite() && !other.isFavourite()) {
+ return -1;
+ } else if (this.isFavourite() == other.isFavourite()) {
+ return 0;
+ } else if (!this.isFavourite() && other.isFavourite()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/HighImportance.java b/src/main/java/seedu/address/model/person/HighImportance.java
new file mode 100644
index 00000000000..dc2c276c229
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/HighImportance.java
@@ -0,0 +1,91 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+public class HighImportance implements Comparable {
+ public static final String MODEL_NAME = "impt";
+ public static final HighImportance HIGH_IMPORTANCE = new HighImportance(true);
+ public static final HighImportance NOT_HIGH_IMPORTANCE = new HighImportance(false);
+ public static final String MESSAGE_CONSTRAINTS =
+ "HighImportance should be either true or false, and should not be blank.";
+ public static final String TRUE_STRING = String.valueOf(true);
+ public static final String FALSE_STRING = String.valueOf(false);
+ /*
+ * The first character of the HighImportance status must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+
+ public final String value;
+
+ /**
+ * Constructs a {@code HighImportance} status object.
+ *
+ * @param hasHighImportance The HighImportance status.
+ */
+ public HighImportance(Boolean hasHighImportance) {
+ requireNonNull(hasHighImportance);
+ value = hasHighImportance.toString();
+ }
+
+ /**
+ * Takes the string representation of the boolean and returns the correct {@code HighImportance} object.
+ *
+ * @param hasHighImportance is the String representation provided.
+ * @return the {@code HighImportance} object that corresponds to the String provided.
+ */
+ public static HighImportance valueOf(String hasHighImportance) throws IllegalArgumentException {
+ if (!(TRUE_STRING.equals(hasHighImportance) || FALSE_STRING.equals(hasHighImportance))) {
+ throw new IllegalArgumentException("HighImportance can only be true or false.");
+ }
+
+ return TRUE_STRING.equals(hasHighImportance)
+ ? HIGH_IMPORTANCE
+ : NOT_HIGH_IMPORTANCE;
+ }
+
+ /**
+ * Returns true if a given string has a valid HighImportance status.
+ */
+ public static boolean isValidHighImportance(String test) {
+ return test.matches(VALIDATION_REGEX)
+ && (TRUE_STRING.equals(test) || FALSE_STRING.equals(test));
+ }
+
+ /**
+ * Returns true if a person has high importance
+ */
+ public boolean hasHighImportance() {
+ return this.equals(HIGH_IMPORTANCE);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof HighImportance // instanceof handles nulls
+ && value.equals(((HighImportance) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(HighImportance other) {
+ if (this.hasHighImportance() && !other.hasHighImportance()) {
+ return -1;
+ } else if (this.hasHighImportance() == other.hasHighImportance()) {
+ return 0;
+ } else if (!this.hasHighImportance() && other.hasHighImportance()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 79244d71cf7..34c3048c580 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -7,8 +7,8 @@
* Represents a Person's name in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
-public class Name {
-
+public class Name implements Comparable {
+ public static final String MODEL_NAME = "name";
public static final String MESSAGE_CONSTRAINTS =
"Names should only contain alphanumeric characters and spaces, and it should not be blank";
@@ -56,4 +56,9 @@ public int hashCode() {
return fullName.hashCode();
}
+ @Override
+ public int compareTo(Name other) {
+ return this.toString().compareToIgnoreCase(other.toString());
+ }
+
}
diff --git a/src/main/java/seedu/address/model/person/Notes.java b/src/main/java/seedu/address/model/person/Notes.java
new file mode 100644
index 00000000000..8508f80c4ed
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Notes.java
@@ -0,0 +1,118 @@
+package seedu.address.model.person;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Notes {
+
+ public static final String MESSAGE_CONSTRAINTS = "Notes can take any values, and it should not be blank";
+
+ private static final char WHITESPACE = ' ';
+
+ public final List value;
+
+ /**
+ * Constructs a new {@code Notes}.
+ */
+ private Notes() {
+ value = new ArrayList<>();
+ }
+
+ /**
+ * Checks if the given note is a valid note that will be added.
+ * A note is valid as long as it contains at least 1 character that
+ * is not a whitespace.
+ * @param noteToAdd note to check validity
+ * @return true if string is valid, false otherwise
+ */
+ public static boolean isValidNote(String noteToAdd) {
+ boolean isAllWhiteSpace = true;
+ int i = 0;
+ while (isAllWhiteSpace && i < noteToAdd.length()) {
+ isAllWhiteSpace = noteToAdd.charAt(i) == WHITESPACE;
+ i++;
+ }
+ return !isAllWhiteSpace;
+ }
+
+ /**
+ * Create a fresh Notes for when a new contact is
+ * created, or when notes for a contact are cleared.
+ * @return Notes object with empty list
+ */
+ public static Notes getNewNotes() {
+ return new Notes();
+ }
+
+ /**
+ * Initialises a Notes object containing notes that
+ * are in the List passed to it.
+ * @param notes list of notes to initialise the Notes object with
+ * @return Notes containing notes passed to it
+ */
+ public static Notes loadNotesFromList(List notes) {
+ Notes newNotes = getNewNotes();
+ newNotes.value.addAll(notes);
+ return newNotes;
+ }
+
+ /**
+ * Creates a new Notes that has this Notes' contents and the given
+ * new note appended.
+ * @param newNote new note to be added
+ * @return new Notes
+ */
+ public Notes updateNotes(String newNote) {
+ Notes newNotes = new Notes();
+ newNotes.value.addAll(this.value);
+ newNotes.value.add(newNote);
+ return newNotes;
+ }
+
+ /**
+ * Creates a new Notes that has this Notes' contents with the note at
+ * the given index deleted.
+ * @param index index of note to be deleted
+ * @return new Notes
+ */
+ public Notes delete(int index) {
+ Notes newNotes = new Notes();
+ newNotes.value.addAll(this.value);
+ newNotes.value.remove(index);
+ return newNotes;
+ }
+
+ /**
+ * Produces a string representation of this Notes
+ * object for printing to GUI.
+ * @return list view of notes in this Notes object
+ */
+ public String listFormat() {
+ if (value.size() == 0) {
+ return "";
+ }
+ StringBuilder result = new StringBuilder();
+ for (int i = 1; i <= value.size(); i++) {
+ result.append(i).append(". ").append(value.get(i - 1)).append("\n");
+ }
+ return result.toString();
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Notes // instanceof handles nulls
+ && value.equals(((Notes) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 8ff1d83fe89..ba1f70d3709 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -1,12 +1,11 @@
package seedu.address.model.person;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.tag.Tag;
/**
@@ -22,18 +21,29 @@ public class Person {
// Data fields
private final Address address;
+ private final DeadlineList deadlines;
+ private final Notes notes;
private final Set tags = new HashSet<>();
+ private final Favourite favouriteStatus;
+ private final ImageDetailsList imageDetailsList;
+ private final HighImportance highImportanceStatus;
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(Name name, Phone phone, Email email, Address address, DeadlineList deadlines, Notes notes,
+ Set tags, Favourite favouriteStatus, HighImportance highImportanceStatus,
+ ImageDetailsList images) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.deadlines = deadlines;
+ this.notes = notes;
+ this.favouriteStatus = favouriteStatus;
+ this.highImportanceStatus = highImportanceStatus;
this.tags.addAll(tags);
+ this.imageDetailsList = images;
}
public Name getName() {
@@ -52,6 +62,34 @@ public Address getAddress() {
return address;
}
+ public DeadlineList getDeadlines() {
+ return deadlines;
+ }
+
+ public Notes getNotes() {
+ return notes;
+ }
+
+ public Favourite getFavouriteStatus() {
+ return favouriteStatus;
+ }
+
+ public boolean isFavourite() {
+ return favouriteStatus.isFavourite();
+ }
+
+ public ImageDetailsList getImageDetailsList() {
+ return imageDetailsList;
+ }
+
+ public HighImportance getHighImportanceStatus() {
+ return highImportanceStatus;
+ }
+
+ public boolean hasHighImportance() {
+ return highImportanceStatus.hasHighImportance();
+ }
+
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
@@ -92,13 +130,19 @@ public boolean equals(Object other) {
&& otherPerson.getPhone().equals(getPhone())
&& otherPerson.getEmail().equals(getEmail())
&& otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
+ && otherPerson.getDeadlines().equals(getDeadlines())
+ && otherPerson.getNotes().equals(getNotes())
+ && otherPerson.getTags().equals(getTags())
+ && otherPerson.getFavouriteStatus().equals(getFavouriteStatus())
+ && otherPerson.getImageDetailsList().equals(getImageDetailsList())
+ && otherPerson.getHighImportanceStatus().equals(getHighImportanceStatus());
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, deadlines, notes, tags, favouriteStatus, imageDetailsList,
+ highImportanceStatus);
}
@Override
@@ -110,14 +154,22 @@ public String toString() {
.append("; Email: ")
.append(getEmail())
.append("; Address: ")
- .append(getAddress());
+ .append(getAddress())
+ .append("; Deadline(s): ")
+ .append(getDeadlines())
+ .append("; Notes: ")
+ .append(getNotes())
+ .append("; Favourite: ")
+ .append(getFavouriteStatus())
+ .append("; Importance Status: ")
+ .append(getHighImportanceStatus());
Set tags = getTags();
+
if (!tags.isEmpty()) {
builder.append("; Tags: ");
tags.forEach(builder::append);
}
return builder.toString();
}
-
}
diff --git a/src/main/java/seedu/address/model/person/PersonHasHighImportancePredicate.java b/src/main/java/seedu/address/model/person/PersonHasHighImportancePredicate.java
new file mode 100644
index 00000000000..5431c2fbe33
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PersonHasHighImportancePredicate.java
@@ -0,0 +1,19 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+/**
+ * Tests that a {@code Person} is a {@code HighImportance} contact.
+ */
+public class PersonHasHighImportancePredicate implements Predicate {
+ @Override
+ public boolean test(Person person) {
+ return person.getHighImportanceStatus().hasHighImportance();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof PersonHasHighImportancePredicate);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/PersonIsFavouriteContactPredicate.java b/src/main/java/seedu/address/model/person/PersonIsFavouriteContactPredicate.java
new file mode 100644
index 00000000000..66bae87b9c4
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PersonIsFavouriteContactPredicate.java
@@ -0,0 +1,20 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+
+/**
+ * Tests that a {@code Person} is a {@code Favourite} contact.
+ */
+public class PersonIsFavouriteContactPredicate implements Predicate {
+ @Override
+ public boolean test(Person person) {
+ return person.getFavouriteStatus().isFavourite();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof NameContainsKeywordsPredicate);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index 872c76b382f..b01b29af975 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -7,9 +7,8 @@
* Represents a Person's phone number in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
*/
-public class Phone {
-
-
+public class Phone implements Comparable {
+ public static final String MODEL_NAME = "phone";
public static final String MESSAGE_CONSTRAINTS =
"Phone numbers should only contain numbers, and it should be at least 3 digits long";
public static final String VALIDATION_REGEX = "\\d{3,}";
@@ -50,4 +49,9 @@ public int hashCode() {
return value.hashCode();
}
+ @Override
+ public int compareTo(Phone other) {
+ return this.toString().compareToIgnoreCase(other.toString());
+ }
+
}
diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..83b7b2342d2
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java
@@ -0,0 +1,51 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Tag} matches any of the keywords given.
+ */
+public class TagContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ /**
+ * Constructs a TagContainKeywordsPredicate to test if a {@code Person} has all the tags listed in the {@code
+ * List} of keywords
+ * @param keywords the keywords to check
+ */
+ public TagContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ boolean hasAllMatches = true;
+
+ if (keywords.size() == 0) {
+ return false;
+ }
+
+ for (String keyword : keywords) {
+ boolean doesKeywordMatchAnyTags = person.getTags().stream()
+ .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword));
+ hasAllMatches = hasAllMatches && doesKeywordMatchAnyTags;
+ }
+
+ return hasAllMatches;
+ }
+
+ public List getMatchedKeyowrds() {
+ return this.keywords;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/tag/ActivatedTagList.java b/src/main/java/seedu/address/model/tag/ActivatedTagList.java
new file mode 100644
index 00000000000..ffe9b40dc24
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/ActivatedTagList.java
@@ -0,0 +1,144 @@
+package seedu.address.model.tag;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.tag.exceptions.DuplicateTagException;
+import seedu.address.model.tag.exceptions.TagNotFoundException;
+
+/**
+ * A list of activated tags that enforces uniqueness between its elements and does not allow nulls.
+ * A tag is considered unique by comparing using {@code Tag#isSameTag(Tag)}. As such, adding and updating of
+ * tags uses Tag#isSameTag(Tag) for equality so as to ensure that the tag being added or updated is
+ * unique in terms of identity in the ActivatedTagList. However, the removal of a tag uses Tag#equals(Object).
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Tag#isSameTag(Tag)
+ */
+public class ActivatedTagList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent person as the given argument.
+ */
+ public boolean contains(Tag toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameTag);
+ }
+
+ /**
+ * Adds a person to the list.
+ * The person must not already exist in the list.
+ */
+ public void add(Tag toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateTagException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the tag {@code target} in the list with {@code editedTag}.
+ * {@code target} must exist in the list.
+ * The tag identity of {@code editedTag} must not be the same as another existing tag in the list.
+ */
+ public void setTag(Tag target, Tag editedTag) {
+ requireAllNonNull(target, editedTag);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new TagNotFoundException();
+ }
+
+ if (!target.isSameTag(editedTag) && contains(editedTag)) {
+ throw new DuplicateTagException();
+ }
+
+ internalList.set(index, editedTag);
+ }
+
+ public void setTags(ActivatedTagList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code tags}.
+ * {@code tags} must not contain duplicate tags.
+ */
+ public void setTags(List tags) {
+ requireAllNonNull(tags);
+ if (!tagsAreUnique(tags)) {
+ throw new DuplicateTagException();
+ }
+
+ internalList.setAll(tags);
+ }
+
+ /**
+ * Removes the equivalent tag from the list.
+ * The tag must exist in the list.
+ */
+ public void remove(Tag toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new TagNotFoundException();
+ }
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ActivatedTagList // instanceof handles nulls
+ && internalList.equals(((ActivatedTagList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Clears the list of activated tags
+ */
+ public void clear() {
+ internalList.clear();
+ internalUnmodifiableList.clear();
+ }
+
+ /**
+ * Returns true if {@code tag} contains only unique tag.
+ */
+ private boolean tagsAreUnique(List tags) {
+ for (int i = 0; i < tags.size() - 1; i++) {
+ for (int j = i + 1; j < tags.size(); j++) {
+ if (tags.get(i).isSameTag(tags.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
index b0ea7e7dad7..ef1ece7ef26 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -32,6 +32,18 @@ public static boolean isValidTagName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns true if both tags have the same name.
+ * This defines a weaker notion of equality between two tags.
+ */
+ public boolean isSameTag(Tag otherTag) {
+ if (otherTag == this) {
+ return true;
+ }
+ return otherTag != null
+ && otherTag.tagName.equalsIgnoreCase(tagName);
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java
new file mode 100644
index 00000000000..f54c34c0dd9
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java
@@ -0,0 +1,136 @@
+package seedu.address.model.tag;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.tag.exceptions.DuplicateTagException;
+import seedu.address.model.tag.exceptions.TagNotFoundException;
+
+/**
+ * A list of tags that enforces uniqueness between its elements and does not allow nulls.
+ * A tag is considered unique by comparing using {@code Tag#isSameTag(Tag)}. As such, adding and updating of
+ * tags uses Tag#isSameTag(Tag) for equality so as to ensure that the tag being added or updated is
+ * unique in terms of identity in the UniqueTagList. However, the removal of a tag uses Tag#equals(Object).
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Tag#isSameTag(Tag)
+ */
+public class UniqueTagList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent person as the given argument.
+ */
+ public boolean contains(Tag toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameTag);
+ }
+
+ /**
+ * Adds a person to the list.
+ * The person must not already exist in the list.
+ */
+ public void add(Tag toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateTagException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the tag {@code target} in the list with {@code editedTag}.
+ * {@code target} must exist in the list.
+ * The tag identity of {@code editedTag} must not be the same as another existing tag in the list.
+ */
+ public void setTag(Tag target, Tag editedTag) {
+ requireAllNonNull(target, editedTag);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new TagNotFoundException();
+ }
+
+ if (!target.isSameTag(editedTag) && contains(editedTag)) {
+ throw new DuplicateTagException();
+ }
+
+ internalList.set(index, editedTag);
+ }
+
+ public void setTags(UniqueTagList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code tags}.
+ * {@code tags} must not contain duplicate tags.
+ */
+ public void setTags(List tags) {
+ requireAllNonNull(tags);
+ if (!tagsAreUnique(tags)) {
+ throw new DuplicateTagException();
+ }
+
+ internalList.setAll(tags);
+ }
+
+ /**
+ * Removes the equivalent tag from the list.
+ * The tag must exist in the list.
+ */
+ public void remove(Tag toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new TagNotFoundException();
+ }
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UniqueTagList // instanceof handles nulls
+ && internalList.equals(((UniqueTagList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code tag} contains only unique tag.
+ */
+ private boolean tagsAreUnique(List tags) {
+ for (int i = 0; i < tags.size() - 1; i++) {
+ for (int j = i + 1; j < tags.size(); j++) {
+ if (tags.get(i).isSameTag(tags.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java b/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java
new file mode 100644
index 00000000000..87b555ed648
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/DuplicateTagException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Tags (Tags are considered duplicates if they have the same
+ * name (case-insensitive)).
+ */
+public class DuplicateTagException extends RuntimeException {
+ public DuplicateTagException() {
+ super("Operation would result in duplicate tags");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java b/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java
new file mode 100644
index 00000000000..febac1a415e
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/exceptions/TagNotFoundException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.tag.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified tag.
+ */
+public class TagNotFoundException extends RuntimeException {
+ public TagNotFoundException() {
+ super("Tag not found.");
+ }
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..f167ac5e553 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,14 +1,23 @@
package seedu.address.model.util;
+import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Deadline;
+import seedu.address.model.person.DeadlineList;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -18,25 +27,41 @@
*/
public class SampleDataUtil {
public static Person[] getSamplePersons() {
- return new Person[] {
+ return new Person[]{
new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
+ new Address("Blk 30 Geylang Street 29, #06-40"), new DeadlineList(), Notes.getNewNotes(),
+ getTagSet("friends"), Favourite.NOT_FAVOURITE, HighImportance.HIGH_IMPORTANCE,
+ new ImageDetailsList()),
new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new DeadlineList(),
+ Notes.getNewNotes(), getTagSet("colleagues", "friends"), Favourite.NOT_FAVOURITE,
+ HighImportance.NOT_HIGH_IMPORTANCE, new ImageDetailsList()),
new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new DeadlineList(), Notes.getNewNotes(),
+ getTagSet("neighbours"), Favourite.NOT_FAVOURITE, HighImportance.NOT_HIGH_IMPORTANCE,
+ new ImageDetailsList()),
new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new DeadlineList(),
+ Notes.getNewNotes(), getTagSet("family"), Favourite.NOT_FAVOURITE,
+ HighImportance.NOT_HIGH_IMPORTANCE, new ImageDetailsList()),
new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
+ new Address("Blk 47 Tampines Street 20, #17-35"), new DeadlineList(), Notes.getNewNotes(),
+ getTagSet("classmates"), Favourite.NOT_FAVOURITE, HighImportance.NOT_HIGH_IMPORTANCE,
+ new ImageDetailsList()),
new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
+ new Address("Blk 45 Aljunied Street 85, #11-31"), new DeadlineList(), Notes.getNewNotes(),
+ getTagSet("colleagues"), Favourite.NOT_FAVOURITE, HighImportance.NOT_HIGH_IMPORTANCE,
+ new ImageDetailsList())
+ };
+ }
+
+ public static Tag[] getSampleTags() {
+ return new Tag[]{
+ new Tag("friends"),
+ new Tag("colleagues"),
+ new Tag("neighbours"),
+ new Tag("classmates"),
+ new Tag("family")
};
}
@@ -45,6 +70,11 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
for (Person samplePerson : getSamplePersons()) {
sampleAb.addPerson(samplePerson);
}
+
+ for (Tag sampleTag : getSampleTags()) {
+ sampleAb.addTag(sampleTag);
+ }
+
return sampleAb;
}
@@ -57,4 +87,28 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ public static DeadlineList getDeadlineList(String[] strings) {
+ ArrayList deadlines = new ArrayList<>();
+ for (String string : strings) {
+ String trimmedDeadline = string.trim();
+ StringBuilder originalInput = new StringBuilder(trimmedDeadline);
+ originalInput.reverse();
+ String[] reversedAndSplit = originalInput.toString().split("\\s+", 2);
+ String description = new StringBuilder(reversedAndSplit[1]).reverse().toString();
+ String date = new StringBuilder(reversedAndSplit[0]).reverse().toString();
+ deadlines.add(new Deadline(description, date));
+ }
+ return new DeadlineList(deadlines);
+ }
+
+ /**
+ * Returns an image details list containing the list of image details given
+ */
+ public static ImageDetailsList getImageDetailsList(String... imageDetails) {
+ return new ImageDetailsList(Arrays.stream(imageDetails)
+ .map(File::new)
+ .map(ImageDetails::new)
+ .collect(Collectors.toList()));
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java b/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java
new file mode 100644
index 00000000000..eada6b95275
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedDeadline.java
@@ -0,0 +1,31 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Deadline;
+
+class JsonAdaptedDeadline {
+ private final String description;
+ private final String date;
+
+ @JsonCreator
+ public JsonAdaptedDeadline(@JsonProperty("description") String description,
+ @JsonProperty("date") String date) {
+ this.description = description;
+ this.date = date;
+ }
+
+ public JsonAdaptedDeadline(Deadline source) {
+ description = source.description;
+ date = source.value;
+ }
+
+ public Deadline toModelType() throws IllegalValueException {
+ if (!Deadline.isValidDeadline(description, date)) {
+ throw new IllegalValueException(Deadline.MESSAGE_CONSTRAINTS);
+ }
+ return new Deadline(description, date);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedImageDetails.java b/src/main/java/seedu/address/storage/JsonAdaptedImageDetails.java
new file mode 100644
index 00000000000..1a37a157663
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedImageDetails.java
@@ -0,0 +1,43 @@
+package seedu.address.storage;
+
+import java.io.File;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.image.ImageDetails;
+
+public class JsonAdaptedImageDetails {
+
+ private final String imageFilePath;
+
+ /**
+ * Constructs a {@code JsonAdaptedImage} with the given {@code imageFilePath}.
+ */
+ @JsonCreator
+ public JsonAdaptedImageDetails(String imageFilePath) {
+ this.imageFilePath = imageFilePath;
+ }
+
+ /**
+ * Converts a given {@code source} into this class for Jackson use.
+ */
+ public JsonAdaptedImageDetails(ImageDetails source) {
+ imageFilePath = source.getPath();
+ }
+
+ @JsonValue
+ public String getImageFilePath() {
+ return imageFilePath;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted imageDetails object into the model's {@code ImageDetails} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted imageDetails.
+ */
+ public ImageDetails toModelType() throws IllegalValueException {
+ return new ImageDetails(new File(imageFilePath));
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index a6321cec2ea..2c55f6d93c1 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,9 +10,16 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Deadline;
+import seedu.address.model.person.DeadlineList;
import seedu.address.model.person.Email;
+import seedu.address.model.person.Favourite;
+import seedu.address.model.person.HighImportance;
import seedu.address.model.person.Name;
+import seedu.address.model.person.Notes;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -28,22 +35,43 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final List deadlines = new ArrayList<>();
+ private final List notes = new ArrayList<>();
private final List tagged = new ArrayList<>();
+ private final String isFavourite;
+ private final List images = new ArrayList<>();
+ private final String hasHighImportance;
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
*/
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
- @JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tagged") List tagged) {
+ @JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("deadlines") List deadlines,
+ @JsonProperty("notes") List notes,
+ @JsonProperty("tagged") List tagged,
+ @JsonProperty("isFavourite") String isFavourite,
+ @JsonProperty("images") List images,
+ @JsonProperty("hasHighImportance") String hasHighImportance) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ if (deadlines != null) {
+ this.deadlines.addAll(deadlines);
+ }
+ if (notes != null) {
+ this.notes.addAll(notes);
+ }
if (tagged != null) {
this.tagged.addAll(tagged);
}
+ this.isFavourite = isFavourite;
+ if (images != null) {
+ this.images.addAll(images);
+ }
+ this.hasHighImportance = hasHighImportance;
}
/**
@@ -54,9 +82,18 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ deadlines.addAll(source.getDeadlines().getDeadlines().stream()
+ .map(JsonAdaptedDeadline::new)
+ .collect(Collectors.toList()));
+ notes.addAll(source.getNotes().value);
+ isFavourite = source.getFavouriteStatus().value;
+ hasHighImportance = source.getHighImportanceStatus().value;
tagged.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ images.addAll(source.getImageDetailsList().getImages().stream()
+ .map(JsonAdaptedImageDetails::new)
+ .collect(Collectors.toList()));
}
/**
@@ -70,8 +107,14 @@ public Person toModelType() throws IllegalValueException {
personTags.add(tag.toModelType());
}
+ final List personDeadlines = new ArrayList<>();
+ for (JsonAdaptedDeadline deadline : deadlines) {
+ personDeadlines.add(deadline.toModelType());
+ }
+
if (name == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ Name.class.getSimpleName()));
}
if (!Name.isValidName(name)) {
throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
@@ -102,8 +145,37 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ final Notes modelNotes = Notes.loadNotesFromList(notes);
+
+ if (isFavourite == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, Favourite.class.getSimpleName()));
+ }
+ if (!Favourite.isValidFavourite(isFavourite)) {
+ throw new IllegalValueException(Favourite.MESSAGE_CONSTRAINTS);
+ }
+ final Favourite modelFavourite = Favourite.valueOf(isFavourite);
+
+ if (hasHighImportance == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, HighImportance.class.getSimpleName()));
+ }
+ if (!HighImportance.isValidHighImportance(hasHighImportance)) {
+ throw new IllegalValueException(HighImportance.MESSAGE_CONSTRAINTS);
+ }
+ final HighImportance modelHighImportance = HighImportance.valueOf(hasHighImportance);
+
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ final DeadlineList modelDeadlines = new DeadlineList(personDeadlines);
+
+ final List personImages = new ArrayList<>();
+ for (JsonAdaptedImageDetails image : images) {
+ personImages.add(image.toModelType());
+ }
+ final ImageDetailsList modelImages = new ImageDetailsList(personImages);
+
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelDeadlines,
+ modelNotes, modelTags, modelFavourite, modelHighImportance, modelImages);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..947e75746b7 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -12,6 +12,7 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
* An Immutable AddressBook that is serializable to JSON format.
@@ -20,15 +21,19 @@
class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_TAG = "Tags list contain duplicate tag(s).";
private final List persons = new ArrayList<>();
+ private final List tags = new ArrayList<>();
/**
* Constructs a {@code JsonSerializableAddressBook} with the given persons.
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
+ public JsonSerializableAddressBook(@JsonProperty("persons") List persons,
+ @JsonProperty("tags") List tags) {
this.persons.addAll(persons);
+ this.tags.addAll(tags);
}
/**
@@ -38,6 +43,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List {
private static final String FXML = "CommandBox.fxml";
private final CommandExecutor commandExecutor;
+ private final HistoryGetter historyGetter;
+ private int historyPosition = 0;
@FXML
private TextField commandTextField;
@@ -24,9 +31,10 @@ public class CommandBox extends UiPart {
/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
*/
- public CommandBox(CommandExecutor commandExecutor) {
+ public CommandBox(CommandExecutor commandExecutor, HistoryGetter historyGetter) {
super(FXML);
this.commandExecutor = commandExecutor;
+ this.historyGetter = historyGetter;
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
}
@@ -36,6 +44,8 @@ public CommandBox(CommandExecutor commandExecutor) {
*/
@FXML
private void handleCommandEntered() {
+ historyPosition = 0; // Reset history pointer on new input
+
String commandText = commandTextField.getText();
if (commandText.equals("")) {
return;
@@ -49,6 +59,37 @@ private void handleCommandEntered() {
}
}
+ @FXML
+ private void handleKeyPressed(KeyEvent e) {
+ if (e.getCode().equals(KeyCode.UP)) {
+ getHistory(+1);
+ }
+ if (e.getCode().equals(KeyCode.DOWN)) {
+ getHistory(-1);
+ }
+ }
+
+ private void getHistory(int step) {
+ int expectedHistoryPosition = historyPosition + step;
+
+ if (expectedHistoryPosition < 0) {
+ return;
+ }
+
+ if (expectedHistoryPosition == 0) {
+ commandTextField.setText("");
+ historyPosition = expectedHistoryPosition;
+ return;
+ }
+
+ CommandHistoryEntry commandText = requireNonNull(historyGetter.getCommandText(expectedHistoryPosition));
+ if (commandText.exists()) {
+ historyPosition = expectedHistoryPosition;
+ commandTextField.setText(commandText.getCommandText());
+ }
+ }
+
+
/**
* Sets the command box style to use the default style.
*/
@@ -82,4 +123,17 @@ public interface CommandExecutor {
CommandResult execute(String commandText) throws CommandException, ParseException;
}
+ /**
+ * Represents a function that can get history from the command history.
+ */
+ @FunctionalInterface
+ public interface HistoryGetter {
+ /**
+ * Retrieves a command from the command history
+ *
+ * @see seedu.address.logic.Logic#getCommandText(int)
+ */
+ CommandHistoryEntry getCommandText(int i);
+ }
+
}
diff --git a/src/main/java/seedu/address/ui/CopyUrlWindow.java b/src/main/java/seedu/address/ui/CopyUrlWindow.java
new file mode 100644
index 00000000000..4c42fa1e853
--- /dev/null
+++ b/src/main/java/seedu/address/ui/CopyUrlWindow.java
@@ -0,0 +1,79 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.stage.Stage;
+import seedu.address.commons.core.LogsCenter;
+
+public class CopyUrlWindow extends UiPart {
+
+ public static final String SUCCESS_MESSAGE = "URL successfully copied!";
+
+ private static final Logger logger = LogsCenter.getLogger(CopyUrlWindow.class);
+ private static final String FXML = "CopyUrlWindow.fxml";
+
+
+ @FXML
+ private Label successMessage;
+
+ /**
+ * Creates a new CopyUrlWindow.
+ */
+ public CopyUrlWindow(Stage root) {
+ super(FXML, root);
+ successMessage.setText(SUCCESS_MESSAGE);
+ }
+
+ /**
+ * Creates a new CopyUrlWindow.
+ */
+ public CopyUrlWindow() {
+ this(new Stage());
+ }
+
+ /**
+ * Shows the help window.
+ * @throws IllegalStateException
+ *
+ * -
+ * if this method is called on a thread other than the JavaFX Application Thread.
+ *
+ * -
+ * if this method is called during animation or layout processing.
+ *
+ * -
+ * if this method is called on the primary stage.
+ *
+ * -
+ * if {@code dialogStage} is already showing.
+ *
+ *
+ */
+ public void show() {
+ logger.fine("Showing message that url has been copied successfully.");
+ getRoot().show();
+ }
+
+ /**
+ * Returns true if the help window is currently being shown.
+ */
+ public boolean isShowing() {
+ return getRoot().isShowing();
+ }
+
+ /**
+ * Hides the help window.
+ */
+ public void hide() {
+ getRoot().hide();
+ }
+
+ /**
+ * Focuses on the help window.
+ */
+ public void focus() {
+ getRoot().requestFocus();
+ }
+}
diff --git a/src/main/java/seedu/address/ui/DetailedContactPanel.java b/src/main/java/seedu/address/ui/DetailedContactPanel.java
new file mode 100644
index 00000000000..ce37553202e
--- /dev/null
+++ b/src/main/java/seedu/address/ui/DetailedContactPanel.java
@@ -0,0 +1,42 @@
+package seedu.address.ui;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.model.person.Person;
+
+public class DetailedContactPanel extends UiPart {
+ private static final String FXML = "DetailedContactPanel.fxml";
+
+ @FXML
+ private ListView detailedContactViewPanel;
+
+ /**
+ * Creates a {@code DetailedContactPanel} with the given {@code ObservableList}.
+ */
+ public DetailedContactPanel(ObservableList detailedContactView) {
+ super(FXML);
+ detailedContactViewPanel.setItems(detailedContactView);
+ detailedContactViewPanel.setCellFactory(listView -> new DetailedPersonCardCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Person} using a
+ * {@code DetailedPersonCard}.
+ */
+ class DetailedPersonCardCell extends ListCell {
+ @Override
+ protected void updateItem(Person person, boolean empty) {
+ super.updateItem(person, empty);
+
+ if (empty || person == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new DetailedPersonCard(person).getRoot());
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/DetailedPersonCard.java b/src/main/java/seedu/address/ui/DetailedPersonCard.java
new file mode 100644
index 00000000000..53a33a06219
--- /dev/null
+++ b/src/main/java/seedu/address/ui/DetailedPersonCard.java
@@ -0,0 +1,136 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+import javafx.fxml.FXML;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.TilePane;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
+import seedu.address.model.person.Person;
+
+/**
+ * A UI component that shows a full, detailed view of a Person
+ */
+public class DetailedPersonCard extends UiPart {
+ private static final String FXML = "DetailedPersonCard.fxml";
+ private static final int MAXIMUM_IMAGES_TO_DISPLAY = 6;
+
+ @FXML
+ private Label name;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label address;
+ @FXML
+ private Label email;
+ @FXML
+ private VBox notesPane;
+ @FXML
+ private Label notesTitle;
+ @FXML
+ private Label notes;
+ @FXML
+ private VBox deadlinesPane;
+ @FXML
+ private Label deadlinesTitle;
+ @FXML
+ private Label deadlines;
+ @FXML
+ private FlowPane tags;
+ @FXML
+ private Canvas starCanvas;
+ @FXML
+ private ImageView flagImageView;
+ @FXML
+ private TilePane imageListView;
+ @FXML
+ private Label moreImages;
+
+ private final Image highImportanceFlag = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/red_flag.png")));
+ private final Image notHighImportanceFlag = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/white_flag.png")));
+
+ /**
+ * Creates a {@code DetailedPersonCard} with the given {@code Person} to display
+ * @param person the person to display
+ */
+ public DetailedPersonCard(Person person) {
+ super(FXML);
+
+ name.setText(person.getName().fullName);
+ phone.setText(person.getPhone().value);
+ address.setText(person.getAddress().value);
+ email.setText(person.getEmail().value);
+ notes.setText(person.getNotes().listFormat());
+ deadlines.setText(person.getDeadlines().listFormat());
+ deadlinesTitle.setText("Deadlines");
+ notesTitle.setText("Notes");
+
+ person.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ if (person.isFavourite()) {
+ starCanvas.setVisible(true);
+ drawStarShape(starCanvas.getGraphicsContext2D());
+ }
+
+ // Red flag if importance, otherwise empty plain flag
+ if (person.hasHighImportance()) {
+ flagImageView.setImage(highImportanceFlag);
+ } else {
+ flagImageView.setImage(notHighImportanceFlag);
+ }
+ flagImageView.setFitHeight(20);
+ flagImageView.setFitWidth(20);
+
+ setImageListView(person.getImageDetailsList());
+ }
+
+ private void setImageListView(ImageDetailsList images) {
+ double totalWidth = this.getRoot().getWidth();
+ int individualWidth = (int) totalWidth / 3;
+
+ for (int i = 0; i < Math.min(images.size(), MAXIMUM_IMAGES_TO_DISPLAY); i++) {
+ Index index = Index.fromZeroBased(i);
+ ImageDetails imageDetails = images.get(index.getZeroBased());
+ ImageCard imageCard = new ImageCard(index.getOneBased(), imageDetails, 120, individualWidth);
+ imageListView.getChildren().add(imageCard.getRoot());
+ }
+
+ if (images.size() > MAXIMUM_IMAGES_TO_DISPLAY) {
+ moreImages.setText(String.format("... and %d more images. Use the images command to see everything.",
+ images.size() - MAXIMUM_IMAGES_TO_DISPLAY));
+ moreImages.setVisible(true);
+ }
+ }
+
+ private void drawStarShape(GraphicsContext gc) {
+ //@@author takufunkai-reused
+ //Reused from https://zetcode.com/gui/javafx/canvas/
+ // with minor modifications to the points and fill, for suitable colour and size.
+
+ double[] xpoints = {1, 7, 9, 11, 17, 13, 14, 9, 4, 5};
+ double[] ypoints = {7, 6.5, 1, 6.5, 7, 10, 15, 12, 15, 10};
+
+ gc.setFill(Color.YELLOW);
+ gc.fillPolygon(xpoints, ypoints, xpoints.length);
+
+ gc.setStroke(Color.BLACK);
+ gc.strokePolygon(xpoints, ypoints, xpoints.length);
+
+ //@@author
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 9a665915949..95ae9a3bf88 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,17 +15,32 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
- public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
+ public static final String HELPWINDOW_MESSAGE = "Need help? Check out any of the links below!";
+ public static final String USERGUIDE_URL = "https://ay2122s2-cs2103t-t12-2.github.io/tp/UserGuide.html#quick-start";
+ public static final String USER_GUIDE_HELP_MESSAGE = "User Guide: " + USERGUIDE_URL;
+ public static final String COMMAND_SUMMARY_URL =
+ "https://ay2122s2-cs2103t-t12-2.github.io/tp/UserGuide.html#command-summary";
+ public static final String COMMAND_SUMMARY_HELP_MESSAGE = "Command Summary: " + COMMAND_SUMMARY_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
private static final String FXML = "HelpWindow.fxml";
+ private CopyUrlWindow copyUrlWindow;
+
+ @FXML
+ private Label helpWindowMessage;
+
@FXML
- private Button copyButton;
+ private Button userGuideCopyButton;
@FXML
- private Label helpMessage;
+ private Label userGuideHelpMessage;
+
+ @FXML
+ private Button commandSummaryCopyButton;
+
+ @FXML
+ private Label commandSummaryHelpMessage;
/**
* Creates a new HelpWindow.
@@ -34,7 +49,11 @@ public class HelpWindow extends UiPart {
*/
public HelpWindow(Stage root) {
super(FXML, root);
- helpMessage.setText(HELP_MESSAGE);
+ userGuideHelpMessage.setText(USER_GUIDE_HELP_MESSAGE);
+ commandSummaryHelpMessage.setText(COMMAND_SUMMARY_HELP_MESSAGE);
+ helpWindowMessage.setText(HELPWINDOW_MESSAGE);
+
+ copyUrlWindow = new CopyUrlWindow();
}
/**
@@ -80,6 +99,7 @@ public boolean isShowing() {
*/
public void hide() {
getRoot().hide();
+ copyUrlWindow.hide();
}
/**
@@ -93,10 +113,35 @@ public void focus() {
* Copies the URL to the user guide to the clipboard.
*/
@FXML
- private void copyUrl() {
+ private void copyUserGuideUrl() {
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent url = new ClipboardContent();
url.putString(USERGUIDE_URL);
clipboard.setContent(url);
+ handleCopyUrlSuccess();
+ }
+
+ /**
+ * Copies the URL to the user guide to the clipboard.
+ */
+ @FXML
+ private void copyCommandSummaryUrl() {
+ final Clipboard clipboard = Clipboard.getSystemClipboard();
+ final ClipboardContent url = new ClipboardContent();
+ url.putString(COMMAND_SUMMARY_URL);
+ clipboard.setContent(url);
+ handleCopyUrlSuccess();
+ }
+
+ /**
+ * Opens the copy url success window or focuses on it if it's already opened.
+ */
+ @FXML
+ public void handleCopyUrlSuccess() {
+ if (!copyUrlWindow.isShowing()) {
+ copyUrlWindow.show();
+ } else {
+ copyUrlWindow.focus();
+ }
}
}
diff --git a/src/main/java/seedu/address/ui/ImageCard.java b/src/main/java/seedu/address/ui/ImageCard.java
new file mode 100644
index 00000000000..122134cd2f9
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ImageCard.java
@@ -0,0 +1,70 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.effect.ColorAdjust;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Region;
+import seedu.address.model.image.ImageDetails;
+
+public class ImageCard extends UiPart {
+
+ private static final String FXML = "ImageCard.fxml";
+
+ public final ImageDetails imageDetails;
+ public final Image image;
+
+ @FXML
+ private Label indexLabel;
+ @FXML
+ private ImageView imageView;
+
+ private ColorAdjust darkened;
+ private ImageWindow imageWindow;
+
+ /**
+ * Creates an ImageCard component, used to display in the image list pane
+ *
+ * @param index the index of this imageCard relative to its parent image list pane
+ * @param imageDetails the details of the image to be displayed
+ * @param height height of the image
+ * @param width width of the image
+ */
+ public ImageCard(int index, ImageDetails imageDetails, int height, int width) {
+ super(FXML);
+ this.imageDetails = imageDetails;
+ indexLabel.setText(String.valueOf(index));
+ image = new Image(imageDetails.getJavaFxImageUrl(), width, height, true, true);
+ imageView.setImage(image);
+
+ darkened = new ColorAdjust();
+ darkened.setBrightness(-0.2);
+ }
+
+ @FXML
+ private void handleClick() {
+ Image image = new Image(imageDetails.getJavaFxImageUrl());
+ if (imageWindow == null) {
+ imageWindow = new ImageWindow(image);
+ imageWindow.show();
+ }
+
+ if (imageWindow.isShowing()) {
+ imageWindow.focus();
+ } else {
+ imageWindow.show();
+ }
+ }
+
+ @FXML
+ public void handleEnter() {
+ this.imageView.setEffect(darkened);
+ }
+
+ @FXML
+ public void handleExit() {
+ this.imageView.setEffect(null);
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/ImageViewPanel.java b/src/main/java/seedu/address/ui/ImageViewPanel.java
new file mode 100644
index 00000000000..a91936a8704
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ImageViewPanel.java
@@ -0,0 +1,33 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.TilePane;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.image.ImageDetails;
+import seedu.address.model.image.ImageDetailsList;
+
+public class ImageViewPanel extends UiPart {
+ private static final String FXML = "ImageViewPanel.fxml";
+
+ @FXML
+ private TilePane imageListView;
+
+ /**
+ * Creates a {@code ImageViewPanel} with the given {@code ImageDetailsList}.
+ */
+ public ImageViewPanel(ImageDetailsList images) {
+ super(FXML);
+
+ double totalWidth = this.getRoot().getWidth();
+ int individualWidth = (int) totalWidth / 3;
+
+ for (int i = 0; i < images.size(); i++) {
+ Index index = Index.fromZeroBased(i);
+ ImageDetails imageDetails = images.get(index.getZeroBased());
+ ImageCard imageCard = new ImageCard(index.getOneBased(), imageDetails, 120, individualWidth);
+ imageListView.getChildren().add(imageCard.getRoot());
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/ImageWindow.java b/src/main/java/seedu/address/ui/ImageWindow.java
new file mode 100644
index 00000000000..a4b938df579
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ImageWindow.java
@@ -0,0 +1,81 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.fxml.FXML;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.stage.Stage;
+import seedu.address.commons.core.LogsCenter;
+
+public class ImageWindow extends UiPart {
+
+ private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
+ private static final String FXML = "ImageWindow.fxml";
+
+ @FXML
+ private ImageView imageView;
+
+ /**
+ * Creates a new ImageWindow.
+ *
+ * @param root Stage to use as the root of the ImageWindow.
+ */
+ public ImageWindow(Stage root, Image image) {
+ super(FXML, root);
+ this.imageView.setImage(image);
+ }
+
+ /**
+ * Creates a new ImageWindow.
+ */
+ public ImageWindow(Image image) {
+ this(new Stage(), image);
+ }
+
+ /**
+ * Shows the image window.
+ * @throws IllegalStateException
+ *
+ * -
+ * if this method is called on a thread other than the JavaFX Application Thread.
+ *
+ * -
+ * if this method is called during animation or layout processing.
+ *
+ * -
+ * if this method is called on the primary stage.
+ *
+ * -
+ * if {@code dialogStage} is already showing.
+ *
+ *
+ */
+ public void show() {
+ logger.fine("Showing full image.");
+ getRoot().show();
+ getRoot().centerOnScreen();
+ }
+
+ /**
+ * Returns true if the image window is currently being shown.
+ */
+ public boolean isShowing() {
+ return getRoot().isShowing();
+ }
+
+ /**
+ * Hides the image window.
+ */
+ public void hide() {
+ getRoot().hide();
+ }
+
+ /**
+ * Focuses on the image window.
+ */
+ public void focus() {
+ getRoot().requestFocus();
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..b83a5d44357 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -16,6 +16,8 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.commandhistory.CommandHistoryEntry;
+import seedu.address.model.image.ImageDetailsList;
/**
* The Main Window. Provides the basic application layout containing
@@ -32,9 +34,15 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private DetailedContactPanel detailedContactPanel;
+ private ImageViewPanel imageViewPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private enum Panel { PERSON_LIST, DETAILED_VIEW, IMAGE_VIEW }
+
+ private Panel panelInDisplay;
+
@FXML
private StackPane commandBoxPlaceholder;
@@ -42,7 +50,7 @@ public class MainWindow extends UiPart {
private MenuItem helpMenuItem;
@FXML
- private StackPane personListPanelPlaceholder;
+ private StackPane informationDisplayPanelPlaceholder;
@FXML
private StackPane resultDisplayPlaceholder;
@@ -78,6 +86,7 @@ private void setAccelerators() {
/**
* Sets the accelerator of a MenuItem.
+ *
* @param keyCombination the KeyCombination value of the accelerator
*/
private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
@@ -110,8 +119,17 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
- personListPanel = new PersonListPanel(logic.getFilteredPersonList());
- personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ personListPanel = new PersonListPanel(logic.getSortedPersonList(), logic.getActivatedTagList());
+ informationDisplayPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+
+ detailedContactPanel = new DetailedContactPanel(logic.getDetailedContactView());
+ informationDisplayPanelPlaceholder.getChildren().add(detailedContactPanel.getRoot());
+ detailedContactPanel.getRoot().setVisible(false);
+
+ ImageDetailsList list = logic.getImagesToView();
+ imageViewPanel = new ImageViewPanel(list);
+ informationDisplayPanelPlaceholder.getChildren().add(imageViewPanel.getRoot());
+ imageViewPanel.getRoot().setVisible(false);
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
@@ -119,10 +137,17 @@ void fillInnerParts() {
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
- CommandBox commandBox = new CommandBox(this::executeCommand);
+ CommandBox commandBox = new CommandBox(this::executeCommand, this::getCommandHistory);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
}
+ private void setPanel(Panel panelToShow) {
+ panelInDisplay = panelToShow;
+ personListPanel.getRoot().setVisible(panelToShow.equals(Panel.PERSON_LIST));
+ detailedContactPanel.getRoot().setVisible(panelToShow.equals(Panel.DETAILED_VIEW));
+ imageViewPanel.getRoot().setVisible(panelToShow.equals(Panel.IMAGE_VIEW));
+ }
+
/**
* Sets the default size based on {@code guiSettings}.
*/
@@ -167,6 +192,30 @@ public PersonListPanel getPersonListPanel() {
return personListPanel;
}
+ @FXML
+ private void handleDetailedView() {
+ setPanel(Panel.DETAILED_VIEW);
+ }
+
+ /**
+ * Loads the contact's images.
+ * Initializes a new ImageViewPanel for each new contact to clear the previous images.
+ */
+ @FXML
+ private void handleViewImages() {
+ ImageDetailsList list = logic.getImagesToView();
+ imageViewPanel = new ImageViewPanel(list);
+ informationDisplayPanelPlaceholder.getChildren().add(imageViewPanel.getRoot());
+ setPanel(Panel.IMAGE_VIEW);
+ }
+
+ /**
+ * Changes the panel view to the full contacts list view
+ */
+ private void handleListView() {
+ setPanel(Panel.PERSON_LIST);
+ }
+
/**
* Executes the command and returns the result.
*
@@ -174,16 +223,34 @@ public PersonListPanel getPersonListPanel() {
*/
private CommandResult executeCommand(String commandText) throws CommandException, ParseException {
try {
- CommandResult commandResult = logic.execute(commandText);
+ logic.cacheCommandText(commandText);
+
+ CommandResult commandResult = panelInDisplay == Panel.DETAILED_VIEW
+ ? logic.executeInDetailedViewMode(commandText)
+ : logic.execute(commandText);
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
- if (commandResult.isShowHelp()) {
- handleHelp();
- }
+ handleListView();
- if (commandResult.isExit()) {
+ switch (commandResult.getSpecialCommandResult()) {
+ case NONE:
+ break;
+ case SHOW_HELP:
+ handleHelp();
+ break;
+ case VIEW_IMAGES:
+ handleViewImages();
+ break;
+ case DETAILED_VIEW:
+ handleDetailedView();
+ break;
+ case EXIT:
handleExit();
+ break;
+ default:
+ logger.warning("Program execution should not reach here");
+ assert false;
}
return commandResult;
@@ -193,4 +260,13 @@ private CommandResult executeCommand(String commandText) throws CommandException
throw e;
}
}
+
+ /**
+ * Retrieves the command history from i-commands ago
+ * @param i the amount of commands to back-step
+ * @return the retrieved command text
+ */
+ private CommandHistoryEntry getCommandHistory(int i) {
+ return logic.getCommandText(i);
+ }
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 7fc927bc5d9..d922a28deda 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -1,12 +1,20 @@
package seedu.address.ui;
+import static javafx.application.Application.launch;
+
import java.util.Comparator;
+import java.util.Objects;
import javafx.fxml.FXML;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
import seedu.address.model.person.Person;
/**
@@ -39,10 +47,22 @@ public class PersonCard extends UiPart {
@FXML
private Label email;
@FXML
+ private Label notes;
+ @FXML
+ private Label deadlines;
+ @FXML
private FlowPane tags;
+ @FXML
+ private Canvas starCanvas;
+ @FXML
+ private ImageView flagImageView;
+ private final Image highImportanceFlag = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/red_flag.png")));
+ private final Image notHighImportanceFlag = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/white_flag.png")));
/**
- * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ * Creates a {@code PersonCard} with the given {@code Person} and index to display.
*/
public PersonCard(Person person, int displayedIndex) {
super(FXML);
@@ -51,10 +71,47 @@ public PersonCard(Person person, int displayedIndex) {
name.setText(person.getName().fullName);
phone.setText(person.getPhone().value);
address.setText(person.getAddress().value);
+ notes.setText(person.getNotes().listFormat());
email.setText(person.getEmail().value);
+ deadlines.setText(person.getDeadlines().listFormat());
person.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
.forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ if (person.isFavourite()) {
+ starCanvas.setVisible(true);
+ drawStarShape(starCanvas.getGraphicsContext2D());
+ }
+
+ // Red flag if importance, otherwise empty plain flag
+ if (person.hasHighImportance()) {
+ flagImageView.setImage(highImportanceFlag);
+ } else {
+ flagImageView.setImage(notHighImportanceFlag);
+ }
+ flagImageView.setFitHeight(20);
+ flagImageView.setFitWidth(20);
+ }
+
+ private void drawStarShape(GraphicsContext gc) {
+ //@@author takufunkai-reused
+ //Reused from https://zetcode.com/gui/javafx/canvas/
+ // with minor modifications to the points and fill, for suitable colour and size.
+
+ double[] xpoints = {1, 7, 9, 11, 17, 13, 14, 9, 4, 5};
+ double[] ypoints = {7, 6.5, 1, 6.5, 7, 10, 15, 12, 15, 10};
+
+ gc.setFill(Color.YELLOW);
+ gc.fillPolygon(xpoints, ypoints, xpoints.length);
+
+ gc.setStroke(Color.BLACK);
+ gc.strokePolygon(xpoints, ypoints, xpoints.length);
+
+ //@@author
+ }
+
+ public static void main(String[] args) {
+ launch(args);
}
@Override
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..f8fefe3ad83 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -1,17 +1,22 @@
package seedu.address.ui;
+import java.util.Comparator;
import java.util.logging.Logger;
+import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
+import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
+import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Region;
import seedu.address.commons.core.LogsCenter;
import seedu.address.model.person.Person;
+import seedu.address.model.tag.Tag;
/**
- * Panel containing the list of persons.
+ * Panel containing the list of persons or a detailed view of a person.
*/
public class PersonListPanel extends UiPart {
private static final String FXML = "PersonListPanel.fxml";
@@ -20,11 +25,26 @@ public class PersonListPanel extends UiPart {
@FXML
private ListView personListView;
+ @FXML
+ private FlowPane activatedTags;
+
/**
- * Creates a {@code PersonListPanel} with the given {@code ObservableList}.
+ * Creates a {@code PersonListPanel} with the given {@code ObservableList} of persons and activated tags
*/
- public PersonListPanel(ObservableList personList) {
+ public PersonListPanel(ObservableList personList, ObservableList activatedTagList) {
super(FXML);
+ activatedTagList.addListener((ListChangeListener) change -> {
+ while (change.next()) {
+ activatedTags.getChildren().clear();
+ activatedTagList.stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> activatedTags.getChildren().add(new Label(tag.tagName)));
+
+ }
+ });
+ activatedTagList.stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> activatedTags.getChildren().add(new Label(tag.tagName)));
personListView.setItems(personList);
personListView.setCellFactory(listView -> new PersonListViewCell());
}
@@ -45,5 +65,4 @@ protected void updateItem(Person person, boolean empty) {
}
}
}
-
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..6c1ce5123f0 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/d_interieur.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
deleted file mode 100644
index 29810cf1fd9..00000000000
Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ
diff --git a/src/main/resources/images/checked.png b/src/main/resources/images/checked.png
new file mode 100644
index 00000000000..d3998392dec
Binary files /dev/null and b/src/main/resources/images/checked.png differ
diff --git a/src/main/resources/images/d_interieur.png b/src/main/resources/images/d_interieur.png
new file mode 100644
index 00000000000..ea8193de51b
Binary files /dev/null and b/src/main/resources/images/d_interieur.png differ
diff --git a/src/main/resources/images/red_flag.png b/src/main/resources/images/red_flag.png
new file mode 100644
index 00000000000..ba5cbe5e0a9
Binary files /dev/null and b/src/main/resources/images/red_flag.png differ
diff --git a/src/main/resources/images/white_flag.png b/src/main/resources/images/white_flag.png
new file mode 100644
index 00000000000..a56d4ce12c4
Binary files /dev/null and b/src/main/resources/images/white_flag.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 09f6d6fe9e4..d700c063d96 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -4,6 +4,6 @@
-
+
diff --git a/src/main/resources/view/CopyUrlWindow.css b/src/main/resources/view/CopyUrlWindow.css
new file mode 100644
index 00000000000..c2ae5bafe47
--- /dev/null
+++ b/src/main/resources/view/CopyUrlWindow.css
@@ -0,0 +1,19 @@
+.Button, Label {
+ -fx-text-fill: white;
+}
+
+.Button {
+ -fx-background-color: dimgray;
+}
+
+.Button:hover {
+ -fx-background-color: gray;
+}
+
+.Button:armed {
+ -fx-background-color: darkgray;
+}
+
+#successMessageContainer {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
diff --git a/src/main/resources/view/CopyUrlWindow.fxml b/src/main/resources/view/CopyUrlWindow.fxml
new file mode 100644
index 00000000000..5053390f232
--- /dev/null
+++ b/src/main/resources/view/CopyUrlWindow.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+