diff --git a/.gitignore b/.gitignore
index 71c9194e8bd..7e3a21383d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+
+# Python test script
+*.py
diff --git a/README.md b/README.md
index 13f5c77403f..36d51c40ff6 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,19 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+[](https://github.com/AY2122S2-CS2103-F09-2/tp/actions)

-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+_Find yourself having trouble managing all the important friendships in your university life?_
+
+**Amigos** is here to help!
+
+With **Amigos**, you can:
+* Keep track of important details about your friends
+* Maintain your friendships by keeping up-to-date logs about your friends, such as their likes/dislikes, aspirations and so on!
+* Remember key events you are doing with friends
+* Gain valuable insights about your friends, like the last time you met up with them.
+
+Amigos is also _super fast_ to use, and optimized for keyboard input while still having the benefits of a Graphical User Interface (GUI).
+
+For more information on how to get started, check out the **[Amigos User Guide](https://ay2122s2-cs2103-f09-2.github.io/tp/UserGuide.html)**.
+
+**Acknowledgements**: This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
diff --git a/build.gradle b/build.gradle
index be2d2905dde..9562f9e77ae 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,6 +16,10 @@ repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
+run {
+ enableAssertions = true
+}
+
checkstyle {
toolVersion = '8.29'
}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 4c001417aea..feb2db1160c 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -29,10 +29,7 @@
-
-
-
-
+
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..d1ca710ff04 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -5,55 +5,59 @@ title: About Us
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
-You can reach us at the email `seer[at]comp.nus.edu.sg`
-
## Project team
-### John Doe
+### Aryan Sarswat
+
+
-
+[[github](https://github.com/AryanSarswat)]
+[[portfolio](team/aryansarswat.md)]
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+* Role: Developer
+* Responsibilities:
+ * IC for UI and Integration
+ * Implement, test and maintain `editfriend`, `showfriends`
+ * Implement, test and maintain Tab management feature
+ * Create python script to stress test application
-* Role: Project Advisor
+### Dione Goh
-### Jane Doe
+
-
+[[github](http://github.com/dionegoh)]
+[[portfolio](team/dionegoh.md)]
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+* Role: Developer
+* Responsibilities:
+ * IC for Logic and code quality
+ * Implement, test and maintain all features related to `friends` feature
-* Role: Team Lead
-* Responsibilities: UI
-### Johnny Doe
+### Lim Wei Liang
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/limweiliang)] [[portfolio](team/limweiliang.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities:
+ * Implement `Event` related commands
+ * Update relevant documentation
+ * Monitor changes to `Model` and Documentation
-### Jean Doe
+### Naaman Tan
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/tanyjnaaman)]
+[[portfolio](team/tanyjnaaman.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
-
-### James Doe
+* Responsibilities:
+ * IC for testing and storage
+ * Implement, test and maintain all features related to `insights` feature
+ * Implement, test and maintain all features related to `logs` feature
-
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
-* Role: Developer
-* Responsibilities: UI
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..b7f620170e8 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -36,7 +36,7 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for,
+**`Main`** has two classes called [`Main`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for,
* At app launch: Initializes the components in the correct sequence, and connects them up with each other.
* At shut down: Shuts down the components and invokes cleanup methods where necessary.
@@ -69,24 +69,24 @@ The sections below give more details of each component.
### UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/ui/Ui.java)

The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
* executes user commands using the `Logic` component.
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
-* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+* depends on some classes in the `Model` component, as it displays `Person` and `Event` objects residing in the `Model`.
### Logic component
-**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** : [`Logic.java`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
@@ -114,15 +114,15 @@ How the parsing works:
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
### Model component
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+**API** : [`Model.java`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/model/Model.java)
-
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
+* stores the data for Amigos, primarily through the `Person`, `Log` and `Event` classes. These classes are each contained in their respective `Unique{Object}List` object.
+* contains `Insight` classes, which store summary details about friendships, are generated using the current Model data on demand. They are encapsulated within a `PersonInsight` object for each `Person` in Amigos.
+* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. This implementation also applies to `Event` objects.
* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
@@ -132,12 +132,11 @@ The `Model` component,
-
### Storage component
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/AY2122S2-CS2103-F09-2/tp/tree/master/src/main/java/seedu/address/storage/Storage.java)
-
+
The `Storage` component,
* can save both address book data and user preference data in json format, and read them back into corresponding objects.
@@ -154,90 +153,413 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### 0. Accessing friends by name or by index
-#### Proposed Implementation
+To improve CLI comfort, _Amigos_ supports accessing of persons by both index in the list and by name, so that users who know names by heart/can remember indices as they switch between tabs would have an easy time.
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+This by-index-by-name feature is supported for:
+- Add/Edit/Delete friend
+- Add/Edit/Delete log
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+#### 0.1 Implementation
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+To support adding logs by the name of a friend or the index in _Amigos_, `ByIndexByNameCommand` is implemented as
+a parent class that encapsulates methods useful to find the specified `Person` in the model.
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+#### 0.2 Main design consideration: in `Model` or `Command`?
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+There were two main approaches considered to "grouping" the by-index-by-name nature of these commands. The first was to do what we did and use a parent `Command` class to encapsulate the methods,
+or to implement methods at the `Model` level. Both have arguments for that are valid.
-
+Using a parent class was chosen primarily for error handling - having a model-based method of retrieval by name or index would mean that the respective commands would have to handle errors thrown by
+the model. This made less sense (in our view) for two reasons.
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+* The first is that the errors occur at command execution time, and it would make most sense to be thrown directly by an object of the command class, so
+doing it by the model entailed creating model exception classes, which would be thrown by the model, caught by the command, then thrown again by the command - tedious!
-
+* The second reason is the duplication of code - each command would individually have to catch and handle and call methods. Seemed unnecessary.
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+### 1. Friends Feature
-
+#### 1.1 Friends' representation in the model and storage
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+A friend is a `Person` containing attributes - `FriendName`, `Phone`, `Address`, `Email`, `Description`, `Set`
+containing a set of `Tag` objects and a `UniqueLogList` containing a list of `Log` objects. Relevant implementations in the
+model and storage follow from it.
-
+#### 1.2 Execution of command for adding, deleting or editing details of a friend
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+All three (add, edit, delete) friends commands work the same at a high level.
-
+1. `Parser` parses input
+2. (Optional) Details wrapped into a `{Type}Descriptor` / `{Type}Person`
+3. `{Type}Command` takes in descriptor and executes with mode
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+##### 1.2.1 Choice of using descriptor subclasses
-
+As with a general command, the friend-related commands could have put all the logic into the `Command::execute` method.
-The following sequence diagram shows how the undo operation works:
+A conscious choice to use `Descriptor` objects arose from the complexity of editing person details - a lot could be edited,
+and having it all in the `Command::execute` implementation could lead to verbose code.
+Having a parser decipher the specific action and wrapping error-handling logic to a `Descriptor` object allowed the
+implementation to be clean and easily extendable.
-
-
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-
-
+##### 1.2.2 A concrete example: `addfriend`
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+The implementation of adding a friend into Amigos is facilitated by the `AddCommand`, `AddCommandParser` in the `Logic` component,
+`UniquePersonList` and `Person` classes in the `Model` component, and `JsonAdaptedPerson` in the `Storage` component.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+Given below is an example usage scenario and how the `Logic` and `Model` components behave at every
+step.
-
+1. User keys in a valid `AddCommand` `e.g. addfriend n/John Doe t/Friend` into the command box of the GUI.
+2. `AddressBookParser` calls `AddCommandParser::parse` and parses the input.
+ - `AddCommandParser::parse` converts the arguments entered by user into `Person` attributes by calling
+ the`ParserUtil`class. It then instantiates a new `Person` with the given attributes returned by `ParserUtil`.
+ (`ParserUtil` checks for the validity of the inputs according to the respective attribute constraints.
+ Next, an `AddCommand` is created with the new `Person` passed into it.
+3. When `AddCommand::execute` is called, the `Model` component will check if the `Person` to be added already
+ exists in Amigos. This check is done using `Model::hasPerson` which ultimately uses `Person::isSamePerson`
+ to check if two `Person` are equal by name only.
+4. If no duplicate person exists, then `Model::addPerson` will be called and the newly created `Person` will
+ be set into the `model` and added into the `UniquePersonList`.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+A sequence diagram showing the interactions between `AddCommand`, `AddCommandParser`, `ParserUtil` and `model`,
+after the user has entered a valid `FriendName`, `Phone`, and `Email`.
+
-
+#### 1.3 Storing optional fields in Person as "wrapped" nulls
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+Minimally, the `AddCommand` requires the user to enter the `FriendName` of the new `Person` to be added using the `n/` prefix.
+The other fields are optional. This is to allow flexibility for the user to add a friend into Amigos even if the user is unsure
+about certain particulars (e.g `Address`/ `Email`) of a friend.
-
+* **Current implementation**
+ - To allow this, we place a `null` value into the value of the optional attribute that was not provided by the user.
+ For example, if an address is not given, then for the newly created `Person` object `p`, `p.address.value` will be `null`.
+ - Whenever a `Person` object is instantiated, we ensure that all the attributes are non-null.
+
+* **Alternative implementation**
+ - An alternative way is to simply pass `null` directly into the `Person` attributes. For example, if an address is not given,
+ then simply make `p.address` to be `null`.
+ - Pros:
+ - More convenient and less hassle required to wrap `null` in the value.
+ - Cons:
+ - It is error-prone because we would be passing null values around which
+ makes the occurrence of exceptions such as `NullPointerException` highly likely.
+
+#### 1.4 Setting a duplicate `Person` as a case-insensitive match
-The following activity diagram summarizes what happens when a user executes a new command:
+Similar to AB3, Amigos prevents a user from adding a duplicate `Person`.
-
+* **Current implementation**
+ - We check whether a `Person` is already in Amigos by `Person::isSamePerson` which makes use of `FriendName::equals`.
+ Furthermore, we made `FriendName::equals` case-insensitive, thus disallowing users from adding a person with the same
+ name but in different capitalisation.
+
+* **Alternative implementations**
+ - An alternative way is to define equality of 2 `Person` objects in a stricter way - to make sure that all the attributes
+ are the same (not just the `FriendName`) for 2 `Person` objects to be considered as duplicates. This means that more
+ checks must be done.
+ - Another alternative way to define equality of 2 `Person` objects would be by using case-sensitive `FriendName`, but
+ we decided that our current implementation makes more logical sense.
+
+
+#### 1.5 Finding friends
+
+##### 1.5.1 Storing the user inputs as attributes, rather than `String`.
+
+* **Current implementation**
+ - The user inputs are stored as attributes.
+ - Inputs beginning with `n/`, `t/`, `ttl` are converted to `FriendName`, `Tag`, `LogName` respectively.
+ - Pros:
+ - Each of the `FriendName`, `Tag` , `LogName` inputs would be subject to their respective regex constraints.
+ - Any invalid input will throw an error. For example, `findfriend n/!` would throw an error because a `FriendName`
+ should only contain alphanumeric characters and spaces.
+ - Cons:
+ - Overhead required to wrap the user input in their respective objects, and then extracting out their values again
+ for the check comparison.
+
+* **Alternative implementation**
+ - An alternative way would be to store the inputs as `String` without converting them to their respective attributes.
+ - Pros:
+ - This reduces the amount of code needed, and possible errors due to conversion.
+ - Cons:
+ - The user would be allowed to search for invalid keywords.
+
+##### 1.5.2 Concrete example of execution: `findfriend`
+
+The implementation of searching for friends in Amigos is facilitated by the `FindCommand`, `FindCommandParser` in the `Logic`
+component and `FriendFilterPredicate` class in the `Model` component.
+
+Given below is an example usage scenario and how the `Logic` and `Model` components behave at every step.
+
+1. User keys in a valid `FindCommand` `e.g findfriend n/Alex t/Friend ttl/Dinner` into the command box of the GUI.
+2. `AddressBookParser` calls `FindCommandParser::parse` and parses the input.
+
+ - `FindCommandParser::parse` converts the arguments entered by the user into a `FriendFilterPredicate` by
+ grouping the user inputs into `Set`, `Set` and `Set` based on the input prefixes. Next,
+ a `FindCommand` is created with the new `FriendFilterPredicate` passed into it.
+
+ - `FriendFilterPredicate::test(Person person)` performs three checks and returns `true` if any of the checks are met:
+ - For each name keyword entered by the user, whether the person's name contains the keyword.
+ - For each tag keyword entered by the user, whether any of the person's tags contains the keyword.
+ - For each log title keyword entered by the user, whether any of the person's log title contains the keyword.
+
+ - When `FindCommand::execute` is called, the `FilteredList` in `ModelManager` will be updated to only contain
+ friends who have passed the predicate test.
+
+An activity diagram showing the implementation of the feature:
+
+
+#### 1.6 Show friend
+
+This feature provides users with a way of seeing details of a friend more in depth.
+
+##### 1.6.1 Concrete example of execution: `showfriend`
+
+The implementation of showing a specific friend in Amigos is facilitated by `ShowFriendCommand`, `ShowFriendCommandParser`, and `CommandResult`
+in the `Logic`component, as well as `ExpandedPersonCard`, `ExpandedPersonListPanel`, and `MainWindow` in the `UI` component.
+
+Given below is how the different classes work together in an example usage scenario.
+
+1. User keys in a valid `ShowFriendCommand` `e.g showfriend n/Alex Yeoh` into the command box of the GUI.
+2. `AddressBookParser` calls `ShowFriendCommandParser::parse` and parses the input, creating a `ShowFriendCommand`.
+ - `ShowFriendCommand` extends from `ByIndexByNameCommand` and accepts either an `Index` or a `FriendName` in its constructors
+ to identify the particular friend.
+3. `ShowFriendCommand` filters the friend list on the GUI to be of length 1, only showing the friend requested by the user.
+ It also filters the event list on the GUI to only show upcoming events for the particular friend.
+ - A `CommandResult` is returned, with a boolean field `showDetails` in its constructor instantiated to be `True`.
+
+4. `MainWindow` switches the view on the GUI to the `ExpandedPersonListPanel` in the Friends tab.
+ This is done by calling `StackPane::requestFocus()` and `StackPane::toFront()`, bringing the `ExpandedPersonListPanel`
+ to the front.
+ As the list panel has already been filtered in `ShowFriendCommand::execute`, only a single `ExpandedPersonCard`
+ would be shown.
+ The following are details displayed on the `ExpandedPersonCard`:
+ - `FriendName`, `Address`, `Email`, `Phone`
+ - `Description`
+ - Upcoming `Event`s related to the friend, wrapped in a `StackPane`
+ - All `Log`s of the friend
+
+ An object diagram showing the `ExpandedPersonCard` of a friend named `Alex Yeoh`:
+
+
+##### 1.6.2 Thought process behind switching view from `PersonCard` to `ExpandedPersonCard`
+
+* **Current implementation**
+ - Both `PersonListPanel` and `ExpandedPersonListPanel` are superimposed on each other and
+ `StackPane::toFront()` is called to bring the selected list to the front based on the command entered by
+ the user.
+ - Pros:
+ - User will be able to see all details pertaining to the particular friend in full view.
+ - Cons:
+ - Overhead required as both lists are running simultaneously in the foreground/background.
+ - May not be extendable to larger features with a large number of lists.
+
+* **Alternative implementation**
+ - Have a SplitPane, with the left pane showing the `PersonListPanel` and the right pane showing the
+ `ExpandedPersonListPanel`.
+ - Pros:
+ - Do not have to keep calling `StackPane::toFront()` each time a friend-related command is called.
+ - Cons:
+ - Have to fill the `ExpandedPersonListPanel` with something to display upon start up.
+ - There will be duplicate information on both the left and right pane when `ShowFriend` is called.
+
+
+### 2. Events Feature
+
+Events in Amigos are implemented in a similar fashion to Persons in AB3, for the sake of consistency in the codebase. Thus, we have a `UniqueEventList` in the `AddressBook`, which is the analogue of `UniquePersonList`. The UI will then refer to a filtered event list contained within `ModelManager` to get the Events to be displayed.
+
+Each event contains a `Name`, `DateTime`, `Description`, and a `FriendName` set. The latter represents the Friends that are linked to this event.
+
+
+
+#### 2.1 Implementing Event-Person relationships
+Key Consideration: How to implement & maintain the validity of the relationship between `Event` and `Person` objects, since Events contain a list of friends involved.
+
+* **Current Implementation**
+ * This relationship is represented by a `FriendName` set that is encapsulated within the `Event` class. (see above diagram)
+ * `AddressBook` is responsible for the maintenance of the relationship's validity, since it encapsulates both the `UniquePersonList` and `Unique EventList`. The relationship is maintained by the following processes:
+ * The validity of the friend names (i.e. they must always correspond to actual `Person` objects in Amigos) is checked during the creation of and when editing an `Event`.
+ * After a `Person` class is edited or deleted, the changes are cascaded to the `FriendName` set as well.
+ * Pros:
+ * Reduce coupling between the `Event` and `Person` classes by making it a one-way dependency.
+ * Reduce dependency further by only storing the `FriendName` object and not the entire `Person` object.
+ * Cons:
+ * Prone to errors if validity checks are overlooked.
+ * Not terribly efficient, because to check validity there is a need to loop through the entire list of `Person` objects for each `FriendName` stored. Similarly, it is inefficient when querying which `Event` objects contain a specific `Person`.
+ * Need to either constantly mutate or replace an `Event` to reflect changes to `FriendName`, which can be troublesome.
+
+* **Alternative Implementation**
+ * The relationship could alternatively be represented using an association class between `Event` and `Person`. This `EventPersonAssociation` could then be stored in a list in the `AddressBook`.
+ * Pros:
+ * Improve abstraction and cohesion by storing and handling the details of the Event-Person relationship in a separate class.
+ * This allows us to avoid modifications to the `Event` and `Person` classes, and reduce coupling between them as the dependency is one-way from the `EventPersonAssociation` class.
+ * Cons:
+ * Additional overhead as new class(es) will have to be created and tested.
+ * Does not solve the error-proneness of maintaining the relationship validity after changes to `Event` or `Person`.
+ * If implemented using a `EventPersonAssociation` list, will not be very efficient as well when making queries/changes,especially if there are a large number of associations in the list
+
+#### 2.2 Notes about implementing `listevent`
+
+Key Consideration: How to implement `listevents` when there exists multiple tabs and other `show` commands such as `listfriends` and `showinsights`.
+
+The following sequence diagram summarizes what happens when a user executes the `ShowEventsCommand`, the sequence diagram is similar for the other types of `show` commands which exist in Amigos.
+
+
+
+* **Current Implementation**
+ * Using a `FilteredList` we are able to segregate events into past events and future events and display these events according to user's preference, this is largely similar to AB3's implementation of `list`, however we need to add the ability to automatically change tabs once this command is called, thus the `CommandResult` class was modified to contain a boolean `isEvent` which is `true` for any command related to events and enables the seamless switching between tabs.
+ * Pros
+ * Allows for easy traversal between the Friends, Events and insights tab.
+ * It is easily extendable, for any new features we simply need to add a boolean which represents that feature, and create an overloaded constructor to initialize that variable to `true`
+ * Enforces the abstraction barrier between the UI component and Logic component
+ * Cons
+ * Although easily extendable, to ensure backward compatibility we need to ensure that the old constructor initialize the boolean for this new feature as `false`
+ * If many features are added, can lead to many overloaded constructor which will make the entire `CommandResult` class convoluted
+
+* **Alternate Implementation**
+ * We can implement a check in `MainWindow` which uses switch cases to detect which command is executed and accordingly switch tabs. The implementation of the representation of `Events` using a `FilteredList` remains the same.
+ * Pros
+ * This is very similar to the implementation in `AddressBookParser`, it easily extendable, as we simply need to add new switch statements for new features and commands
+ * Cons
+ * Violates the Law of Demeter as the UI does not need to know exactly which command is executed, it simply needs to know whether to switch tabs to event or not
+ * Increases the coupling between the `Commands` classes and the `MainWindow` since now we need to add a new switch case for every new command created.
+ * Involves a lot of code duplication as well
+
+#### 2.3 Notes about implementing the `findevent` command
+Key Consideration: Keeping track of the various filtering conditions that could be potentially set by the user, so that it is easily maintained and extended.
+
+
+
+* **Current Implementation**:
+ * The `FindEventCommandParser` class takes in the user's input and produces a List containing all the `Predicate` that can be derived from the input.
+ * Each predicate type implements the `Predicate` interface in its own class. e.g. `EventDateIsBeforePredicate`
+ * This list of `Predicate` is then passed to the `FindEventCommand` class via its constructor, and it will later filter the event list accessed by the UI based on all the given predicates.
+ * The sequence diagram shows how this process is carried out (with unnecessary details omitted) given two filtering conditions - by event name and by event date
+ * Pros:
+ * Easy to add new predicates and modify existing ones
+ * Cons:
+ * A bit of extra overhead in code, as we need to create a class for each predicate to override the equals() method, which is needed in testing.
+
+* **Alternative Implementation**:
+ * Create a single class that acts as a predicate for all possible filtering conditions i.e. an `EventFilterPredicate`.
+ * The `FindEventCommandParser` will instead initialize this predicate class and pass it on to the `FindEventCommand` class which will use it to filter the list.
+ * Pros: Less overhead because we only need to create a single predicate class for filtering the Event list.
+ * Cons:
+ * Violates both Separation of Concerns and the Single Responsibility Principle
+ * Harder to test due to increased complexity of the single predicate class
+ * Decreased modularity means it is harder to modify and extend in the future e.g. if we wanted to add more filtering conditions
+
+### 3. Logs Feature
+
+_Amigos_ supports logs, via `addlog`, `deletelog` and `editlog` features.
+
+In this section we will go through the high-level design details and notes to take about the implementation for these features.
+
+#### 3.1 Log's representation in the model and storage
+
+1. In the model, `Person` now has an additional `UniqueLogList` field encapsulating some number
+ of `Log` objects.
+
+
+
+2. 'JsonAdaptedLog' objects are used to save `Log` objects to json format, in an implementation analogous to that of
+ 'JsonAdaptedTag'.
+
+
+
+#### 3.2 Execution of a log-related command
+
+All three (add, edit, delete) log commands work the same at a high level.
+
+1. `Parser` parses input
+2. Details wrapped into `{Type}LogDescriptor`
+3. `{Type}LogCommand` takes in descriptor and executes with model
+
+##### 3.2.1 Choice of using descriptors
+As with a general command, the log-related commands could have put all the logic into the `Command::execute` method.
+
+A conscious choice to use `Descriptor` objects arose from the complexity of the features, as users had multiple permutations
+that were possible. e.g. `deletelog` allows a user to delete all logs, delete a specifc log, or all logs of a user.
+Having a parser decipher the specific action and wrapping error-handling logic to a `Descriptor` object allowed the
+implementation to be clean and easily extendable.
+
+##### 3.2.2 A concrete example: `addlog`
+
+Given below is an example usage scenario and how the `Logic`, `Model` and `Storage` components behave at every
+step. In particular, take note how the parent `ByIndexByNameCommand` is involved.
+
+
+
+
+1. User keys in a valid `addlog` command.`e.g. addlog JOHN DOE ttl/some log title`
+2. `AddressBookParser` calls `AddLogCommandParser::parse` and parses the input.
+ 1. `AddLogCommandParser::parse`wraps the log title and (optional) log description into an `AddLogDescriptor` object
+ and instantiates a new `AddLogCommand` object with it.
+3. When `AddLogCommand::execute` is called, the parent method `getPersonByName` or `getPersonByIndex` is called,
+ returning the specified `Person` object.
+ 1. `AddLogCommand::createAddedLogPerson` is called, which calls `AddLogCommandDescriptor::getLogsAfterAdd`, and the
+ latter takes the specified person and duplicates him, instantiates a new `Log` and appends it to the existing list,
+ before returning it.
+ 2. Then the new `Person` object with the new `Log` is set in the `model`.
+
+A sequence diagram shows, clearly, the interactions between `AddLogCommand`, `AddLogCommandParser`, `AddLogDescriptor` and `model`.
+
+
+#### 3.3 Some considerations in designing Log's representation
+
+**Aspect: How should `Log` objects should be implemented in `Amigos`?**
+* **Alternative 1 (current choice):** Store `Log` objects inside a `List`, inside a `Person`.
+ * Pros: Easy to implement, intuitive, easy to maintain and test.
+ * Cons: Downstream features such as `find` applied to logs may be more tedious, having to iterate through all `Person` objects.
+* **Alternative 2:** Store `Log` objects inside a `List`, in some global data field part of the `AddressBook`.
+ * Pros: Easier access to logs, since searching through a unified list in a single location.
+ * Cons: Tedious to implement and maintain, higher degree of coupling.
+* **Resolution:** Alternative 1 was chosen since no "global" level features e.g. logs tied to events were on the horizon.
-#### Design considerations:
-**Aspect: How undo & redo executes:**
+**Aspect: Uniqueness of a `Log` object**
+* Intuitively, it makes sense that a `Log` has a title and description.
+* **Alternative 1 (current choice):** No two logs can have the same title (case-sensitive).
+ * Pros: Easy to implement, intuitive, easy to maintain and test.
+ * Cons: Some users may want logs with the same title, but different descriptions.
+* **Alternative 2:** No two logs can have the same title and same description
+ * Pros: Stricter notion of equality that makes intuitive sense.
+ * Cons: Checks for uniqueness require two degrees of checking, and user is less likely to be able to
+ look at and find logs easily.
+* **Resolution:** Alternative 1 was chosen since at the end of the day, so long as a user can distinguish two logs
+it is functional. So we consider logs duplicates if they have the same title case-sensitively.
+
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+### 4. Tabs Feature
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+Key consideration: How to display information about Friends, Events and insights
-_{more aspects and alternatives to be added}_
+* **Current Implementation**
+ * Since our application had three primary classes Friends, Events and Insights we needed to be able to view instances of all of these classes without the GUI being cluttered with details. Thus, we decided to implement a Tab Pane with one tab for each of our features.
+ * Pros
+ * All the features displayed in an organised fashion, since each feature is represented by their own tabs.
+ * It is highly extendable, any new feature can easily be incorporated by the addition of new tabs
+ * It is very intuitive, allowing user to change their view by clicking on the tab they wish to view (Interactive)
+ * Aesthetically pleasing
+ * Cons
+ * Changing of tabs can create issues with updating of lists
+ * Increases complexity of GUI
-### \[Proposed\] Data archiving
+* **Alternate Implementation**
+ * Superimpose all the scenes for each feature and call `requestFocus()` to bring the specific feature we want to observe to the forefront.
+ * Pros
+ * Easy implementation
+ * Cons
+ * Can get messy very fast, since the fxml doc will have multiple `FeaturePlaceHolders` on top of each other
+ * Not easily extendable, only suitable for a few features
+ * Can get very cluttered
-_{Explain here how the data archiving feature will be implemented}_
+The following images show how the Tabs feature look when the tab for each feature is selected.
+  
--------------------------------------------------------------------------------------------------------------------
@@ -257,71 +579,455 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
+* tech-savvy university students
* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
-
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+* can type fast and has plenty of experience with CLI application
+* is often busy and struggles to manage important details about their relationships
+**Value proposition**: The program will help busy university students to manage their friendships by keeping track of important details. An example of possible features include the tracking of birthdays, money owed, upcoming meetings etc. and providing relevant reminders, in addition to basic functionalities.
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| Priority | As a … | I want to … | So that I can… |
+|----------|-----------------|---------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
+| `* * *` | User | add new friends | keep track of any new friends I make |
+| `* * *` | User | remove friends I am no longer in contact with | keep my friends list updated and remove irrelevant contacts |
+| `* * *` | User | view all details, descriptions and logs that I previously wrote about my friend | do a quick recap of important details about my friend |
+| `* * *` | User | view all my friends | ensure that the information I have added is saved and check all of my friends' details |
+| `* * *` | User | add events relating to my friends | keep track of any future events |
+| `* * *` | User | remove an event | cancel events previously added to keep my event list updated |
+| `* * *` | User | review my upcoming events along with their relevant details | take note and be prepared for them |
+| `* * *` | Forgetful user | add descriptions and logs about my friends that I would like to remember | not stress about remember these details for the next time we meet |
+| `* * *` | User | remove a previous log recorded about a friend | clear irrelevant details about my friend |
+| `* *` | User | update details about friends | keep my friends list up-to-date |
+| `* *` | User | update details of events | correct outdated or wrong information entered about any event |
+| `* *` | User | update previous logs recorded about a friend | ensure that information about my friend is up-to-date and accurate |
+| `* *` | Organized User | tag friends under different categories | better classify and organise my friend circle |
+| `* *` | User | find specific friends using a keyword search | view the friend I want to check out efficiently instead of scrolling through the list of friends |
+| `* *` | Forgetful User | use a keyword search to find a note written about someone | find out who I spoke to about a certain issue |
+| `* *` | User | view upcoming events tied to a specific friend | take note and be prepared for specific events |
+| `* *` | User | find specific events using certain search criteria | view the specific event(s) I want to see efficiently and flexibly |
+| `* *` | Reflective user | look at summary statistics about my friendships | reflect about how well my friendships are doing |
+| `* *` | New User | access a help page | learn how to use certain commands I am unfamiliar with |
+| `* *` | New User | reset the application to its default state | properly experiment with the application before using it properly |
+| `* *` | Seasoned User | shorten command lengths | increase my efficiency while using this application |
+| `*` | Careless User | undo a command | revert back accidental commands |
+| `*` | Seasoned User | chain commands | reduce the number of commands I need to type thereby increasing efficiency |
+| `*` | New User | know which features of the application I am not using as often | fully utilise the application to its maximum capability |
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `Amigos` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case: F01 - Add a friend**
+
+**Guarantees**
+* A new friend will be added into Amigos only if there does not already exist a friend with the same name in it.
+
+**MSS**
+
+1. User requests to add a friend.
+2. Amigos proceeds to add the friend.
+3. Amigos clears the user input.
+
+ Use case ends.
+
+**Extensions**
+* 1a. A friend with the same name already exists in Amigos.
+ * 1a1. Amigos displays the existing friend with the same name and the corresponding error message.
+
+ Use case ends
+
+
+**Use case: F02 - Edit a friend**
+
+**Guarantees**
+* The field(s) of an existing friend will be updated only if the user input is valid.
+
+**MSS**
+
+1. User requests to list friends.
+2. Amigos shows a list of friends.
+3. User requests to edit details of a specific friend in the list.
+4. Amigos edits the details of the friend accordingly and clears user input.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+ * Use case ends.
+
+
+* 3a. No name or index is entered by the user.
+ * 3a1. Amigos requests user to enter a name or index.
+ * 3a2. User reenters the command along with a valid name or index and other relevant fields (at least one) to edit.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b/3c again.
+
+
+* 3b. Amigos finds no contact with the given name or invalid index is given.
+ * 3b1. Amigos requests user to check input and reenter.
+ * 3b2. User reenters the command along with a valid name or index and other relevant fields (at least one) to edit.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b/3c again.
+
+
+* 3c. A valid name or index is entered by user but no fields to edit are given.
+ * 3c1. Amigos requests user to enter at least one field to edit.
+ * 3c2. User reenters command and name or index, along with at least one field to edit.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b/3c again.
+
+**Use case: F03 - Delete a friend**
-**Use case: Delete a person**
+**Guarantees**
+* An existing friend in Amigos will be deleted only if the name input matches that of an existing friend in Amigos
+ or a valid index is provided.
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to list friends.
+2. Amigos shows a list of friends.
+3. User requests to delete a specific friend in the list.
+4. Amigos deletes the friend and clears the user input.
Use case ends.
**Extensions**
* 2a. The list is empty.
+ * Use case ends.
+
+
+* 3a. No name or index is entered by the user.
+ * 3a1. Amigos shows an error message showing the correct command format.
+ * 3a2. User reenters the command along with a valid name or index.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b/3c again.
+
+
+* 3b. Amigos finds no contact with the given name.
+ * 3b1. Amigos throws an error message that the name entered is invalid.
+ * 3b2. User reenters the command along with a valid name.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b/3c again.
+
+
+* 3c. An invalid index is provided.
+ * 3c1. Amigos throws an error message that the index entered is invalid.
+ * 3c2. User reenters the command along with a valid index.
+
+ Use case resumes at step 4 if newly-entered user input is valid, otherwise it may reach 3a/3b again.
+
+
+**Use case: F04 - Checking details of a friend**
+
+**MSS**
+
+1. User requests to see all friends in Amigos.
+2. Amigos switches the GUI to the friends tab and shows all friends.
+3. User requests to view full details of a particular friend.
+4. Amigos displays a page containing the full details of that particular friend.
+
+Use case ends
+
+**Extensions**
+* 3a. The friend does not exist.
+ * 3a1. Amigos shows an error message.
+
+ Use case ends
+
+
+* 3b. Amigos detects an issue in the input (e.g. incorrect input format)
+ * 3b1. Amigos displays the error feedback to the user.
+ * 3b2. User reenters command with a valid input.
+
+ Use case resumes at 4 if newly-entered user input is valid, otherwise it may reach 3a/3b again.
+
+**Use case: F05 - Finding a friend**
+
+**MSS**
+
+1. User requests to list friends.
+2. Amigos shows a list of friends.
+3. User enters search keyword(s) to find a specific friend/friends based on name/tags/log titles.
+4. Amigos displays a list containing friends who match at least one of the keyword(s) entered by user.
+
+Use case ends
+
+**Extensions**
+
+* 3a. User enters invalid keywords.
+ * 2a1. Amigos throws an error message and requests user to check input.
+ * 2a2. User corrects command, and keys in edited command.
+
+ Use case resumes at step 4 if newly-entered command is valid.
+
+**Use case: L01 - Adding a new log to a friend**
+
+**Guarantees:**
+* If successful, log will be added to friend details and saved in storage.
+
+**MSS**
+
+
+1. User decides to add a new log to a specific friend.
+2. User keys in necessary details.
+3. Amigos adds the log to the friend.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. Amigos finds no friend with the given name or index.
+ * 2a1. Amigos requests user to check input and reenter.
+ * 2a2. User corrects command, and keys in edited command.
+
+ Use case resumes at step 3.
+
+
+* 2b. Amigos detects that an invalid format of the command has been keyed in.
+ * 2b1. Amigos clears the input and prompts the user with potential corrections, and requests for input.
+ * 2b2. User corrects command, and keys in edited command.
+
+ Use case resumes at step 3.
+
+
+**Use case: L02 - Editing a log of a single friend**
+
+**Guarantees:**
+* If successful, specified log will be overwritten and saved in storage.
+
+
+**MSS**
+
+
+1. User decides to edit a log in a specific friend.
+2. User keys in necessary details.
+3. Amigos edits the specific log of the friend.
+
+ Use case ends.
+
+
+**Extensions**
+
+* 2a. Amigos finds no friend with the given name or index.
+ * 2a1. Amigos requests user to check input and reenter.
+ * 2a2. User corrects command (if desired), and keys in edited command.
+
+ Use case resumes at step 3.
+
+
+* 2b. Amigos detects that the user has not requested to edit a specific log.
+ * 2b1. Amigos clears the input and provides a list of all logs (their titles) and an accompanying index, asking the user to key in the index.
+ * 2b2. User keys in the index.
+
+ Use case resumes at step 3.
+
+
+* 2c. Amigos detects that an invalid format of the command has been keyed in.
+ * 2c1. Amigos clears the input and prompts the user with potential corrections, and requests for input.
+ * 2c2. User corrects command, and keys in edited command.
+
+ Use case resumes at step 3.
+
+
+* 2d. Amigos detects that the requested friend has no logs to be edited.
+ * 2d1. Amigos throws an error message saying that the log does not exist.
Use case ends.
-* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+**Use case: L03 - Deleting a log/logs from a friend**
- Use case resumes at step 2.
+**Guarantees:**
+* If successful, log will be deleted from friend and reflected in storage.
-*{More to be added}*
+**MSS**
-### Non-Functional Requirements
+1. User decides to delete a log/logs in a specific friend.
+2. User keys in necessary details.
+3. Amigos clears the input and provides feedback of deletion success.
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+ Use case ends.
+
+
+**Extensions**
-*{More to be added}*
+* 2a. Amigos finds no friend with the given name or index.
+ * 2a1. Amigos throws an error message saying that name or index is invalid.
+ * 2a2. User corrects command (if desired), and keys in edited command.
+
+ Use case resumes at step 3.
+
+
+* 2b. Amigos detects User has requested to delete all logs of all friends.
+ * 2b1. All logs in all friend are deleted.
+
+ Use case resumes at step 3.
+
+
+* 2c. Amigos detects that User has requested to delete all logs of a friend.
+ * 2c1. Amigos deletes all logs of the friend.
+
+ Use case resumes at step 3.
+
+
+* 2d. Amigos detects an invalid combination of arguments.
+ * 2d1. Amigos clears the input and requests the user to check input and reenter if necessary.
+
+
+* 2e. Amigos detects that the requested friend has no logs.
+ * 2e1. Amigos clears the input and notifies the user that this friend has no logs to be deleted.
+
+ Use case ends.
+
+**Use Case: E01 - Adding a new event**
+
+**Preconditions**: User can remember the event details they want. (e.g. names of friends to add)
+
+**Guarantees**: A new event will be created in Amigos, as long as the command was executed successfully.
+
+**MSS**:
+1. User chooses to add a new event, entering the details accordingly.
+2. System applies the change and reflects the new event details to the user.
+3. System clears the user input.
+
+ Use case ends.
+
+**Extensions**:
+* 1a. System detects an issue in the given input. (e.g. missing/wrong flag, wrong formatting)
+ * 1a1. System displays the error feedback to the user.
+ * 1a2. User edits and resends the input to the system.
+
+ Use case resumes at 1 but may reach 1a again if the input remains erroneous.
+
+
+* 1b. System detects that an event with the same details already exists.
+ * 1b1. System displays the duplicate event to the user and the corresponding error message.
+ * 1b2. User cancels the operation.
+
+ Use case ends.
+
+
+* 3a. User realises they made a mistake in the event details.
+ * 3a1. User edits the event details (E02).
+
+ Use case ends.
+
+**Use Case: E02 - Editing an existing event**
+
+**Preconditions**: The event has already been created, and the user can remember what they want to change the event details to.
+
+**Guarantees**: An existing event will be edited, as long as the command is executed successfully.
+
+**MSS**:
+ 1. User chooses to search for the details of a specific event.
+ 2. System shows the search results to the user.
+ 3. User edits the details of an existing event through the system input.
+ 4. System applies the change and reflects the updated event details to the user.
+ 5. System clears the user input.
+
+ Use case ends.
+
+**Extensions:**
+ * 3a. System detects an issue in the given input (e.g. missing/wrong flag, wrong formatting).
+ * 3a1. System displays the error feedback to the user.
+ * 3a2. User resends the command to the system.
+
+ Use case resumes at 3 but may reach 3a again if the command remains erroneous.
+
+
+ * 3b. System detects that an event with the same details already exists.
+ * 3b1. System displays the duplicate event to the user and the corresponding error message.
+ * 3b2. User cancels the operation.
+
+ Use case ends.
+
+**Use case: E03 - Checking upcoming events**
+
+**Guarantees**
+
+* If any upcoming events exist within the system they will be displayed.
+
+**MSS**
+
+1. User requests to show all upcoming events.
+2. Amigos switches the GUI to the events tab and displays all the upcoming events.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. User requests to show all events, both past and upcoming.
+ * 1a1. Amigos switches the GUI to the events tab and displays all stored events.
+
+ Use case ends.
+
+
+* 2a. There are no events to show.
+ * 2a1. Amigos displays an empty interface.
+
+ Use case ends.
+
+**Use case: E04 - Finding events**
+
+**MSS**
+
+1. User requests to list events.
+2. Amigos shows a list of events.
+3. User enters search keyword(s) to find a specific event(s) based on the event name / date / friends
+ involved in the event.
+4. Amigos displays a list of events matching all the keyword(s) entered by user.
+
+Use case ends
+
+**Extensions**
+
+* 3a. User enters invalid keywords.
+ * 2a1. Amigos throws an error message and requests user to check input.
+ * 2a2. User corrects command, and keys in edited command.
+
+Use case resumes at step 4 if newly-entered command is valid.
+
+**Use case : S01 - Show insights**
+
+**Guarantees**
+
+* Insights (number of logs and number of events) of each friend would be displayed.
+
+**MSS**
+
+1. User requests view insights of all friends in Amigos.
+2. Amigos switches the GUI to the Insights tab and displays all statistics.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There are no friends to show insights of.
+ * 2a1. Amigos displays an empty interface.
+
+ Use case ends.
+
+
+### Non-Functional Requirements
+
+1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
+2. Should be able to hold up to 200 friends with up to 100 events/logs per person without a noticeable sluggishness in performance for typical usage (time taken to process any one command is 1 second at most).
+3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish all the basic tasks faster using commands than using the mouse.
+4. The project only supports managing of the user friendships and not other types of relationships.
+5. The application should work with different screen sizes, resolutions and window sizes (minimum resolution is 600x450).
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Log**: Details of interaction between user and friend
+* **Event**: Upcoming meeting/ date of significance for a friend
+* **Insight**: Some summary statistic(s) about a friend
--------------------------------------------------------------------------------------------------------------------
@@ -340,7 +1046,7 @@ testers are expected to do more *exploratory* testing.
1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 2. Double-click the jar file Expected: Shows the GUI with a set of sample friends. The window size may not be optimum.
1. Saving window preferences
@@ -349,29 +1055,183 @@ testers are expected to do more *exploratory* testing.
1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+### Friend management
-### Deleting a person
+#### Adding a friend
-1. Deleting a person while all persons are being shown
+1. Adding a friend while all friends are being shown
+ 1. Test case: `addfriend n/John Doe p/92402912 e/johndoe@example.com`
+ Expected: a new entry in the `Friends` tab with the name `John Doe`, phone number `92402912` and email `johndoe@example.com` should appear.
+ 2. Test case: `addfriend n/John& Doe`
+ Expected: an error message regarding incorrect name format should be displayed
+ 3. Test case: `addfriend p/942142412 n/Jane Doe`
+ Expected: Order of arguments does not matter, command should create a friend `Jane Doe` with the phone number `942142412`.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+2. Adding a friend while in another tab
+ 1. Prerequisites: Must be in either `Events` or `Insights` tab.
+
+ 2. Test case: `addfriend n/Jennifer Doe`
+ Expected: Tab should switch to `Friends` tab and a new friend `Jennifer Doe` should be present in the friends list.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+#### Deleting a friend
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+1. Deleting a friend while all friends are being shown
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Prerequisites: List all persons using the `listfriend` command. Multiple friends in the list.
+
+ 2. Test case: `deletefriend 1`
+ Expected: First friend is deleted from the list. Details of the deleted contact shown in the status message.
+
+ 3. Test case: `deletefriend 0`
+ Expected: No friend is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 4. Other incorrect delete commands to try: `deletefriend`, `deletefriend x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+#### Editing a friend
+
+1. Editing a friend while all friends are being shown
+ 1. Prerequisites: At least 3 friends present, with one their names being `Alex Yeoh`
+
+ 2. Test case: `editfriend cn/Alex Yeoh nn/Alex Row d/Best friend`
+ Expected: Friend with the name `Alex Yeoh` edited to have a new name `Alex Row` and a new description `Best friend`
+
+ 3. Test case: `editfriend 2 nn/Barney Smith t/Cartoon`
+ Expected: Friend with index `2` in the friend list is edited to have a new name `Barney Smith` and all tags are replaced with `Cartoon`.
+
+ 4. Test case: `editfriend 3 cn/Alex Yeoh p/29529592`
+ Expected: Error message stating that either a name is provided or an index, but not both.
+
+#### Finding a friend
+
+1. finding a friend while all friends are being shown
+ 1. Prerequisites: At least 3 friends present, with one their names being `Alex Row`, one of them having a tag `colleagues` and one of them having a log titled `Dinner`
+
+ 2. Test case: `findfriend n/Alex Row`
+ Expected: Contacts whose names contain the substring `Alex Row` are displayed
+
+ 3. Test case: `findfriend ttl/Dinner`
+ Expected: Contacts whose log titles contain the substring `Dinner` are displayed
+
+ 4. Test case: `findfriend t/colleagues`
+ Expected: Contacts whose tags contain `colleagues` is displayed
+
+ 5. Test case: `findfriend n/Alex Row ttl/Dinner t/colleagues`
+ Expected: All the above 3 contacts are displayed
+
+### Log management
+
+#### Adding a log
+
+1. Adding a log while all friends being shown
+ 1. Prerequisites Atleast 1 friend present
+
+ 2. Test case: `addlog n/Alex Row ttl/Dinner`
+ Expected: Log added to `Alex Row`, GUI updated to display log title
+
+ 3. Test case: `addlog 1 ttl/Bday`
+ Expected: Log added to friend in index 1, GUI updated to display log title as well
+
+ 4. Test case: `addlog 1 n/Alex Row ttl/Event_Log`
+ Expected: Error either the name is provided or the index, not both
+
+#### Deleting a log
+
+1. Deleting a log from a friend while all friend are being shown
+
+ 1. Prerequisites: List all persons using the `listfriend` command. Multiple friends in the list, log must be present to delete
+
+ 2. Test case: `deletelog n/Alex Row id/1`
+ Expected: Alex Row's first log is deleted
+
+ 3. Test case: `deletelog 1 id/1`
+ Expected: The first log of the friend who is at index 1 is deleted
+
+ 4. Other incorrect delete commands to try: `deletelog 1 n/Alex Row id/1`, `deletelog 1 id/x`, `...` (where x is larger than the list size)
+ Expected: Error message provided for incorrect format or, incorrect index provided.
+
+#### Editing a log
+
+1. Editing a friend's log while all friends are being shown
+ 1. Prerequisites: Atleast two friends each with a log is present.
+
+ 2. Test case: `editlog n/Alex Yeoh id/1 ttl/New Log Title`
+ Expected: `Alex Yeoh`'s first log's title is changed to `New Log description`
+
+ 3. Test case: `editlog 2 id/1 d/The new description`
+ Expected: First log's description of friend at index `2` in the friend list is edited to have a description `New Log description`
+
+### Event management
+
+#### Adding an event
+
+1. Adding an event while all upcoming events are being shown
+ 1. Test case: `addevent n/Old Event dt/14-04-2020 1600`
+ Expected: No change in the GUI as the event is past event, although if `listevent -a` is run this event should be visable.
+ 2. Test case: `addevent n/Future Event dt/14-04-2023 1300`
+ Expected: GUI should be updated with a new event called `Future Event`
+ 3. Test case: `addfriend n/Date Check dt/99-04-2023 1240`
+ Expected: Error message should be displayed concerning the incorrect format of date.
+
+2. Adding an event while in another tab
+ 1. Prerequisites: Must be in either `Friends` or `Insights` tab.
+
+ 2. Test case: `addevent n/Party dt/14-04-2023 1400 d/Cool party`
+ Expected: Tab should switch to `Events` tab and a new event `Party` should be present in the events list.
+
+#### Deleting an event
+
+1. Deleting an event while all events are being shown
+
+ 1. Prerequisites: List all events using the `listevents` command. Multiple events in the list.
+
+ 2. Test case: `deletevent 1`
+ Expected: First event is deleted from the list. Details of the deleted event shown in the status message.
+
+ 3. Test case: `deletefriend 0`
+ Expected: No event is deleted. Error details shown in the status message. Status bar remains the same.
+
+ 4. Other incorrect delete commands to try: `deleteevent`, `deleteevent x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+#### Editing an event
+
+1. Editing an event while all events are being shown
+
+ 1. Prerequisites: Atleast 3 events present, with one their names being `Birthday`
+
+ 2. Test case: `editevent 1 n/Alex's Birthday d/Alex's 26th birthday`
+ Expected: Event at index 1 is changed to have the name `Alex's Birthday` and description `Alex's 26th birthday`
+
+ 3. Test case: `editevent 1 af/Alex Yeoh rf/Bernice Yu`
+ Expected: `Alex Yeoh` is added to the first event and `Bernice Yu` is removed from the first event.
+
+ 4. Test case: `editevent x n/New Event Title` (where x is larger than the list size)
+ Expected: No event edited, index error message provided.
+
+#### Finding an event
+
+1. finding an event while all events are being shown
+ 1. Prerequisites: Atleast 3 events present, with one their names being `Birthday`, one of them having a friend `Alex Row` and one of having a starting date of `10-03-2022`
+
+ 2. Test case: `findevent n/Birthday`
+ Expected: Events whose names contain the substring `Birthday` are displayed
+
+ 3. Test case: `findevent f/Alex Row`
+ Expected: Events whose friends contain `Alex Row` are displayed
+
+ 4. Test case: `findevent ds/10-03-2022`
+ Expected: Events which occur on `10-03-2022` and after are displayed,
+
+ 5. Test case: `findevent n/Birthday f/Alex Row ds/10-03-2022`
+ Expected: All the events matching the above predicates are displayed
### Saving data
-1. Dealing with missing/corrupted data files
+1. Dealing with corrupted data files
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. To test whether `Amigos` can detect corrupted data files, navigate to the data folder of `Amigos`.
+ and modify some data in `addressbook.json` to be invalid. For example, change an email to be of an invalid format (e.g `alexyeoh@e`).
+ 2. Upon running `Amigos` after editing `addressbook.json`, `Amigos` should start without any data in it due to the error.
+ 3. To revert `Amigos` back to its previous state, reopen `addressbook.json` and undo the edits made in step 1, or alternatively delete the `addressbook.json` to initialise a template `addressbook.json`.
-1. _{ more test cases … }_
diff --git a/docs/GuiTestDocumentation.md b/docs/GuiTestDocumentation.md
new file mode 100644
index 00000000000..b8def378132
--- /dev/null
+++ b/docs/GuiTestDocumentation.md
@@ -0,0 +1,282 @@
+---
+layout: page
+title: Developer Guide
+---
+* Table of Contents
+ {:toc}
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Start up**
+
+On start up the Application should open with the friends tabs selected by default, and all the friends in storage being displayed if any. An image of this is shown below.
+
+
+
+## **Clearing Address Book**
+
+To start fresh with a new clean version of Amigos the `clear` command needs to be typed in the CommandBox as shown below. This should update the application to have no events and friends and also switch to the friends tab.
+
+| Before | After |
+|:-------------------------------------------------:|:---------------------------------------------------:|
+|  |  |
+
+## **Commands**
+
+### Friends
+
+Do note that after the execution of any friend related commands, the GUI should switch to the `Friends` tab
+
+#### Add Friend
+
+Upon successful adding of a friend
+
+1. Correct Command result display message
+2. Friend should be added and should be ordered by name
+
+| Before | After |
+|:---------------------------------------------------:|:-------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Naaman` was inserted in the correct order with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of add command is entered, the appropriate add friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Edit a friend
+
+Upon successful editing of a friend
+
+1. Correct Command result display message
+2. Friend should be edited and if name is changed, list should be reordered
+
+| Before | After |
+|:--------------------------------------------------:|:------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Naaman` was edited to `Bean` and the list was reordered. The appropriate command result message displayed was also in the CommandResult box.
+
+If incorrect format of edit command is entered, the appropriate edit friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Deleting a friend
+
+Upon successful deletion of a friend
+
+1. Correct Command result display message
+2. Friend card should be removed from the GUI
+
+| Before | After |
+|:------------------------------------------------------:|:--------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Bean` was deleted with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of delete command is entered, the appropriate delete friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Show a specific friend
+
+Upon successful execution of `showfriend` command the following must occur
+
+1. Correct Command result display message
+2. Expanded Friend card of friend requested should be displayed
+
+| Before | After |
+|:--------------------------------------------------------------:|:------------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Bean` was deleted with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of delete command is entered, the appropriate delete friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Show all friends
+
+Upon successful execution of `showfriends` command the following must occur
+
+1. Friends tab is selected and displayed
+2. All the friends in Amigos are displayed
+
+| Before | After |
+|:----------------------------------------------------------------:|:--------------------------------------------------------------:|
+|  |  |
+
+#### Find a friend
+
+Upon successful execution of `findfriend` command the following must occur
+
+1. Correct Command result display message
+2. Any friends who match the search criteria are displayed
+
+| Before | After |
+|:--------------------------------------------------------------:|:------------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `findfriend dio` was executed and since `Dione` matches, her card is displayed.
+
+### Events
+
+Do note that after the execution of any event related commands, the GUI should switch to the `Events` tab
+
+#### Add Event
+
+Upon successful adding of an event
+
+1. Correct Command result display message
+2. Event should be added and should be ordered by date
+
+| Before | After |
+|:----------------------------------------------------------:|:--------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `WL's Bday` was inserted in the correct order with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of `addevent` command is entered, the appropriate error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Edit an event
+
+Upon successful editing of an event
+
+1. Correct Command result display message
+2. Event should be edited and if date is changed, list should be reordered
+
+| Before | After |
+|:------------------------------------------------------------:|:----------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `WL's Bday` date was edited to a later date, thus the list was reordered. The appropriate command result message displayed was also in the CommandResult box.
+
+If incorrect format of edit command is entered, the appropriate edit friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Deleting an event
+
+Upon successful deletion of an event
+
+1. Correct Command result display message
+2. Event card should be removed from the GUI
+
+| Before | After |
+|:----------------------------------------------------------------:|:------------------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Dione's Bday` was deleted with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of `deleteevent` command is entered, the appropriate delete event error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Show all events
+
+Upon successful execution of `showevents` command the following must occur
+
+1. Events tab is selected and displayed
+2. All the Events in Amigos are displayed
+
+| Before | After |
+|:--------------------------------------------------------------:|:------------------------------------------------------------:|
+|  |  |
+
+#### Find an event
+
+Upon successful execution of `findevent` command the following must occur
+
+1. Correct Command result display message
+2. Any friends who match the search criteria are displayed
+
+| Before | After |
+|:------------------------------------------------------------:|:----------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `findfriend n/Naa` was executed and since `Naaman's Bday` matches, that event card is displayed.
+
+If incorrect format of `findevent` command is entered, the appropriate find event error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+### Logs
+
+Do note that after the execution of any log related commands, the GUI should switch to the `Friends` tab
+
+#### Add Log
+
+Upon successful adding of a log
+
+1. Correct Command result display message
+2. Log should be added and title should be displayed in the friend's card
+
+| Before | After |
+|:------------------------------------------------------:|:----------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `First Log!` was inserted in the correct order with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of `addlog` command is entered, the appropriate error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Edit log
+
+Upon successful editing of a log
+
+1. Correct Command result display message
+2. Log should be edited and the changes should be reflected in the GUI
+
+| Before | After |
+|:--------------------------------------------------------:|:------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Second Log!` was edited to `Edit Log New Title`. The appropriate command result message displayed was also in the CommandResult box.
+
+If incorrect format of edit command is entered, the appropriate edit friend error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+#### Delete a log
+
+Upon successful deletion of a log
+
+1. Correct Command result display message
+2. Log title should be removed from the friend card should in the GUI
+
+| Before | After |
+|:------------------------------------------------------------:|:--------------------------------------------------------------:|
+|  |  |
+
+As you can see in the above images `Edit Log New Title` was deleted with the appropriate command result message displayed in the CommandResult box.
+
+If incorrect format of `deletelog` command is entered, the appropriate delete log error message should be displayed in the CommandResult box. Shown below is the error message in the CommandResultBox.
+
+
+
+
+### Help command
+
+Upon successful execution of the `help` command a pop-up with the link to the user guide is displayed.
+
+Below is an image showing the correct execution of the help command
+
+
+
+## **Stress tests**
+
+### Large number of Friends
+
+Tested application with 500 friends, able to run commands very quickly well within the requirements.
+
+### Large number of Events
+
+Tested application with 500 events, able to run commands very quickly well within the requirements.
+
+### Large number of logs
+
+Tested application with 500 logs, able to run commands very quickly well within the requirements.
+
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..0fc973f73d5 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,49 +3,43 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+_Amigos_ is a desktop application to help tech-savvy university students manage their friendships by helping them to keep track of important details. It is optimized for use via a Command Line interface while still having the benefits of a Graphical User Interface (GUI).
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+# Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. Download the latest `_Amigos_.jar` from [here](https://github.com/AY2122S2-CS2103-F09-2/tp/releases/tag/v1.4).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+3. Copy the file to the folder you want to use as the _home folder_ for your application.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- 
+4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
+
- * **`list`** : Lists all contacts.
+5. Type the command in the command box and press Enter to execute it. Here are some starters:
- * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
-
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
-
- * **`clear`** : Deletes all contacts.
-
- * **`exit`** : Exits the app.
-
-1. Refer to the [Features](#features) below for details of each command.
+ * **`listfriends`** : Lists all friends.
+ * **`listevents`** : Lists all events.
+ * **`showinsights`** : Lists insights about friends in the app.
+
+6. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
-## Features
+# Features
**:information_source: Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+ e.g. in `addfriend n/NAME`, `NAME` is a parameter which can be used as `addfriend n/John Doe`.
* Items in square brackets are optional.
e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
@@ -59,134 +53,342 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* Extraneous parameters for commands that do not take in parameters (such as `listfriends`, `listevents` and `exit`) will be ignored.
+ e.g. if the command specifies `listfriends 123`, it will be interpreted as `listfriends`.
+* Arguments connected by a `?` are exclusively-or - i.e. only one or the other can be provided.
+ e.g. in `addlog INDEX ? n/NAME`, either `INDEX` or `NAME` must be provided, but not both.
-### Viewing help : `help`
+## Friend Management
+_Amigos_ is designed to help you keep track of the friends in your life.
-Shows a message explaning how to access the help page.
+### Adding a friend: `addfriend` or `af`
-
+Adds a new friend to _Amigos_.
-Format: `help`
+**Format**: `addfriend n/NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DESCRIPTION] [t/TAG]…`
+* Note that `NAME` field is minimally compulsory. `p/`, `e/`, `a/` and `d/` `t/` flags and their arguments are optional.
+* Note that friend names should only contain alphanumeric characters and spaces
+* Note that there can be no duplicate friends having the same name (case-insensitive).
+* Note that names have to be exactly matched (in a case-insensitive manner) to be considered as duplicates. For example,
+ John Doe and John Doe (with the extra space) are considered as different friends.
-### Adding a person: `add`
+**Examples**:
-Adds a person to the address book.
+* `addfriend n/John Doe p/98765432 a/John street, block 123, #01-01 t/Classmate` will result in the following friend being added.
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+* `addfriend n/John Doe t/Friend t/Banker`
+
+### Editing a friend : `editfriend` or `ef`
+
+Edits an existing friend in _Amigos_.
+
+**Format**: `editfriend INDEX ? cn/CURRENT_NAME [nn/NEW_NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DESCRIPTION] [t/TAG]...`
+
+* Edits an existing friend in _Amigos_.
+* Either a specified `INDEX` or `CURRENT_NAME` of an existing friend in _Amigos_ must be provided.
+* At least one of the optional fields must be provided.
+* Existing values will be overwritten to the input values.
+* Note that when editing tags, the existing tags of the friend will be removed i.e. adding of tags is not cumulative.
+
+
+**Examples**:
+* `editfriend 1 a/John street, block 456, #01-01 e/johndoe@example.com` edits the address
+ and email of the friend at `INDEX` 1 to be `John street, block 456, #01-01` and `johndoe@example.com` respectively.
+* `editfriend cn/Alex Yeoh nn/Alex Tan` will change the name of `Alex Yeoh` to `Alex Tan`.
+
+### Deleting a friend : `deletefriend` or `df`
+
+Deletes a friend in _Amigos_.
+
+**Format**: `deletefriend INDEX ? n/NAME`
+
+* Either a specified`INDEX`or `NAME` of an existing friend in _Amigos_ must be provided.
+
+**Examples**:
+* `deletefriend n/John Doe`
+
+
+| Before | After |
+|:-----------------------------------------:|:---------------------------------------------:|
+|  |  |
+
+* `deletefriend 1`
-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`
+### Show a specific friend: `showfriend` or `sf`
-### Listing all persons : `list`
+Shows expanded page containing the full details related to an existing friend in Amigos.
-Shows a list of all persons in the address book.
+**Format**: `showfriend INDEX ? n/NAME`
-Format: `list`
+* Either a specified `INDEX` or `NAME` of an existing friend in _Amigos_ must be provided.
+* When viewing the page containing the full details related to an existing friend, other commands dependent on `INDEX`
+ will refer to the `INDEX` of the friend/event shown on the expanded friend page. For example, when viewing the expanded
+ friend page of `Charlotte Tan`, entering `editevent 2 n/Dinner with Bernice`
+ will update the name of `Charlotte Tan`'s current 2nd upcoming event to `Dinner with Bernice`. Similarly, `deletefriend 1`
+ will remove `Charlotte Tan` from Amigos, since `Charlotte Tan` is the only friend currently being viewed on the
+ expanded friend page.
+* Clicking on the `Events` tab directly after entering the `showfriend` command would only show events related to the
+ friend currently being viewed on the expanded friend page.
-### Editing a person : `edit`
+**Examples**:
+* `showfriend n/Bernice Yu` Will open up the page containing full details related to Bernice Yu - her name, address,
+ phone number, email, description, logs, and upcoming events with her.
-Edits an existing person in the address book.
+* 
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+* `showfriend 2` Will open up the page containing full details related to the friend at `INDEX` 2 on the filtered GUI list.
-* 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, …
+
+### List all friends : `listfriends` or `lf`
+
+Lists all friends in _Amigos_. Switches GUI to the friends tab.
+
+**Format**: `listfriends`
+
+
+
+### Find friends : `findfriend` or `ff`
+
+Find friends in _Amigos_ whose name, tags or logs' title matches ANY of the given keyword(s).
+
+**Format**: `findfriend [n/NAME_KEYWORD]... [ttl/LOG_TITLE_KEYWORD]... [t/TAG_KEYWORD]...`
+
+* Name, tags, and log titles will be searched.
* 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.
+* The search is case-insensitive. e.g. `john` will match `John`
+* The order of the keywords does not matter. e.g. Doe John will match John Doe
+* As long as the keyword matches a substring of the name / log title / tags
+ of a friend, the friend will be returned. e.g. `findfriend n/Jim` will return `Jimmy Tan` and `findfriend ttl/Dinner`
+ will return friends with log titles containing the substring `Dinner`.
+* Friends matching at least one keyword will be returned. e.g. `findfriend n/John ttl/John` will return `John Doe`, `John Tan`,
+ as well as all friends with log titles containing the substring `John`.
-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.
+**Examples**:
+* `findfriend n/John t/neighbour` returns all friends with 'John' in the name or 'neighbour' in any of the tags.
+* `findfriend n/John n/Emily n/Russel`returns all friends with 'John' or 'Emily' or 'Russel' in the name.
-### Locating persons by name: `find`
+
-Finds persons whose names contain any of the given keywords.
+## Logs management
+_Amigos_ provides functionality to manage logs, which are essentially detailed notes about a specific friend.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+### Adding a log: `addlog` or `al`
-* 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`
+Adds a log to an existing friend at the specified `INDEX` or with the specified `NAME` in _Amigos_.
+The `INDEX` refers to the index number shown in the displayed person's list.
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
+**Format**: `addlog INDEX ? n/NAME ttl/TITLE [d/DESCRIPTION]`
-### Deleting a person : `delete`
+* Exactly one of `INDEX` or the `NAME` fields is compulsory.
+* The `DESCRIPTION` argument is optional.
+* Note that duplicate logs are not allowed, and two logs are considered "duplicates" if they have the same title (case-sensitive).
+ The rationale for this is that users should be minimally able to tell logs apart at a glance without restrictions on content.
-Deletes the specified person from the address book.
+**Examples**:
+* `addlog 1 ttl/has a pet named poki` adds a log to the person at index 1 of the "Friends" tab with the title "has a pet named poki".
-Format: `delete INDEX`
+
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+* `addlog n/Benson Meier ttl/recommended movies d/the martian, interstellar, three idiots`
-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.
+### Editing a log: `editlog` or `el`
-### Clearing all entries : `clear`
+Edits an existing log of an existing friend in _Amigos_.
-Clears all entries from the address book.
+**Format**:
+`editlog INDEX ? n/NAME id/LOG_INDEX [ttl/NEW_TITLE] [d/NEW_DESCRIPTION]`
-Format: `clear`
+* Exactly one of `INDEX` or the `NAME` fields is compulsory.
+* At least one of the `NEW_TITLE` or `NEW_DESCRIPTION` arguments must be provided.
+ Both will directly overwrite the current values.
+* Note that editing a log to be case-insensitively the same as an existing log is not recommended, but allowed.
-### Exiting the program : `exit`
+**Examples:**
+* `editlog n/John Doe id/1 ttl/has a pet named Poki`
+* `editlog 1 id/1 d/the martian, interstellar, three idiots, peaky blinders`
-Exits the program.
+### Deleting a log: `deletelog` or `dl`
-Format: `exit`
+Deletes an existing log or logs of an existing friend in _Amigos_.
-### Saving the data
+**Format**: `deletelog INDEX ? n/NAME id/LOG_INDEX ? -a` or `deletelog -a`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+There are three cases of usage:
+* If `INDEX` or `NAME` is provided as well as `LOG_INDEX`, then that specific log will be deleted.
+* If `INDEX` or `NAME` is provided as well as a `-a` flag, then all logs of that person will
+ be deleted.
+* If only `-a` is provided, then all possible logs of all friends will be deleted.
+ * Note that in the command summary, we present the above as `deletelog INDEX ? n/NAME [id/LOG_INDEX] [-a]` for convenience.
-### Editing the data file
+**Examples:**
+* `deletelog n/John Doe id/1`
-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.
+| Before | After |
+|:-------------------------------------:|:-----------------------------------------:|
+|  |  |
-
: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.
-
+* `deletelog n/John Doe -a`
+* `deletelog -a`
+
+## Event Management
+_Amigos_ also allows you to keep track of your social events!
+
+
+
+### Creating an event: `addevent` or `ae`
+Adds a new event, which can be optionally linked to any number of existing friends.
+
+**Format**: `addevent n/EVENT_NAME dt/DATE_TIME [d/DESCRIPTION] [f/FRIEND_NAME]...`
+
+* There **cannot** be any duplicate events with the same name and date.
+* The `DATE_TIME` must be given in the following format: `DD-MM-YYYY hhmm`
+* Individual fields are parsed intelligently where possible. Here are two examples (not comprehensive):
+ * A day-of-month that is out of range for the given month will be automatically corrected to the last valid day-of-month. (`31-06-2020 1200` will be automatically adjusted to `30-06-2020 1200`)
+ * If the time is given as `2400`, the final date-time will automatically jump to the next day with time `0000`
+* All given `FRIEND_NAME` values must match the `NAME` of an existing friend in _Amigos_.
+
+**Examples**:
+* `addevent n/John’s Birthday dt/15-08-2022 1700 d/Remember to get a present! f/John Doe f/Alex Yeo`
+
+
+
+* `addevent n/Christmas Party dt/25-12-2022 1930`
-### Archiving data files `[coming in v2.0]`
+### Editing an event: `editevent` or `ee`
+Edits an existing event.
+
+**Format**: `editevent INDEX [n/EVENT_NAME] [dt/DATE_TIME] [d/DESCRIPTION] [af/ADD_FRIEND_NAME]... [rf/REMOVE_FRIEND_NAME]...`
+
+* Edits the event at the specified `INDEX`. The index refers to the index number shown in the displayed events list. The index **must be a positive integer** 1, 2, 3, ...
+* At least one of the optional fields must be provided.
+* The `EVENT_NAME`, `DATE_TIME` and `DESCRIPTION` arguments directly overwrite the existing details.
+* The `ADD_FRIEND_NAME` and `REMOVE_FRIEND_NAME` arguments add/remove friends tied to the event respectively, and must match the `NAME` of an existing friend in _Amigos_.
+
+**Examples**:
+* `editevent 2 dt/16-08-2022 1600 af/Jacky Jones rf/Sarah Lim rf/Alex Yeo` will edit the date & time of the 2nd event to `16-08-2022 1600`, adds `Jacky Jones`, and removes `Sarah Lim` and `Alex Yeo` from the event.
+
+### Deleting events: `deleteevent` or `de`
+Delete existing event(s).
+
+**Format**: `deleteevent INDEX`
+
+* Deletes the event at the specified `INDEX`.
+* The index refers to the index number shown in the displayed events list.
+* The index **must be a positive integer** 1, 2, 3, ...
+
+**Examples**:
+* `deleteevent 2`
+
+### List all events : `listevents` or `le`
+Lists all the upcoming events in _Amigos_.
+
+**Format**: `listevents [-a]`
+
+* Switches GUI to the events tab
+* if the `-a` flag is omitted it will only show upcoming events (Events with date and time after the system's date and time)
+* if the `-a` flag is provided it will show all events in _Amigos_
+
+**Examples**:
+* `listevents`
+* `listevents -a`
+
+### Find events : `findevent` or `fe`
+
+Find events in _Amigos_ whose properties match the given search criteria.
+
+**Format**: `findevent [n/EVENT_NAME_SUBSTRING] [ds/DATE_START] [de/DATE_END] [f/FRIEND_NAME_SUBSTRING]...`
+
+* For search by `EVENT_NAME_KEYWORD`/`FRIEND_NAME_KEYWORD`:
+ * The search is **case-insensitive**. e.g john will match John
+ * For each search field, an event is a match if the search substring matches
+ * e.g n/Birthday will match events with names John's Birthday, Bob's Birthday
+ * e.g. f/joe will match events containing either Joe Maggio or Joe Allen in the friends list.
+ * Any leading or trailing whitespace in the search substring will be trimmed.
+
+* For search by `DATE`:
+ * The date must be given as follows: `DD-MM-YYYY`
+ * `DATE_START` and `DATE_END` set the inclusive starting and ending date range to filter events by respectively
+ * The date is interpreted intelligently where possible, similar to `DATE_TIME` in `addevent`
+ * It is acceptable to provide only the `DATE_START` or `DATE_END`, if desired.
+* At least one of the optional fields must be provided
+* If more than one field is given, only events with matches for **all** search criteria will be shown.
+* All events, including past ones, will be checked for matches.
+
+**Examples**:
+* `findevent n/dinner ds/20-03-2022` returns events starting from 20 Mar 2022 with an event name containing 'dinner'
+* `findevent f/Joe f/John` will return events with a friends list containing both 'joe' and 'john'
+
+## Insights
+
+_Amigos_ aims to help you improve your relationships by providing the tools to reflect on your relationships.
+
+### Review insights of your friends: `showinsights` or `si`
+
+Shows insights about friends in _Amigos_. Switches GUI to the Insights tab.
+
+**Format**: `showinsights`
+
+
+
+## Miscellaneous
+### Viewing help : `help`
+
+Shows a message explaining how to access the help page.
+
+**Format**: `help`
+
+### Clearing all existing data : `clear`
+
+Clears all existing friends, events, and logs in _Amigos_.
+
+**Format**: `clear`
+
+### Exiting the program : `exit`
+
+Exits the program.
+
+**Format**: `exit`
+
+### Saving the data
+
+_Amigos_ data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Please do not manually edit the save file to avoid the risk of data corruption.
-_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 _Amigos_ 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`
+| Action | Command Alias | Format, Examples |
+|----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add Friend** | `af` | `addfriend n/NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DESCRIPTION] [t/TAG]...` e.g., `addfriend n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 d/Physics Major, Sarah’s friend. Met at Freshman Dinner. t/friend t/classmate` |
+| **Edit friend** | `ef` | `editfriend INDEX ? cn/CURRENT_NAME [nn/NEW_NAME] [p/NEW_PHONE_NUMBER] [e/NEW_EMAIL] [a/NEW_ADDRESS] [d/NEW_DESCRIPTION] [t/TAG]...` e.g., `editfriend 1 a/John street, block 456, #01-01 e/johndoe@example.com` |
+| **Delete Friend** | `df` | `deletefriend INDEX ? n/NAME` e.g., `deletefriend n/John Doe`, `deletefriend 1` |
+| **Show a specific friend** | `sf` | `showfriend INDEX ? n/NAME` |
+| **List all friends** | `lf` | `listfriends` |
+| **Find friends** | `ff` | `findfriend [n/NAME_KEYWORD]... [ttl/LOG_TITLE_KEYWORD]... [t/TAG_KEYWORD]...` e.g, `findfriend n/Amy Tom` |
+| **Add log** | `al` | `addlog INDEX ? n/NAME t/TITLE [d/DESCRIPTION]` |
+| **Edit log** | `el` | `editlog INDEX ? n/NAME id/LOG_INDEX [ttl/NEW_TITLE] [d/NEW_DESCRIPTION]` |
+| **Delete log** | `dl` | `deletelog INDEX ? n/NAME [id/LOG_INDEX] [-a]` |
+| **Add Event** | `ae` | `addevent n/EVENT_NAME dt/DATE_TIME [d/DESCRIPTION] [f/FRIEND_NAME]...` e.g.,`addevent n/John’s Birthday dt/15-08-2021 1700 d/Remember to get a present! f/John Low f/Alex Yeo` |
+| **Edit Event** | `ee` | `editevent INDEX [n/EVENT_NAME] [dt/DATE_TIME] [d/DESCRIPTION] [af/ADD_FRIEND_NAME]... rf/[REMOVE_FRIEND_NAME]...` e.g., `editevent 2 dt/16-08-2022 1600 af/Jacky Jones rf/Sarah Lim rf/Alex Yeo` |
+| **Delete Event** | `de` | `deleteevent INDEX` e.g., `deleteevent 2` |
+| **List all events** | `le` | `listevents [-a]` |
+| **Find events** | `fe` | `findevent [n/EVENT_NAME_SUBSTRING] [ds/DATE_START] [de/DATE_END] [f/FRIEND_NAME_SUBSTRING]...` e.g.,`findevent n/dinner ds/20-03-2022 f/Maggie` |
+ | **Show insights** | `si` | `showinsights` |
+| **Help** | NA | `help` |
+| **Clear** | NA | `clear` |
+| **Exit** | NA | `exit` |
+
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..f84f1d362a8 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "Amigos"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2122S2-CS2103-F09-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..8d0198a21cf 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: "Amigos";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddFriendSequenceDiagram.puml b/docs/diagrams/AddFriendSequenceDiagram.puml
new file mode 100644
index 00000000000..033e9dd9d58
--- /dev/null
+++ b/docs/diagrams/AddFriendSequenceDiagram.puml
@@ -0,0 +1,100 @@
+@startuml
+!include style.puml
+
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant ":ParserUtil" as ParserUtil LOGIC_COLOR
+participant "d:AddCommand" as AddCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "p:Person" as Person MODEL_COLOR
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("addfriend \n n/John Doe p/97367116 \n e/john_doe@test.com")
+
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("addfriend \n n/John Doe p/97367116 \n e/john_doe@test.com")
+activate AddressBookParser
+
+create AddCommandParser
+AddressBookParser -> AddCommandParser
+activate AddCommandParser
+
+AddCommandParser --> AddressBookParser
+deactivate AddCommandParser
+
+AddressBookParser -> AddCommandParser : parse("n/John Doe \n p/97367116 \n e/john_doe@test.com")
+activate AddCommandParser
+
+AddCommandParser -> ParserUtil : parseName("John Doe")
+activate ParserUtil
+ParserUtil --> AddCommandParser
+deactivate ParserUtil
+
+AddCommandParser -> ParserUtil : parsePhone("97367116")
+activate ParserUtil
+ParserUtil --> AddCommandParser
+deactivate ParserUtil
+
+AddCommandParser -> ParserUtil : parseEmail("john_doe@test.com")
+activate ParserUtil
+ParserUtil --> AddCommandParser
+deactivate ParserUtil
+
+create Person
+AddCommandParser -> Person
+activate Person
+Person --> AddCommandParser : p
+deactivate Person
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+Person -[hidden]-> AddCommandParser
+destroy Person
+
+create AddCommand
+AddCommandParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddCommandParser : d
+deactivate AddCommand
+
+AddCommandParser --> AddressBookParser : d
+deactivate AddCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddCommandParser -[hidden]-> AddressBookParser
+destroy AddCommandParser
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+AddCommand -> Model : addPerson(p)
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+create CommandResult
+AddCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddCommand
+deactivate CommandResult
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
+
diff --git a/docs/diagrams/AddLogSequenceDiagram.puml b/docs/diagrams/AddLogSequenceDiagram.puml
new file mode 100644
index 00000000000..03a34adb3c8
--- /dev/null
+++ b/docs/diagrams/AddLogSequenceDiagram.puml
@@ -0,0 +1,109 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddLogCommandParser" as AddLogCommandParser LOGIC_COLOR
+participant "a:AddLogCommand" as AddLogCommand LOGIC_COLOR
+participant "d:AddLogDescriptor" as AddLogDescriptor LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+participant "newLog:Log" as newLog LOGIC_COLOR
+participant "addedLogPerson:Person" as addedLogPerson LOGIC_COLOR
+end box
+
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("addlog JOHN DOE ttl/some log title")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("addlog JOHN DOE ttl/some log title")
+activate AddressBookParser
+
+create AddLogCommandParser
+AddressBookParser -> AddLogCommandParser
+activate AddLogCommandParser
+
+AddLogCommandParser --> AddressBookParser
+deactivate AddLogCommandParser
+
+AddressBookParser -> AddLogCommandParser : parse("JOHN DOE ttl/some log title")
+activate AddLogCommandParser
+
+create AddLogDescriptor
+AddLogCommandParser -> AddLogDescriptor
+activate AddLogDescriptor
+
+AddLogDescriptor -> AddLogCommandParser : d
+deactivate AddLogDescriptor
+
+create AddLogCommand
+AddLogCommandParser -> AddLogCommand
+activate AddLogCommand
+
+AddLogCommand --> AddLogCommandParser : a
+deactivate AddLogCommand
+
+AddLogCommandParser --> AddressBookParser : a
+deactivate AddLogCommandParser
+
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddLogCommandParser -[hidden]-> AddressBookParser
+destroy AddLogCommandParser
+
+AddressBookParser --> LogicManager : a
+deactivate AddressBookParser
+
+LogicManager -> AddLogCommand : execute()
+activate AddLogCommand
+
+AddLogCommand -> Model : getFilteredPersonList()
+activate Model
+
+Model --> AddLogCommand : List
+deactivate Model
+
+AddLogCommand -> AddLogCommand : createAddedLogPerson()
+activate AddLogCommand
+
+AddLogCommand -> AddLogDescriptor : getLogsAfterAdd()
+activate AddLogDescriptor
+
+create newLog
+AddLogDescriptor -> newLog
+activate newLog
+newLog -> AddLogDescriptor
+deactivate newLog
+
+AddLogDescriptor -> AddLogCommand : List
+deactivate AddLogDescriptor
+
+create addedLogPerson
+AddLogCommand -> addedLogPerson
+activate addedLogPerson
+addedLogPerson -> AddLogCommand
+deactivate addedLogPerson
+
+AddLogCommand -> AddLogCommand : addedLogPerson
+deactivate AddLogCommand
+
+AddLogCommand -> Model : setPerson(addedLogPerson)
+activate Model
+deactivate Model
+
+create CommandResult
+AddLogCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddLogCommand
+deactivate CommandResult
+
+AddLogCommand --> LogicManager : result
+deactivate AddLogCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EditEvent.puml b/docs/diagrams/EditEvent.puml
new file mode 100644
index 00000000000..9f06ce2d8de
--- /dev/null
+++ b/docs/diagrams/EditEvent.puml
@@ -0,0 +1,28 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User executes EditEventCommand;
+:EditEventParser parses the command provided by user;
+if () then ([Command is valid])
+ :EditEventCommand is executed;
+ if () then ([Index is valid])
+ if () then ([Edited Event is Duplicate])
+ :Show duplicate event message;
+ else ([else])
+ if () then ([Edited friend names are valid])
+ :Event at index is replaced with edited event;
+ :Show EditEventCommand success message;
+ else ([else])
+ :Show invalid friend error;
+ endif
+ endif
+ else ([Index is invalid])
+ :Show invalid index error;
+ endif
+else ([Command is invalid])
+ :Show parse error message;
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/EventPersonRelationshipClassDiagram.puml b/docs/diagrams/EventPersonRelationshipClassDiagram.puml
new file mode 100644
index 00000000000..fbf9a29f9c7
--- /dev/null
+++ b/docs/diagrams/EventPersonRelationshipClassDiagram.puml
@@ -0,0 +1,23 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+AddressBook *-right-> "1" UniqueEventList
+AddressBook *-right-> "1" UniquePersonList
+UniquePersonList -[hidden]down- UniqueEventList
+UniquePersonList -[hidden]down- UniqueEventList
+
+UniquePersonList -right-> "*" Person
+Person *-> "1" FriendName
+
+UniqueEventList -right-> "*" Event
+
+Event -right-> "*" FriendName
+
+Event *--> "1" EventName
+Event *--> "1" DateTime
+Event *--> "1" Description
+
+@enduml
diff --git a/docs/diagrams/ExpandedPersonCardObjectDiagram.puml b/docs/diagrams/ExpandedPersonCardObjectDiagram.puml
new file mode 100644
index 00000000000..1679ce9cf57
--- /dev/null
+++ b/docs/diagrams/ExpandedPersonCardObjectDiagram.puml
@@ -0,0 +1,45 @@
+
+
+@startuml
+
+
+object ":ExpandedPersonCard" as expcard
+object "name:Label" as name
+object "address:Textflow" as address
+object "email:Textflow" as email
+object "description:Textflow" as description
+object "tags:FlowPane" as tags
+object "upcomingEventsPanel:StackPane" as upcomingEvents
+object "logs:Label" as logs
+object ":Label" as tagLabelOne
+object ":Label" as tagLabelTwo
+object ":EventCard" as EventCardOne
+object ":EventCard" as EventCardTwo
+
+name : text = "Alex Yeoh"
+address : text = "Blk 10 Clementi Road"
+email : text = "alexyeoh@gmail.com"
+description : text = "Has a corgi"
+tagLabelOne : text = "Friend"
+tagLabelTwo : text = "Neighbour"
+logs : text = "1. My First Log! \n Alex is my first friend added."
+EventCardOne : name = "Alex's Birthday"
+EventCardOne : dateTime = "15-08-2022 1700"
+EventCardTwo : name = "Class outing"
+EventCardTwo : dateTime = "19-10-2022 0800"
+
+expcard -down-> name
+expcard -down-> address
+expcard -down-> email
+expcard -down-> description
+expcard -down-> upcomingEvents
+upcomingEvents -down-> EventCardOne
+upcomingEvents -down-> EventCardTwo
+expcard -down-> tags
+expcard -down-> logs
+description -[hidden]-> tags
+logs -[hidden]-> upcomingEvents
+tags -down-> tagLabelOne
+tags -down-> tagLabelTwo
+
+@enduml
diff --git a/docs/diagrams/FindEventSequenceDiagram.puml b/docs/diagrams/FindEventSequenceDiagram.puml
new file mode 100644
index 00000000000..99edce471bf
--- /dev/null
+++ b/docs/diagrams/FindEventSequenceDiagram.puml
@@ -0,0 +1,53 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":FindEventCommandParser" as FindEventCommandParser LOGIC_COLOR
+participant "p1:EventNameContainsSubstringPredicate" as EventNameContainsSubstring LOGIC_COLOR
+participant "p2:EventDateIsAfterPredicate" as EventDateIsAfter LOGIC_COLOR
+participant "f:FindEventCommand" as FindEventCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> FindEventCommandParser : parse("n/Birthday ds/15-05-2022")
+activate FindEventCommandParser
+
+create EventNameContainsSubstring
+FindEventCommandParser -> EventNameContainsSubstring : "Birthday"
+activate EventNameContainsSubstring
+
+EventNameContainsSubstring --> FindEventCommandParser
+deactivate EventNameContainsSubstring
+
+create EventDateIsAfter
+FindEventCommandParser -> EventDateIsAfter : 15-05-2022
+activate EventDateIsAfter
+
+EventDateIsAfter --> FindEventCommandParser
+deactivate EventDateIsAfter
+
+create FindEventCommand
+FindEventCommandParser -> FindEventCommand : [p1, p2]
+activate FindEventCommand
+
+FindEventCommand --> FindEventCommandParser
+deactivate FindEventCommand
+
+[<-- FindEventCommandParser : f
+deactivate FindEventCommandParser
+
+[-> FindEventCommand : execute()
+activate FindEventCommand
+
+FindEventCommand -> Model : updateFilteredEventList(p1 && p2)
+activate Model
+Model --> FindEventCommand
+deactivate Model
+
+[<-- FindEventCommand
+deactivate FindEventCommand
+
+@enduml
diff --git a/docs/diagrams/FindFriendActivityDiagram.puml b/docs/diagrams/FindFriendActivityDiagram.puml
new file mode 100644
index 00000000000..e5c4957cde2
--- /dev/null
+++ b/docs/diagrams/FindFriendActivityDiagram.puml
@@ -0,0 +1,28 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User enters a FindCommand;
+:FindCommandParser parses the inputs provided by user;
+if () then ([Inputs are valid])
+ :Inputs are grouped into Set, \nSet, Set;
+ :FindCommmandParser generates a FriendFilterPredicate;
+ :FindCommandParser returns a FindCommand;
+ while () is ([else])
+
+ if () then ([Substring of friend's \nname OR tags OR log \ntitles matches user input])
+ :Friend is shown on the filtered list;
+
+ else ([else])
+ :Friend is not shown on the filtered list;
+
+ endif
+
+ endwhile ([All friends in Amigos \nhave been processed])
+
+else ([Inputs are invalid])
+ :Show parse error message;
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/LogFeaturesModelClassDiagram.puml b/docs/diagrams/LogFeaturesModelClassDiagram.puml
new file mode 100644
index 00000000000..65872236861
--- /dev/null
+++ b/docs/diagrams/LogFeaturesModelClassDiagram.puml
@@ -0,0 +1,17 @@
+
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Person -up-> "*" Tag
+
+Person *--> Name
+Person *--> Phone
+Person *--> Email
+Person *--> Address
+Person *-right-> "1" UniqueLogList
+UniqueLogList *-> "*" Log
+
+@enduml
diff --git a/docs/diagrams/LogFeaturesStorageClassDiagram.puml b/docs/diagrams/LogFeaturesStorageClassDiagram.puml
new file mode 100644
index 00000000000..8f94f935cd9
--- /dev/null
+++ b/docs/diagrams/LogFeaturesStorageClassDiagram.puml
@@ -0,0 +1,25 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor STORAGE_COLOR
+skinparam classBackgroundColor STORAGE_COLOR
+
+package AddressBookStorage{
+
+
+package "Within JsonSerializableAddressBook" #F4F6F6{
+Class JsonSerializableAddressBook
+Class JsonAdaptedPerson
+Class JsonAdaptedTag
+Class JsonAdaptedLog
+}
+
+}
+
+Class HiddenOutside #FFFFFF
+
+JsonSerializableAddressBook -right-> "*" JsonAdaptedPerson
+JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonAdaptedPerson --> "*" JsonAdaptedLog
+
+@enduml
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/LogicSequenceDiagram.puml
similarity index 100%
rename from docs/diagrams/tracing/LogicSequenceDiagram.puml
rename to docs/diagrams/LogicSequenceDiagram.puml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..c42936c6877 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -13,12 +13,16 @@ Class ModelManager
Class UserPrefs
Class UniquePersonList
+Class UniqueEventList
+
Class Person
-Class Address
-Class Email
-Class Name
-Class Phone
-Class Tag
+Class Event
+
+Class UniqueLogList
+Class Log
+
+Class PersonInsight
+Class Insight
}
@@ -35,16 +39,21 @@ ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
-
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
-
-ModelManager -->"~* filtered" Person
+AddressBook *-d-> "1" UniqueEventList
+
+UniquePersonList -r-> "~* all" Person
+UniqueEventList --> "~* all" Event
+
+Person *--> "1" UniqueLogList
+UniqueLogList *--> "*" Log
+
+PersonInsight *--> Insight
+Insight .left..> Event
+Insight .left.> Person
+
+ModelManager ---->"~* filtered" Person
+ModelManager ----->"~* filtered" Event
+
@enduml
+
+
diff --git a/docs/diagrams/ShowEvents.puml b/docs/diagrams/ShowEvents.puml
new file mode 100644
index 00000000000..b20a3f95e9b
--- /dev/null
+++ b/docs/diagrams/ShowEvents.puml
@@ -0,0 +1,53 @@
+@startuml
+
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "s:ShowEventsCommand" as ShowEventsCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("listevents -a")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("listevents -a")
+activate AddressBookParser
+
+create ShowEventsCommand
+AddressBookParser -> ShowEventsCommand
+activate ShowEventsCommand
+
+ShowEventsCommand --> AddressBookParser : s
+deactivate ShowEventsCommand
+
+AddressBookParser --> LogicManager : s
+deactivate AddressBookParser
+
+LogicManager -> ShowEventsCommand : execute()
+activate ShowEventsCommand
+
+ShowEventsCommand -> Model : updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS)
+activate Model
+
+Model --> ShowEventsCommand
+deactivate Model
+
+create CommandResult
+ShowEventsCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ShowEventsCommand
+deactivate CommandResult
+
+ShowEventsCommand --> LogicManager : result
+deactivate ShowEventsCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 760305e0e58..5004d6a1d75 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -19,7 +19,10 @@ Class "<>\nAddressBookStorage" as AddressBookStorage
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
+Class JsonAdaptedEvent
Class JsonAdaptedTag
+Class JsonAdaptedName
+Class JsonAdaptedLog
}
}
@@ -38,6 +41,10 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
+JsonSerializableAddressBook --> "*" JsonAdaptedEvent
JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonAdaptedPerson --> "*" JsonAdaptedLog
+
+JsonAdaptedEvent --> "*" JsonAdaptedName
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..3752cd0deed 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -13,6 +13,12 @@ Class HelpWindow
Class ResultDisplay
Class PersonListPanel
Class PersonCard
+Class PersonInsightListPanel
+Class PersonInsightCard
+Class ExpandedPersonListPanel
+Class ExpandedPersonCard
+Class EventListPanel
+Class EventCard
Class StatusBarFooter
Class CommandBox
}
@@ -26,28 +32,44 @@ Class HiddenLogic #FFFFFF
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> Ui
-
-UiManager .left.|> Ui
-UiManager -down-> "1" MainWindow
-MainWindow *-down-> "1" CommandBox
-MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
-MainWindow *-down-> "1" StatusBarFooter
+HiddenOutside .> Ui
+
+UiManager ..left.|> Ui
+UiManager --down-> "1" MainWindow
+MainWindow *--down-> "1" CommandBox
+MainWindow *--down-> "1" ResultDisplay
+MainWindow *--down-> "1" PersonListPanel
+MainWindow *--down-> "1" PersonInsightListPanel
+MainWindow *--down-> "1" ExpandedPersonListPanel
+MainWindow *--down-> "1" EventListPanel
+MainWindow *--down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
PersonListPanel -down-> "*" PersonCard
+PersonInsightListPanel -down-> "*" PersonInsightCard
+ExpandedPersonListPanel -down-> "1" ExpandedPersonCard
+EventListPanel -down-> "*" EventCard
+
MainWindow -left-|> UiPart
-ResultDisplay --|> UiPart
-CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
-StatusBarFooter --|> UiPart
-HelpWindow --|> UiPart
+ResultDisplay ---|> UiPart
+CommandBox ---|> UiPart
+PersonListPanel ---|> UiPart
+PersonCard ---|> UiPart
+PersonInsightListPanel ---|> UiPart
+PersonInsightCard ---|> UiPart
+ExpandedPersonListPanel ---|> UiPart
+ExpandedPersonCard ---|> UiPart
+EventListPanel --|> UiPart
+EventCard ---|> UiPart
+StatusBarFooter ---|> UiPart
+HelpWindow ---|> UiPart
+ExpandedPersonCard ..> Model
+EventCard ..> Model
PersonCard ..> Model
+PersonInsightCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/images/AddEventExample.png b/docs/images/AddEventExample.png
new file mode 100644
index 00000000000..c20ade67bc7
Binary files /dev/null and b/docs/images/AddEventExample.png differ
diff --git a/docs/images/AddFriendSequenceDiagram.png b/docs/images/AddFriendSequenceDiagram.png
new file mode 100644
index 00000000000..3b7f86ac3b8
Binary files /dev/null and b/docs/images/AddFriendSequenceDiagram.png differ
diff --git a/docs/images/AddLogExample.png b/docs/images/AddLogExample.png
new file mode 100644
index 00000000000..2f91865ef6b
Binary files /dev/null and b/docs/images/AddLogExample.png differ
diff --git a/docs/images/AddLogSequenceDiagram.png b/docs/images/AddLogSequenceDiagram.png
new file mode 100644
index 00000000000..e247cfc2ded
Binary files /dev/null and b/docs/images/AddLogSequenceDiagram.png differ
diff --git a/docs/images/Addfriend_JohnDoe.png b/docs/images/Addfriend_JohnDoe.png
new file mode 100644
index 00000000000..3baed9cdfa9
Binary files /dev/null and b/docs/images/Addfriend_JohnDoe.png differ
diff --git a/docs/images/DeleteFriend_JohnDoe.png b/docs/images/DeleteFriend_JohnDoe.png
new file mode 100644
index 00000000000..bd1b6abc961
Binary files /dev/null and b/docs/images/DeleteFriend_JohnDoe.png differ
diff --git a/docs/images/DeleteLogExample.png b/docs/images/DeleteLogExample.png
new file mode 100644
index 00000000000..fbd6cf77ce9
Binary files /dev/null and b/docs/images/DeleteLogExample.png differ
diff --git a/docs/images/EditEvent.png b/docs/images/EditEvent.png
new file mode 100644
index 00000000000..d4795c0a875
Binary files /dev/null and b/docs/images/EditEvent.png differ
diff --git a/docs/images/EventPage.png b/docs/images/EventPage.png
new file mode 100644
index 00000000000..5702d58a1d8
Binary files /dev/null and b/docs/images/EventPage.png differ
diff --git a/docs/images/EventPersonRelationshipClassDiagram.png b/docs/images/EventPersonRelationshipClassDiagram.png
new file mode 100644
index 00000000000..f87e1accc84
Binary files /dev/null and b/docs/images/EventPersonRelationshipClassDiagram.png differ
diff --git a/docs/images/ExpandedPersonCard_ObjectDiagram.png b/docs/images/ExpandedPersonCard_ObjectDiagram.png
new file mode 100644
index 00000000000..30ff4443ad7
Binary files /dev/null and b/docs/images/ExpandedPersonCard_ObjectDiagram.png differ
diff --git a/docs/images/FindEventSequenceDiagram.png b/docs/images/FindEventSequenceDiagram.png
new file mode 100644
index 00000000000..fb938ef8c72
Binary files /dev/null and b/docs/images/FindEventSequenceDiagram.png differ
diff --git a/docs/images/FindFriendActivityDiagram.png b/docs/images/FindFriendActivityDiagram.png
new file mode 100644
index 00000000000..394b76aa50f
Binary files /dev/null and b/docs/images/FindFriendActivityDiagram.png differ
diff --git a/docs/images/FindFriendExample.png b/docs/images/FindFriendExample.png
new file mode 100644
index 00000000000..45b8c2b3fcf
Binary files /dev/null and b/docs/images/FindFriendExample.png differ
diff --git a/docs/images/GUITestImages/AddError.png b/docs/images/GUITestImages/AddError.png
new file mode 100644
index 00000000000..b2ca15d07e8
Binary files /dev/null and b/docs/images/GUITestImages/AddError.png differ
diff --git a/docs/images/GUITestImages/AddEventError.png b/docs/images/GUITestImages/AddEventError.png
new file mode 100644
index 00000000000..a0b35041c7a
Binary files /dev/null and b/docs/images/GUITestImages/AddEventError.png differ
diff --git a/docs/images/GUITestImages/AddLogError.png b/docs/images/GUITestImages/AddLogError.png
new file mode 100644
index 00000000000..2d4479c111f
Binary files /dev/null and b/docs/images/GUITestImages/AddLogError.png differ
diff --git a/docs/images/GUITestImages/AfterAdd.png b/docs/images/GUITestImages/AfterAdd.png
new file mode 100644
index 00000000000..6ca730f756a
Binary files /dev/null and b/docs/images/GUITestImages/AfterAdd.png differ
diff --git a/docs/images/GUITestImages/AfterAddEvent.png b/docs/images/GUITestImages/AfterAddEvent.png
new file mode 100644
index 00000000000..08a070c5e16
Binary files /dev/null and b/docs/images/GUITestImages/AfterAddEvent.png differ
diff --git a/docs/images/GUITestImages/AfterAddLog.png b/docs/images/GUITestImages/AfterAddLog.png
new file mode 100644
index 00000000000..213c1474ea2
Binary files /dev/null and b/docs/images/GUITestImages/AfterAddLog.png differ
diff --git a/docs/images/GUITestImages/AfterDelete.png b/docs/images/GUITestImages/AfterDelete.png
new file mode 100644
index 00000000000..e9ea0257bf0
Binary files /dev/null and b/docs/images/GUITestImages/AfterDelete.png differ
diff --git a/docs/images/GUITestImages/AfterDeleteEvent.png b/docs/images/GUITestImages/AfterDeleteEvent.png
new file mode 100644
index 00000000000..27a7076d873
Binary files /dev/null and b/docs/images/GUITestImages/AfterDeleteEvent.png differ
diff --git a/docs/images/GUITestImages/AfterDeleteLog.png b/docs/images/GUITestImages/AfterDeleteLog.png
new file mode 100644
index 00000000000..4bda896fd8b
Binary files /dev/null and b/docs/images/GUITestImages/AfterDeleteLog.png differ
diff --git a/docs/images/GUITestImages/AfterEdit.png b/docs/images/GUITestImages/AfterEdit.png
new file mode 100644
index 00000000000..246cb3d930c
Binary files /dev/null and b/docs/images/GUITestImages/AfterEdit.png differ
diff --git a/docs/images/GUITestImages/AfterEditEvent.png b/docs/images/GUITestImages/AfterEditEvent.png
new file mode 100644
index 00000000000..5629b0412c6
Binary files /dev/null and b/docs/images/GUITestImages/AfterEditEvent.png differ
diff --git a/docs/images/GUITestImages/AfterEditLog.png b/docs/images/GUITestImages/AfterEditLog.png
new file mode 100644
index 00000000000..add9027158f
Binary files /dev/null and b/docs/images/GUITestImages/AfterEditLog.png differ
diff --git a/docs/images/GUITestImages/AfterFindEvent.png b/docs/images/GUITestImages/AfterFindEvent.png
new file mode 100644
index 00000000000..1551a940ba8
Binary files /dev/null and b/docs/images/GUITestImages/AfterFindEvent.png differ
diff --git a/docs/images/GUITestImages/AfterFindFriend.png b/docs/images/GUITestImages/AfterFindFriend.png
new file mode 100644
index 00000000000..ecb5d345ba2
Binary files /dev/null and b/docs/images/GUITestImages/AfterFindFriend.png differ
diff --git a/docs/images/GUITestImages/AfterShowEvents.png b/docs/images/GUITestImages/AfterShowEvents.png
new file mode 100644
index 00000000000..4d4295f93ed
Binary files /dev/null and b/docs/images/GUITestImages/AfterShowEvents.png differ
diff --git a/docs/images/GUITestImages/AfterShowFriend.png b/docs/images/GUITestImages/AfterShowFriend.png
new file mode 100644
index 00000000000..704d3849e9e
Binary files /dev/null and b/docs/images/GUITestImages/AfterShowFriend.png differ
diff --git a/docs/images/GUITestImages/AfterShowFriends.png b/docs/images/GUITestImages/AfterShowFriends.png
new file mode 100644
index 00000000000..f516fda3c85
Binary files /dev/null and b/docs/images/GUITestImages/AfterShowFriends.png differ
diff --git a/docs/images/GUITestImages/BeforeAdd.png b/docs/images/GUITestImages/BeforeAdd.png
new file mode 100644
index 00000000000..6d9f9801b8a
Binary files /dev/null and b/docs/images/GUITestImages/BeforeAdd.png differ
diff --git a/docs/images/GUITestImages/BeforeAddEvent.png b/docs/images/GUITestImages/BeforeAddEvent.png
new file mode 100644
index 00000000000..062883c6d70
Binary files /dev/null and b/docs/images/GUITestImages/BeforeAddEvent.png differ
diff --git a/docs/images/GUITestImages/BeforeAddLog.png b/docs/images/GUITestImages/BeforeAddLog.png
new file mode 100644
index 00000000000..b6eb06a97c2
Binary files /dev/null and b/docs/images/GUITestImages/BeforeAddLog.png differ
diff --git a/docs/images/GUITestImages/BeforeDelete.png b/docs/images/GUITestImages/BeforeDelete.png
new file mode 100644
index 00000000000..7ed3131bf33
Binary files /dev/null and b/docs/images/GUITestImages/BeforeDelete.png differ
diff --git a/docs/images/GUITestImages/BeforeDeleteEvent.png b/docs/images/GUITestImages/BeforeDeleteEvent.png
new file mode 100644
index 00000000000..919cc301287
Binary files /dev/null and b/docs/images/GUITestImages/BeforeDeleteEvent.png differ
diff --git a/docs/images/GUITestImages/BeforeDeleteLog.png b/docs/images/GUITestImages/BeforeDeleteLog.png
new file mode 100644
index 00000000000..0761fdb5b71
Binary files /dev/null and b/docs/images/GUITestImages/BeforeDeleteLog.png differ
diff --git a/docs/images/GUITestImages/BeforeEdit.png b/docs/images/GUITestImages/BeforeEdit.png
new file mode 100644
index 00000000000..aec9e684df0
Binary files /dev/null and b/docs/images/GUITestImages/BeforeEdit.png differ
diff --git a/docs/images/GUITestImages/BeforeEditEvent.png b/docs/images/GUITestImages/BeforeEditEvent.png
new file mode 100644
index 00000000000..12222bde1bf
Binary files /dev/null and b/docs/images/GUITestImages/BeforeEditEvent.png differ
diff --git a/docs/images/GUITestImages/BeforeEditLog.png b/docs/images/GUITestImages/BeforeEditLog.png
new file mode 100644
index 00000000000..7aa99996d5e
Binary files /dev/null and b/docs/images/GUITestImages/BeforeEditLog.png differ
diff --git a/docs/images/GUITestImages/BeforeFindEvent.png b/docs/images/GUITestImages/BeforeFindEvent.png
new file mode 100644
index 00000000000..d5c8300a21f
Binary files /dev/null and b/docs/images/GUITestImages/BeforeFindEvent.png differ
diff --git a/docs/images/GUITestImages/BeforeFindFriend.png b/docs/images/GUITestImages/BeforeFindFriend.png
new file mode 100644
index 00000000000..8ea7dfbc782
Binary files /dev/null and b/docs/images/GUITestImages/BeforeFindFriend.png differ
diff --git a/docs/images/GUITestImages/BeforeShowEvents.png b/docs/images/GUITestImages/BeforeShowEvents.png
new file mode 100644
index 00000000000..6ee80d03eb2
Binary files /dev/null and b/docs/images/GUITestImages/BeforeShowEvents.png differ
diff --git a/docs/images/GUITestImages/BeforeShowFriend.png b/docs/images/GUITestImages/BeforeShowFriend.png
new file mode 100644
index 00000000000..a21909b8ec1
Binary files /dev/null and b/docs/images/GUITestImages/BeforeShowFriend.png differ
diff --git a/docs/images/GUITestImages/BeforeShowFriends.png b/docs/images/GUITestImages/BeforeShowFriends.png
new file mode 100644
index 00000000000..6d23f035b48
Binary files /dev/null and b/docs/images/GUITestImages/BeforeShowFriends.png differ
diff --git a/docs/images/GUITestImages/DeleteError.png b/docs/images/GUITestImages/DeleteError.png
new file mode 100644
index 00000000000..51fd405a73c
Binary files /dev/null and b/docs/images/GUITestImages/DeleteError.png differ
diff --git a/docs/images/GUITestImages/DeleteEventError.png b/docs/images/GUITestImages/DeleteEventError.png
new file mode 100644
index 00000000000..d42523370f6
Binary files /dev/null and b/docs/images/GUITestImages/DeleteEventError.png differ
diff --git a/docs/images/GUITestImages/DeleteLogError.png b/docs/images/GUITestImages/DeleteLogError.png
new file mode 100644
index 00000000000..870b1898ede
Binary files /dev/null and b/docs/images/GUITestImages/DeleteLogError.png differ
diff --git a/docs/images/GUITestImages/EditError.png b/docs/images/GUITestImages/EditError.png
new file mode 100644
index 00000000000..bf3c630bf74
Binary files /dev/null and b/docs/images/GUITestImages/EditError.png differ
diff --git a/docs/images/GUITestImages/EditEventError.png b/docs/images/GUITestImages/EditEventError.png
new file mode 100644
index 00000000000..af9457ab06f
Binary files /dev/null and b/docs/images/GUITestImages/EditEventError.png differ
diff --git a/docs/images/GUITestImages/EditLogError.png b/docs/images/GUITestImages/EditLogError.png
new file mode 100644
index 00000000000..f2183182e6d
Binary files /dev/null and b/docs/images/GUITestImages/EditLogError.png differ
diff --git a/docs/images/GUITestImages/FindEventError.png b/docs/images/GUITestImages/FindEventError.png
new file mode 100644
index 00000000000..1b380deba84
Binary files /dev/null and b/docs/images/GUITestImages/FindEventError.png differ
diff --git a/docs/images/GUITestImages/Help.png b/docs/images/GUITestImages/Help.png
new file mode 100644
index 00000000000..63027360871
Binary files /dev/null and b/docs/images/GUITestImages/Help.png differ
diff --git a/docs/images/GUITestImages/PostClear.png b/docs/images/GUITestImages/PostClear.png
new file mode 100644
index 00000000000..61d8d5cf429
Binary files /dev/null and b/docs/images/GUITestImages/PostClear.png differ
diff --git a/docs/images/GUITestImages/PreClear.png b/docs/images/GUITestImages/PreClear.png
new file mode 100644
index 00000000000..00c669b9f9d
Binary files /dev/null and b/docs/images/GUITestImages/PreClear.png differ
diff --git a/docs/images/GUITestImages/ShowFriendError.png b/docs/images/GUITestImages/ShowFriendError.png
new file mode 100644
index 00000000000..4392a6a26c9
Binary files /dev/null and b/docs/images/GUITestImages/ShowFriendError.png differ
diff --git a/docs/images/GUITestImages/Startup.png b/docs/images/GUITestImages/Startup.png
new file mode 100644
index 00000000000..1fe9939b864
Binary files /dev/null and b/docs/images/GUITestImages/Startup.png differ
diff --git a/docs/images/ListEventsSequenceDiagram.png b/docs/images/ListEventsSequenceDiagram.png
new file mode 100644
index 00000000000..25e7ab144e4
Binary files /dev/null and b/docs/images/ListEventsSequenceDiagram.png differ
diff --git a/docs/images/ListFriendExample.png b/docs/images/ListFriendExample.png
new file mode 100644
index 00000000000..dd269700703
Binary files /dev/null and b/docs/images/ListFriendExample.png differ
diff --git a/docs/images/LogFeaturesModelClassDiagram.png b/docs/images/LogFeaturesModelClassDiagram.png
new file mode 100644
index 00000000000..354d4b81df6
Binary files /dev/null and b/docs/images/LogFeaturesModelClassDiagram.png differ
diff --git a/docs/images/LogFeaturesStorageClassDiagram.png b/docs/images/LogFeaturesStorageClassDiagram.png
new file mode 100644
index 00000000000..39403d841ba
Binary files /dev/null and b/docs/images/LogFeaturesStorageClassDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..2c98b141ff0 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ShowFriendExample.png b/docs/images/ShowFriendExample.png
new file mode 100644
index 00000000000..8d2446c8c96
Binary files /dev/null and b/docs/images/ShowFriendExample.png differ
diff --git a/docs/images/ShowInsightsExample.png b/docs/images/ShowInsightsExample.png
new file mode 100644
index 00000000000..3e01f7eec8d
Binary files /dev/null and b/docs/images/ShowInsightsExample.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 2533a5c1af0..e0627b78c55 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..c803c90ef0f 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..dcf0b835574 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/aryansarswat.png b/docs/images/aryansarswat.png
new file mode 100644
index 00000000000..e8836bc4f96
Binary files /dev/null and b/docs/images/aryansarswat.png differ
diff --git a/docs/images/dionegoh.png b/docs/images/dionegoh.png
new file mode 100644
index 00000000000..dab584e4e0f
Binary files /dev/null and b/docs/images/dionegoh.png differ
diff --git a/docs/images/eventsTab.png b/docs/images/eventsTab.png
new file mode 100644
index 00000000000..92bad7a33c8
Binary files /dev/null and b/docs/images/eventsTab.png differ
diff --git a/docs/images/friendsTab.png b/docs/images/friendsTab.png
new file mode 100644
index 00000000000..c7909226d20
Binary files /dev/null and b/docs/images/friendsTab.png differ
diff --git a/docs/images/insightsTab.png b/docs/images/insightsTab.png
new file mode 100644
index 00000000000..d6a5eb79de3
Binary files /dev/null and b/docs/images/insightsTab.png differ
diff --git a/docs/images/limweiliang.png b/docs/images/limweiliang.png
new file mode 100644
index 00000000000..d8c729de654
Binary files /dev/null and b/docs/images/limweiliang.png differ
diff --git a/docs/images/tanyjnaaman.png b/docs/images/tanyjnaaman.png
new file mode 100644
index 00000000000..b2b0e5e81e6
Binary files /dev/null and b/docs/images/tanyjnaaman.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..549d6db5a84 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,17 +1,16 @@
---
layout: page
-title: AddressBook Level-3
+title: Amigos
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
-
+[](https://github.com/AY2122S2-CS2103-F09-2/tp/actions)
+[](https://codecov.io/gh/AY2122S2-CS2103-F09-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).
+**Amigos is a desktop application for managing your friendships.** 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 Amigos, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested in developing Amigos, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/aryansarswat.md b/docs/team/aryansarswat.md
new file mode 100644
index 00000000000..1b8a5447266
--- /dev/null
+++ b/docs/team/aryansarswat.md
@@ -0,0 +1,58 @@
+---
+layout: page
+title: Aryan Sarswat's Project Portfolio Page
+---
+
+### Project: Amigos
+
+Amigos is a desktop application to help tech-savvy university students manage their friendships by helping them to keep track of important details. It is optimized for use via a Command Line interface while still having the benefits of a Graphical User Interface (GUI). The user enters commands through the GUI which is built using JavaFX. It is written in Java and has over 20kLoC.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=aryansarswat&breakdown=true)
+
+### Summary of Contributions
+
+Given below are my contributions to the project.
+
+1. **New Feature: Tabs**
+ * What it does: It enables to user to view different features in Amigos, i.e. there exist different tabs for each feature (Friends Tab, Events Tab, ShowInsights tab)
+ * Justification: This allows us to view each aspect of Amigos without the clutter which would arise if we fit all our features into one window.
+ * Highlights:
+ * Automatically switches tabs based on the command entered for ease of user. Example if `deleteevent 5` is called and the user is in the friends tab, the tab will be automatically switched to the events tab.
+
+2. **New Feature: EditEvents** Added the ability to edit events which are inside Amigos.
+ * What it does: Allows the user to change any one of the following attributes in events; event name, event date, event description, friends related to events
+ * Justification: It allows the user to edit details of events given that they change, this is alot more efficient than having to delete and event and add a new events with the corrected attributes
+ * Highlights:
+ * To optimize for command-line convenience, instead of just replacing the entire friends list with a new friends list (like in the implementation of tags), the `af/` and the `rf/` allow for adding and removing friends from an event. This allows for a shorter `edtievent` command.
+
+3. **New Feature: ShowEvents**
+ * What it does: Allows the user to view all the events in Amigos, specifically it only shows upcoming events sorted by their dates.
+ * Justification: This is an essential feature for the user as they might need see which events are upcoming or even try to plan their day depending on whether they have an upcoming event.
+ * Highlights:
+ * In the scenario that the user wants to check past events we optimize the command-line interface such that if the flag `-a` is passed it will show **every** event in Amigos even if they have passed.
+4. **New Feature: EventCards for GUI**
+ * What it does: This is the card which represents events in the GUI.
+ * Justification: We designed as such as we wanted to present all the necessary information in a concise yet informative fashion.
+
+5. **Enhancement to existing features**
+ * Abstraction of `FriendName`, `LogName`, `EventName` into a common `Name` class, this reduces the amount of repeated code (Pullrequests [\#104](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/104), [\#105](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/105))
+
+* **Contribution to team-based-tasks**
+ * Maintained issue tracker for milestone v1.3b
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `edtifriend`, `showfriends`, `showfriend` and `showevents` [\#58](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/58)
+ * Developer Guide:
+ * Added User Stories, Glossary and Non-functional requirements
+ * Added Use cases for `addevent`, `edtievent` `showfriends`, `showfriend` and `showevents` [\#44](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/44), [\#57](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/57)
+ * Added implementation details for `editevent`, `showevents` and Tab management [\#143](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/143)
+ * Created a GUI Test documentation
+ * Contains all the various test performed manually on the GUI [\#169](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/169).
+
+* **Community**:
+ * PR reviewed (with non-trivial review comments): [#166](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/166), [#136](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/136)
+
+* **Tools**:
+ * Wrote python script to generate upwards of 500 entries for names, events to stress test the application
+
diff --git a/docs/team/dionegoh.md b/docs/team/dionegoh.md
new file mode 100644
index 00000000000..a64a3773072
--- /dev/null
+++ b/docs/team/dionegoh.md
@@ -0,0 +1,68 @@
+---
+layout: page
+title: Dione Goh's Project Portfolio Page
+---
+### Project: Amigos
+
+Amigos is a desktop application to help tech-savvy university students manage their friendships by helping them to keep
+track of important details. It is optimized for use via a Command Line interface while still having the benefits of a
+Graphical User Interface (GUI). The user enters commands through the GUI which is built using JavaFX.
+It is written in Java and has over 10kLoC.
+
+Given below are my contributions to the project.
+
+1. **New Feature: showfriend**
+ * What it does: Allows the user to view the full details of a friend in Amigos on a single page (`ExpandedPersonCard`).
+ * Justification: We added more attributes for a friend, such as description and logs. We also added events which
+ contain references to `FriendName`. Thus, it is essential for the user to be able to view all these new features
+ for an individual friend in an uncluttered manner.
+ * Highlights: Created an `ExpandedPersonCard` showing the full details of a friend.
+
+2. **New Feature: command aliases**
+ * What it does: Enables users to enter shorter command aliases instead of the usual command.
+ * Justification: Allows experienced users familiar with the commands to enter commands faster.
+
+3. **Enhancement to existing feature `addfriend`**:
+ * What it does: Allows users to add a friend into address book by typing in the relevant command into the GUI.
+ * Highlights:
+ * Added a new `Description` field for a `Friend`
+ * Made other fields in `Friend` optional and only `FriendName` compulsory, so that users who do not know full details of a friend
+ can still add the friend into `Amigos`.
+
+4. **Enhancement to existing feature `deletefriend`**:
+ * What it does: Allows users to delete a friend from `Amigos`.
+ * Highlights: Users can delete a friend either by entering the `FriendName` or a valid `Index`.
+ Users can delete an existing friend by `FriendName` even if the
+ friend is not currently being shown on the filtered list on the GUI.
+
+5. **Enhancement to existing feature `editfriend`**:
+ * What it does: Allows users to edit a friend from `Amigos`.
+ * Justification: Users can edit a friend either by entering the `FriendName` or a valid `Index`.
+ * Highlights: The prefix `cn/` is used to identify the current `FriendName`. `nn/` is used for the
+ edited `FriendName`.
+
+6. **Enhancement to existing feature `findfriend`**:
+ * What it does: Allows users to search for friends from `Amigos` using keywords.
+ * Highlights: Keywords are not limited to `FriendName`. `Tag` and `LogName` of a Person are also
+ searched. The user input is split according to the prefixes (`n/` for `FriendName`, `t/` for `Tag`,
+ `ttl` for `LogName`). Also, keywords do not have to be matched fully. As long as a substring is matched, then
+ the friend would be shown.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=dionegoh&breakdown=true)
+
+* **Project management**:
+ * Update site-wide settings [\#21](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/21)
+ * Maintained issue tracker for milestone v1.2
+
+* **Documentation**:
+ * User Guide:
+ * Add documentation for the features `addfriend`, `deletefriend`, and `editfriend`', `showfriend`,
+ `listfriends`, and `clear`.
+ * Add documentation for command aliases.
+
+ * Developer Guide:
+ * Add implementation and sequence diagram for `addfriend`
+ * Add implementation and activity diagram for `findfriend`
+ * Add implementation and object diagram for `showfriend`
+ * Add use cases for `addfriend`, `deletefriend`, `editfriend`.
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/limweiliang.md b/docs/team/limweiliang.md
new file mode 100644
index 00000000000..46185857e18
--- /dev/null
+++ b/docs/team/limweiliang.md
@@ -0,0 +1,38 @@
+---
+layout: page
+title: Lim Wei Liang's Project Portfolio Page
+---
+
+### Project: Amigos
+
+Amigos is a desktop application that helps tech-savvy university students manage their friendships by helping them track important details. It is optimized for use via a CLI and has a GUI created with JavaFX. It is written in Java and has 20 kLoC.
+
+Given below are my contributions to the project:
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=limweiliang&breakdown=true)
+
+* **New feature**: Creating and Deleting `Event` objects in Amigos.
+ * What it does: Users will be able to create and delete new `Event` objects in Amigos, which encapsulates an event name, date-time, description, and the names of friends tied to that event.
+ * Justification: Users will now be able to keep track of their social events, both past and present. This is an important friendship detail to keep track of.
+ * Highlights:
+ * Created numerous support classes in `Model` and `Storage` in order to represent and store the new `Event` class respectively
+ * Had to handle and maintain the newly created relationship between the `Person` and `Event` classes, since `Event` needs to keep track of the names of friends involved. This required deep consideration of the potential designs.
+ * Created support classes to help with testing, such as `EventUtil`.
+
+* **New feature**: Searching for specific `Event` objects in Amigos.
+ * What it does: Users will be able to search for specific events using some or all of the following criteria: the event name, names of friends attending, and date range.
+ * Justification: This is an important feature as it allows users to easily search for the specific events that they are interested in viewing.
+
+* **Enhancement to existing features**:
+ * Updated individual friend page to also display upcoming events for that specific friend.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the commands `addevent`, `deleteevent`, and `findevent`
+ * Developer Guide:
+ * Added implementation details and diagrams for `findevent` and representing the `Event`-`Person` dependency
+ * Updated `Model` section and diagram
+
+* **Contributions to team-based tasks**:
+ * Managed the milestone and release for v1.2b
+ * Overall I/C for `Model` component and documentation, reviewed PRs that modified those components (Examples: [\#74](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/74), [\#104](https://github.com/AY2122S2-CS2103-F09-2/tp/pull/104))
diff --git a/docs/team/tanyjnaaman.md b/docs/team/tanyjnaaman.md
new file mode 100644
index 00000000000..e29b0f80267
--- /dev/null
+++ b/docs/team/tanyjnaaman.md
@@ -0,0 +1,52 @@
+---
+layout: page
+title: Naaman Tan's Project Portfolio Page
+---
+
+### Project: Amigos
+
+Amigos is a desktop application to help tech-savvy university students
+manage their friendships by helping them to keep track of important
+details related to their friends. It is optimized for use via a Command Line interface while
+still having the benefits of a Graphical User Interface (GUI).
+
+### Summary of contributions
+1. **New Feature: Logs** Added the ability to create, edit and delete logs.
+ * What it does: Allows the user to add "notes" about friends in the application,
+ each with a title and a description.
+ * Justification: As an application to help improve user friendships, the addition of logs
+ feature allows users to better keep track of their relationship details.
+ * Highlights:
+ * To optimize for command-line convenience, the implementation focused on giving users
+ flexibility - selecting users by a displayed `INDEX` or by their `NAME`, using flags to
+ choose actions (e.g. `-a` flag to delete all logs at once), etc., and with this flexibility
+ came a careful analysis of requirements, design choices and rigorous testing.
+
+
+2. **New Feature: Insights** Added the ability to view summary statistics about friends in the application.
+ * What it does: Allows the user to get a better sense of the _quality_ of his relationships by looking at numbers.
+ * Justification: The feature aims to "complete" the focus on helping users maintain their relationships, by complementing logs
+ and event-planning features by computing some summary statistics about their relationships
+ (e.g. most recent event).
+ * Highlights:
+ * Choice was made to include insights as a separate tab that was _dynamically_ computed/updated to reduce coupling
+ with other components within the `Model` component.
+
+
+3. **Enhancements to existing features**
+ * Access to persons by both `INDEX` and `NAME` for convenience and different user skill levels. (Pull requests [\#107](), [\#110](), [\#134]())
+
+
+4. **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s2.github.io/tp-dashboard/?search=tanyjnaaman&breakdown=true)
+
+
+5. **Documentation**:
+ * User Guide:
+ * Added documentation for the features `addlog`, `editlog`, `deletelog` and `showinsights`.
+ * Developer Guide:
+ * Added documentation for use cases of the features `addlog`, `editlog`, `deletelog` and `showinsights`
+ * Added documentation for design of features `addlog`, `editlog`, `deletelog` and `showinsights`
+
+6. **Team-based tasks**
+ * Set up GitHub team org/repo and maintained issue tracker for milestone v1.1
+
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..5fa88e4e081 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -75,22 +75,21 @@ public void init() throws Exception {
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ ReadOnlyAddressBook initialAddressBookData;
try {
addressBookOptional = storage.readAddressBook();
if (!addressBookOptional.isPresent()) {
logger.info("Data file not found. Will be starting with a sample AddressBook");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialAddressBookData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
} catch (DataConversionException e) {
logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressBookData = new AddressBook();
} catch (IOException e) {
logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ initialAddressBookData = new AddressBook();
}
-
- return new ModelManager(initialData, userPrefs);
+ return new ModelManager(initialAddressBookData, userPrefs);
}
private void initLogging(Config config) {
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..b1c56f38480 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -8,6 +8,11 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid";
+ public static final String MESSAGE_INDEX_IS_NOT_NON_ZERO_UNSIGNED_INTEGER = "Index is not a non-zero unsigned integer.";
+ public static final String MESSAGE_INVALID_PERSON_NAME = "The name provided is not a valid name!";
+ public static final String MESSAGE_PERSON_DOES_NOT_EXIST = "This friend does not exist in Amigos";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_EVENTS_LISTED_OVERVIEW = "%1$d events listed!";
+ public static final String MESSAGE_INVALID_EVENT_FRIENDS = "An event contains friend names that do not exist!";
}
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java
index 19536439c09..ef1ae0b8580 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/seedu/address/commons/core/index/Index.java
@@ -51,4 +51,9 @@ public boolean equals(Object other) {
|| (other instanceof Index // instanceof handles nulls
&& zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check
}
+
+ @Override
+ public String toString() {
+ return "" + this.getOneBased();
+ }
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..9fde6f32e9e 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,7 +8,9 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
/**
* API of the Logic component
@@ -33,6 +35,11 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered list of persons */
+ ObservableList getFilteredEventList();
+
+ ObservableList getInsightsList();
+
/**
* Returns the user prefs' address book file path.
*/
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..5f9b635f546 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -14,7 +14,9 @@
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
import seedu.address.storage.Storage;
/**
@@ -64,6 +66,18 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getInsightsList() {
+ return model.getInsightsList();
+ }
+
+
+
+ @Override
+ public ObservableList getFilteredEventList() {
+ return model.getFilteredEventList();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..a6bd338e07c 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
@@ -16,25 +17,29 @@
*/
public class AddCommand extends Command {
- public static final String COMMAND_WORD = "add";
+ public static final String COMMAND_WORD = "addfriend";
+ public static final String COMMAND_ALIAS = "af";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Adds a friend to the address book. "
+ "Parameters: "
+ PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_EMAIL + "EMAIL] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]"
+ "[" + 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_DESCRIPTION + "Loves to eat sushi"
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_SUCCESS = "New friend added: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This friend already exists in the address book";
private final Person toAdd;
diff --git a/src/main/java/seedu/address/logic/commands/AddEventCommand.java b/src/main/java/seedu/address/logic/commands/AddEventCommand.java
new file mode 100644
index 00000000000..c1a8cd6db06
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddEventCommand.java
@@ -0,0 +1,77 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EVENT_FRIENDS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FRIEND_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+/**
+ * Adds an event to Amigos.
+ */
+public class AddEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "addevent";
+ public static final String COMMAND_ALIAS = "ae";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS + ": Adds an event to Amigos. "
+ + "Parameters: "
+ + PREFIX_NAME + "EVENT_NAME "
+ + PREFIX_DATETIME + "DATE_TIME "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ + "[" + PREFIX_FRIEND_NAME + "FRIEND_NAME]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "John's Birthday "
+ + PREFIX_DATETIME + "15-08-2021 1700 "
+ + PREFIX_DESCRIPTION + "Remember to get a present! "
+ + PREFIX_FRIEND_NAME + "John Low "
+ + PREFIX_FRIEND_NAME + "Amy Lim";
+
+ public static final String MESSAGE_SUCCESS = "New event added: %1$s";
+ public static final String MESSAGE_PAST_EVENT_WARNING =
+ "Warning: You have added a past event. Use 'listevents -a' if it is not visible.";
+ public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists!";
+
+ private final Event toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Event}
+ */
+ public AddEventCommand(Event event) {
+ requireNonNull(event);
+ toAdd = event;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasEvent(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+ if (!model.areEventFriendsValid(toAdd)) {
+ throw new CommandException(MESSAGE_INVALID_EVENT_FRIENDS);
+ }
+
+ model.addEvent(toAdd);
+
+ String output = String.format(MESSAGE_SUCCESS, toAdd);
+ if (toAdd.isBeforeNow()) {
+ output += "\n" + MESSAGE_PAST_EVENT_WARNING;
+ }
+
+ return new CommandResult(output, false, false, true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddEventCommand // instanceof handles nulls
+ && toAdd.equals(((AddEventCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddLogCommand.java b/src/main/java/seedu/address/logic/commands/AddLogCommand.java
new file mode 100644
index 00000000000..dc0d891b805
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddLogCommand.java
@@ -0,0 +1,242 @@
+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_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.common.Description;
+import seedu.address.model.common.Name;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Log;
+import seedu.address.model.person.LogName;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * Adds a log to a person in the address book.
+ */
+public class AddLogCommand extends ByIndexByNameCommand {
+
+ public static final String COMMAND_WORD = "addlog";
+ public static final String COMMAND_ALIAS = "al";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Adds a log to an existing friend in Amigos. "
+ + "Parameters: "
+ + "INDEX ? " + PREFIX_NAME + "NAME "
+ + PREFIX_TITLE + "TITLE"
+ + " [" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 "
+ + PREFIX_TITLE + "Likes apples";
+
+ public static final String MESSAGE_ADD_LOG_SUCCESS = "New log added!";
+ public static final String MESSAGE_DUPLICATE_LOG = "This log already exists for this friend.";
+
+ private final Index index;
+ private final Name nameToAddLog;
+ private final AddLogDescriptor addLogDescriptor;
+ private final boolean byName;
+
+ /**
+ * Creates an AddLogCommand to add the specified {@code Log} to the
+ * specified {@code Person}.
+ */
+ public AddLogCommand(Index index, AddLogDescriptor addLogDescriptor) {
+ requireAllNonNull(index, addLogDescriptor);
+ this.index = index;
+ this.nameToAddLog = null;
+ this.addLogDescriptor = addLogDescriptor;
+ this.byName = false;
+ }
+
+ /**
+ * Creates an AddLogCommand to add the specified {@code Log} to the specified
+ * {@code Person}.
+ */
+ public AddLogCommand(FriendName name, AddLogDescriptor addLogDescriptor) {
+ requireAllNonNull(name, addLogDescriptor);
+ this.nameToAddLog = name;
+ this.index = null;
+ this.addLogDescriptor = addLogDescriptor;
+ this.byName = true;
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Person personToEdit;
+
+ if (this.byName) {
+ personToEdit = this.getPersonByName(model, this.nameToAddLog);
+ } else {
+ personToEdit = this.getPersonByFilteredIndex(model, this.index);
+ }
+
+ // create person with added logs
+ Person addedLogPerson = createAddedLogPerson(personToEdit, this.addLogDescriptor);
+
+ // add to address book
+ model.setPerson(personToEdit, addedLogPerson);
+ return new CommandResult(MESSAGE_ADD_LOG_SUCCESS);
+ }
+
+ /**
+ * Creates a {@code Person} with the details of {@code personToEdit}, with logs modified by
+ * {@code addLogDescriptor}.
+ *
+ * @throws CommandException if {@code addLogDescriptor} results in an invalid {@code Log}
+ * being created.
+ */
+ private static Person createAddedLogPerson(Person personToEdit, AddLogDescriptor addLogDescriptor)
+ throws CommandException {
+ requireAllNonNull(personToEdit, addLogDescriptor);
+ FriendName name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Description description = personToEdit.getDescription();
+ Set tags = personToEdit.getTags();
+ List updatedLogs = addLogDescriptor.getLogsAfterAdd(personToEdit); // main logic encompassed here
+ return new Person(name, phone, email, address, description, tags, updatedLogs);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddLogCommand)) {
+ return false;
+ }
+
+ // cast
+ AddLogCommand a = (AddLogCommand) other;
+
+ // compare descriptors
+ if (!this.addLogDescriptor.equals(a.addLogDescriptor)) {
+ return false;
+ }
+
+ // compare name or index
+ if ((this.byName) && (a.byName)) {
+ assert ((this.index == null) && (a.index == null));
+ return this.nameToAddLog.equals(a.nameToAddLog);
+
+ } else if ((!this.byName) && (!a.byName)) {
+ assert ((this.nameToAddLog == null) && (a.nameToAddLog == null));
+ return this.index.equals(a.index);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Index: " + this.index.toString() + "\nContent:\n" + this.addLogDescriptor.toString();
+ }
+
+ /**
+ * Stores the details of the edited log to add a person's logs with, as well as the person's
+ * original details.
+ */
+ public static class AddLogDescriptor {
+
+ private LogName newTitle;
+ private Description newDescription;
+
+ /**
+ * Constructs a new {@code AddLogDescriptor} object.
+ */
+ public AddLogDescriptor() {
+ this.newTitle = null;
+ this.newDescription = null;
+ }
+
+ public void setNewTitle(String newTitle) {
+ this.newTitle = new LogName(newTitle);
+ }
+
+ public void setNewTitle(LogName newTitle) {
+ this.newTitle = newTitle;
+ }
+
+ public void setNewDescription(String newDescription) {
+ this.newDescription = new Description(newDescription);
+ }
+
+ public void setNewDescription(Description newDescription) {
+ this.newDescription = newDescription;
+ }
+ /**
+ * Returns true if title has been edited.
+ */
+ public boolean isTitleEdited() {
+ return this.newTitle != null;
+ }
+
+ /**
+ * Returns a list of {@code Log} objects that include the {@code Person}'s original logs
+ * as well as the new logs.
+ */
+ public List getLogsAfterAdd(Person personToEdit) throws CommandException {
+
+ // sanity checks
+ assert (this.newTitle != null);
+
+ Log toAdd = new Log(this.newTitle, this.newDescription); // create log to be added
+ if (personToEdit.containsLog(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_LOG); // ensure not a duplicate log being inserted
+ }
+
+ List newLogs = new ArrayList<>(personToEdit.getLogs());
+ newLogs.add(toAdd); // add log
+
+ return newLogs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof
+ if (!(other instanceof AddLogDescriptor)) {
+ return false;
+ }
+
+ // cast and check by wrapping into log object
+ AddLogDescriptor a = (AddLogDescriptor) other;
+ Log thisLog = new Log(this.newTitle, this.newDescription);
+ Log otherLog = new Log(a.newTitle, a.newDescription);
+ return thisLog.equals(otherLog);
+ }
+
+ @Override
+ public String toString() {
+ return "Title: " + this.newTitle + "\nDescription: \n" + this.newDescription;
+ }
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/ByIndexByNameCommand.java b/src/main/java/seedu/address/logic/commands/ByIndexByNameCommand.java
new file mode 100644
index 00000000000..68cc60bd251
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ByIndexByNameCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+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.common.Name;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a command with hidden internal logic and the ability to be executed,
+ * specifically with the requirement that it supports INDEX and NAME based access to persons
+ * in Amigos.
+ */
+public abstract class ByIndexByNameCommand extends Command {
+
+ public static final String MESSAGE_INVALID_INDEX = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
+ public static final String MESSAGE_PERSON_NOT_FOUND = Messages.MESSAGE_INVALID_PERSON_NAME;
+
+ /**
+ * Gets a person by name from the model. Assumes that for a given name, there is
+ * exactly one person with the name.
+ *
+ * @throws CommandException if name provided does not exist in the model.
+ */
+ public static Person getPersonByName(Model model, Name name) throws CommandException {
+ requireAllNonNull(model, name);
+ // find person with same name
+ List personsToEdit = model.getAddressBook()
+ .getPersonList().stream()
+ .filter(p -> p.hasName(name))
+ .collect(Collectors.toList());
+
+ // if person not found, throw an error
+ if (personsToEdit.size() < 1) {
+ throw new CommandException(MESSAGE_PERSON_NOT_FOUND);
+ }
+
+ // assumes exactly one person with a given name, assertion to check
+ assert (personsToEdit.size() == 1);
+
+ return personsToEdit.get(0);
+ }
+
+ /**
+ * Gets a person from the model by his index in the filtered list.
+ *
+ * @throws CommandException if index provided is out of bounds.
+ */
+ public static Person getPersonByFilteredIndex(Model model, Index index) throws CommandException {
+ requireAllNonNull(model, index);
+
+ // get list of persons from model
+ List lastShownList = model.getFilteredPersonList();
+
+ // ensure that index is valid
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_INDEX);
+ }
+
+ return lastShownList.get(index.getZeroBased());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..de2eda84307 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -11,8 +11,7 @@
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
+ public static final String MESSAGE_SUCCESS = "All data in Amigos have been cleared!";
@Override
public CommandResult execute(Model model) {
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..9c2f56ab22e 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -17,6 +17,43 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ /** The application should switch to event */
+ private final boolean event;
+
+ /** PersonListPanel will switch to expanded view showing all details of a friend */
+ private final boolean showDetails;
+
+ /** The application should switch to insights */
+ private final boolean showInsights;
+
+
+
+ /**
+ * Constructs a {@code CommandResult} with the specified fields.
+ */
+ public CommandResult(String feedbackToUser, boolean showHelp,
+ boolean exit, boolean event,
+ boolean showDetails, boolean showInsights) {
+ // todo use assertions as sanity checks!
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = showHelp;
+ this.exit = exit;
+ this.event = event;
+ this.showDetails = showDetails;
+ this.showInsights = showInsights;
+ }
+ /**
+ * Constructs a {@code CommandResult} with the specified fields.
+ */
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean event) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = showHelp;
+ this.exit = exit;
+ this.event = event;
+ this.showDetails = false;
+ this.showInsights = false;
+ }
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
@@ -24,14 +61,18 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.event = false;
+ this.showDetails = false;
+ this.showInsights = false;
}
+
/**
* Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false, false, false);
}
public String getFeedbackToUser() {
@@ -46,6 +87,18 @@ public boolean isExit() {
return exit;
}
+ public boolean isEvent() {
+ return event;
+ }
+
+ public boolean isShowFriendCommand() {
+ return showDetails;
+ }
+
+ public boolean isShowInsights() {
+ return this.showInsights;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,12 +113,14 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && exit == otherCommandResult.exit
+ && event == otherCommandResult.event
+ && showDetails == otherCommandResult.showDetails;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, exit, event, showDetails);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..f1b40159db5 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,53 +1,92 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.person.FriendName;
import seedu.address.model.person.Person;
/**
* Deletes a person identified using it's displayed index from the address book.
*/
-public class DeleteCommand extends Command {
+public class DeleteCommand extends ByIndexByNameCommand {
+
+ public static final String COMMAND_WORD = "deletefriend";
+ public static final String COMMAND_ALIAS = "df";
- public static final String COMMAND_WORD = "delete";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Deletes the friend identified by index or name.\n"
+ + "Parameters: "
+ + " INDEX ? "
+ + PREFIX_NAME + "NAME \n"
+ + "Example 1: " + COMMAND_WORD + " 1" + "\n"
+ + "Example 2: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe\n";
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted friend: %1$s";
+ private final FriendName nameOfPersonToDelete;
private final Index targetIndex;
+ private final boolean isDeletionByIndex;
+
+ /**
+ * Constructs a DeleteCommand for deletion by name
+ *
+ * @param nameOfPersonToDelete The name of the person to be deleted
+ */
+ public DeleteCommand(FriendName nameOfPersonToDelete) {
+ this.nameOfPersonToDelete = nameOfPersonToDelete;
+ this.targetIndex = null;
+ this.isDeletionByIndex = false;
+ }
+ /**
+ * Constructs a DeleteCommand for deletion by index
+ *
+ * @param targetIndex The index of the person to be deleted on the filtered list on GUI
+ */
public DeleteCommand(Index targetIndex) {
+ this.nameOfPersonToDelete = null;
this.targetIndex = targetIndex;
+ this.isDeletionByIndex = true;
}
@Override
public CommandResult execute(Model model) throws CommandException {
+
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ Person personToDelete;
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
+ if (isDeletionByIndex) { //deletion by index
+ personToDelete = this.getPersonByFilteredIndex(model, targetIndex);
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+ } else { //deletion by name
+ personToDelete = this.getPersonByName(model, nameOfPersonToDelete);
+ }
+ assert(personToDelete != null);
model.deletePerson(personToDelete);
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
}
@Override
public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
+ if (other == this) {
+ return true;
+ } else if (other instanceof DeleteCommand) {
+ DeleteCommand otherDeleteCommand = (DeleteCommand) other;
+ if (otherDeleteCommand.isDeletionByIndex && this.isDeletionByIndex) {
+ //assertion to ensure that if it is deletion by index, then targetIndex will not be null
+ assert(otherDeleteCommand.targetIndex != null && this.targetIndex != null);
+ return otherDeleteCommand.targetIndex.equals(this.targetIndex);
+ } else if (!otherDeleteCommand.isDeletionByIndex && !this.isDeletionByIndex) {
+ //assertion to ensure that if it is deletion by name, then name will not be null
+ assert(otherDeleteCommand.nameOfPersonToDelete != null && this.nameOfPersonToDelete != null);
+ return otherDeleteCommand.nameOfPersonToDelete.equals(this.nameOfPersonToDelete);
+ }
+ }
+ return false;
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java b/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java
new file mode 100644
index 00000000000..e59cde35cd5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteEventCommand.java
@@ -0,0 +1,54 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+/**
+ * Deletes an event identified using it's displayed index from the address book.
+ */
+public class DeleteEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteevent";
+ public static final String COMMAND_ALIAS = "de";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Deletes the event identified by the index number used in the displayed event list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Deleted Event: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteEventCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredEventList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ Event eventToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteEvent(eventToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_EVENT_SUCCESS, eventToDelete), false, false, true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteEventCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteEventCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteLogCommand.java b/src/main/java/seedu/address/logic/commands/DeleteLogCommand.java
new file mode 100644
index 00000000000..d970c9b2705
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteLogCommand.java
@@ -0,0 +1,393 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.FLAG_ALL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LOG_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.DeleteLogCommandParser;
+import seedu.address.model.Model;
+import seedu.address.model.common.Description;
+import seedu.address.model.common.Name;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Log;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Deletes a log from a person in the address book.
+ */
+public class DeleteLogCommand extends ByIndexByNameCommand {
+
+ public static final String COMMAND_WORD = "deletelog";
+ public static final String COMMAND_ALIAS = "dl";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Deletes a log from an existing friend in Amigos. "
+ + "Parameters: "
+ + "[INDEX ? " + PREFIX_NAME + "NAME] ["
+ + PREFIX_LOG_INDEX + "LOG_INDEX]"
+ + " [" + FLAG_ALL + "]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 "
+ + PREFIX_LOG_INDEX + "2";
+
+ public static final String MESSAGE_DELETE_LOG_SUCCESS = "Log deleted.";
+ public static final String MESSAGE_LOG_NOT_FOUND = "The specified log does not exist!";
+
+ // data fields
+ private final DeleteLogDescriptor descriptor;
+
+ /**
+ * Creates a {@code DeleteLogCommand} object.
+ */
+ public DeleteLogCommand(DeleteLogDescriptor descriptor) {
+ requireNonNull(descriptor);
+ this.descriptor = descriptor;
+ }
+
+ /**
+ * Creates a {@code DeleteLogCommand} object.
+ */
+ public DeleteLogCommand(boolean isForOnePerson, boolean isForDeletingAllLogs,
+ Index personIndex, Index logIndex) {
+ requireAllNonNull(isForOnePerson, isForDeletingAllLogs);
+ this.descriptor = new DeleteLogDescriptor(isForOnePerson,
+ isForDeletingAllLogs, personIndex, null, logIndex, false);
+ }
+
+ /**
+ * Creates a {@code DeleteLogCommand} object.
+ */
+ public DeleteLogCommand(boolean isForOnePerson, boolean isForDeletingAllLogs,
+ FriendName personName, Index logIndex) {
+ requireAllNonNull(isForOnePerson, isForDeletingAllLogs);
+ this.descriptor = new DeleteLogDescriptor(isForOnePerson,
+ isForDeletingAllLogs, null, personName, logIndex, true);
+ }
+
+ /**
+ * Creates a {@code DeleteLogCommand} object, specifically one for
+ * deleting all logs of all persons.
+ */
+ public DeleteLogCommand(boolean isForDeletingAllLogs) {
+ assert (isForDeletingAllLogs);
+ this.descriptor = new DeleteLogDescriptor(false,
+ true, null, null, null, false);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ // apply delete to model
+ return this.descriptor.applyDelete(model);
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteLogCommand)) {
+ return false;
+ }
+
+ // cast
+ DeleteLogCommand d = (DeleteLogCommand) other;
+ return this.descriptor.equals(d.descriptor);
+ }
+
+ @Override
+ public String toString() {
+ return this.descriptor.toString();
+ }
+
+ /**
+ * Stores the details of the nature of deletion, whether it is to
+ * delete a specific log, all logs of a person or all logs of all persons.
+ */
+ public static class DeleteLogDescriptor {
+
+ private final boolean isForOnePerson;
+ private final boolean isForDeletingAllLogs;
+ private final Index personIndex;
+ private final Name nameToDeleteLog;
+ private final Index logIndex;
+ private final boolean byName;
+
+ /**
+ * Creates a {@code} DeleteLogDescriptor} object that wraps the details of deletion.
+ */
+ private DeleteLogDescriptor(boolean isForOnePerson, boolean isForDeletingAllLogs,
+ Index personIndex, FriendName name, Index logIndex, boolean byName) {
+ this.isForOnePerson = isForOnePerson;
+ this.isForDeletingAllLogs = isForDeletingAllLogs;
+ this.personIndex = personIndex;
+ this.nameToDeleteLog = isNull(name) ? null : name;
+ this.logIndex = logIndex;
+ this.byName = byName;
+
+ // sanity checks
+ assert (!(this.personIndex != null && this.nameToDeleteLog != null)); // cannot be overdefined
+ if (!this.byName) {
+ assert (((logIndex == null
+ || (!isForDeletingAllLogs && isForOnePerson && personIndex != null)))
+ && ((!isForOnePerson && personIndex == null)
+ || isForOnePerson && personIndex != null));
+
+ } else {
+ assert (((logIndex == null
+ || (!isForDeletingAllLogs && isForOnePerson && nameToDeleteLog != null)))
+ && ((!isForOnePerson && nameToDeleteLog == null)
+ || isForOnePerson && nameToDeleteLog != null));
+ }
+ }
+
+ /**
+ * Applies the deletion logic to the model and returns as
+ * {@code CommandResult} object.
+ *
+ * @throws CommandException if invalid data state encountered.
+ */
+ public CommandResult applyDelete(Model model) throws CommandException {
+
+ CommandResult result;
+
+ if (isForOnePerson && !isForDeletingAllLogs) {
+ // case 1: delete specific log of specific person
+ result = this.deleteSpecificPersonLog(model);
+
+ } else if (isForOnePerson && isForDeletingAllLogs) {
+ // case 2: delete all logs of specific person
+ result = this.deleteAllLogsOfPerson(model);
+
+ } else if (!isForOnePerson && isForDeletingAllLogs) {
+ // case 3: delete all logs all persons
+ result = this.deleteAllLogs(model);
+
+ } else {
+ throw new CommandException(DeleteLogCommandParser.MESSAGE_INVALID_FORMAT);
+ }
+ return result;
+ }
+
+ /**
+ * Deletes all {@code Logs} objects of all persons in the address book.
+ **/
+ public CommandResult deleteAllLogs(Model model) {
+
+ // sanity check
+ assert (this.isForDeletingAllLogs && !this.isForOnePerson
+ && this.personIndex == null && this.logIndex == null);
+
+ // get all persons and delete all
+ List allPersonsList = model.getAddressBook().getPersonList();
+ for (Person person : allPersonsList) {
+ Person editedPerson = copyPersonButWithEmptyLogs(person);
+ model.setPerson(person, editedPerson);
+ }
+ return new CommandResult(MESSAGE_DELETE_LOG_SUCCESS);
+ }
+
+ private Person getPersonToEdit(Model model) throws CommandException {
+
+ // sanity checks
+ assert (this.personIndex != null || this.nameToDeleteLog != null);
+
+ // get person to edit
+ Person personToEdit = null;
+
+ if (this.byName) {
+ // sanity check
+ requireNonNull(this.nameToDeleteLog);
+ personToEdit = DeleteLogCommand.getPersonByName(model, this.nameToDeleteLog);
+
+ } else {
+ // sanity check
+ requireNonNull(this.personIndex);
+
+ personToEdit = DeleteLogCommand.getPersonByFilteredIndex(model, this.personIndex);
+ }
+ // another sanity check
+ requireNonNull(personToEdit);
+ return personToEdit;
+ }
+
+ /**
+ * Carries out deletion logic for deletion of all {@code Log} objects of a specified person.
+ *
+ * @throws CommandException if person specified is not in address book.
+ */
+ public CommandResult deleteAllLogsOfPerson(Model model) throws CommandException {
+
+ // sanity checks
+ assert (this.isForDeletingAllLogs && this.isForOnePerson && this.logIndex == null);
+ assert (this.personIndex != null || this.nameToDeleteLog != null);
+
+ // ===== GET PERSON =====
+
+ Person personToEdit = this.getPersonToEdit(model);
+ Person deletedLogsPerson = copyPersonButWithEmptyLogs(personToEdit);
+
+ // add to address book
+ model.setPerson(personToEdit, deletedLogsPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+
+ return new CommandResult(MESSAGE_DELETE_LOG_SUCCESS);
+
+ }
+
+ /**
+ * Returns a {@code Person} object that is identical except without any logs.
+ * A helper method.
+ */
+ public static Person copyPersonButWithEmptyLogs(Person personToEdit) {
+ requireAllNonNull(personToEdit);
+ FriendName name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Description description = personToEdit.getDescription();
+ Set tags = personToEdit.getTags();
+ List emptyLogs = new ArrayList<>(); // main logic encompassed here
+ return new Person(name, phone, email, address, description, tags, emptyLogs);
+ }
+
+ /**
+ * Deletes a specific {@code Log} of a specified person.
+ *
+ * @throws CommandException if {@code Person} or {@code Log} is not found.
+ */
+ public CommandResult deleteSpecificPersonLog(Model model) throws CommandException {
+
+ // sanity checks
+ requireNonNull(model);
+ assert (this.isForOnePerson
+ && !this.isForDeletingAllLogs
+ && this.logIndex != null);
+
+ assert (!((this.personIndex == null && this.nameToDeleteLog == null)
+ || (this.personIndex != null && this.nameToDeleteLog != null)));
+
+ // ===== GET PERSON =====
+ Person personToEdit = this.getPersonToEdit(model);
+
+ Person deletedLogPerson = createdDeletedLogPerson(personToEdit, this.logIndex);
+
+ // add to address book
+ model.setPerson(personToEdit, deletedLogPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(MESSAGE_DELETE_LOG_SUCCESS);
+ }
+
+ /**
+ * Creates a {@code Person} with the details of {@code personToEdit}, with log equal to
+ * {@code toDelete} removed.
+ *
+ * @throws CommandException if {@code toDelete} does not exist in the logs of {@code personToEdit}
+ */
+ public static Person createdDeletedLogPerson(Person personToEdit, Index toDelete) throws CommandException {
+ requireAllNonNull(personToEdit, toDelete);
+ FriendName name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Set tags = personToEdit.getTags();
+ Description description = personToEdit.getDescription();
+ List updatedLogs = getLogsAfterDelete(personToEdit, toDelete); // main logic encompassed here
+ return new Person(name, phone, email, address, description, tags, updatedLogs);
+ }
+
+ /**
+ * Returns a list of {@code Log} objects that include the {@code Person}'s original logs
+ * less the specified {@code Log} to be deleted.
+ */
+ public static List getLogsAfterDelete(Person personToEdit, Index toDeleteIndex) throws CommandException {
+ requireAllNonNull(personToEdit, toDeleteIndex);
+
+ // check that safe to remove
+ List logs = new ArrayList(personToEdit.getLogs());
+ if (toDeleteIndex.getZeroBased() >= logs.size()) {
+ throw new CommandException(MESSAGE_LOG_NOT_FOUND);
+ }
+
+ // remove by index
+ logs.remove(toDeleteIndex.getZeroBased());
+
+ return logs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteLogDescriptor)) {
+ return false;
+ }
+
+ // cast
+ DeleteLogDescriptor d = (DeleteLogDescriptor) other;
+
+ // person index must be same
+ boolean isSamePerson = bothNullOrEqual(this.personIndex, d.personIndex);
+
+ // log index must be same
+ boolean isSameLog = bothNullOrEqual(this.logIndex, d.logIndex);
+
+ // person to delete must be same
+ boolean isSamePersonByName = bothNullOrEqual(this.nameToDeleteLog, d.nameToDeleteLog);
+
+ // remaining must be same
+ return (isSameLog && isSamePerson && isSamePersonByName
+ && this.isForOnePerson == d.isForOnePerson
+ && this.isForDeletingAllLogs == d.isForDeletingAllLogs);
+ }
+
+ private static boolean bothNullOrEqual(Object propertyOfOne, Object propertyOfOther) {
+ if ((propertyOfOne == null && propertyOfOther != null)
+ || propertyOfOne != null && propertyOfOther == null) {
+ return false;
+ }
+ return (propertyOfOne == null && propertyOfOther == null
+ || propertyOfOne.equals(propertyOfOther));
+
+ }
+
+ @Override
+ public String toString() {
+ return "For one person: " + this.isForOnePerson
+ + "\nFor all logs: " + this.isForDeletingAllLogs
+ + "\nBy Name: " + this.byName
+ + "\nPerson with name: " + this.nameToDeleteLog
+ + "\nPerson index: " + this.personIndex
+ + "\nLog Index: " + this.logIndex;
+ }
+ }
+
+}
+
+
+
+
+
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 7e36114902f..945bfcca004 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,9 +1,12 @@
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_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CURRENT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_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;
@@ -14,14 +17,15 @@
import java.util.Optional;
import java.util.Set;
-import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.common.Description;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Log;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -29,58 +33,83 @@
/**
* Edits the details of an existing person in the address book.
*/
-public class EditCommand extends Command {
+public class EditCommand extends ByIndexByNameCommand {
- public static final String COMMAND_WORD = "edit";
+ public static final String COMMAND_WORD = "editfriend";
+ public static final String COMMAND_ALIAS = "ef";
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
+ + "by the name or index number used in the displayed person list. "
+ "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
+ + "Parameters: INDEX ? "
+ + PREFIX_CURRENT_NAME + " CURRENT_NAME"
+ + "[" + PREFIX_NEW_NAME + "NEW_NAME] "
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
+
public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
- private final Index index;
+ private final FriendName nameOfPersonToEdit;
+ private final Index targetIndex;
private final EditPersonDescriptor editPersonDescriptor;
+ private final boolean isEditByIndex;
/**
* @param index of the person in the filtered person list to edit
* @param editPersonDescriptor details to edit the person with
*/
public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
+ requireAllNonNull(index, editPersonDescriptor);
+
+ this.nameOfPersonToEdit = null;
+ this.targetIndex = index;
+ this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
+ this.isEditByIndex = true;
+ }
+
+ /**
+ * @param name of the friend in the person list to edit
+ * @param editPersonDescriptor details to edit the person with
+ */
+ public EditCommand(FriendName name, EditPersonDescriptor editPersonDescriptor) {
+ requireAllNonNull(name, editPersonDescriptor);
- this.index = index;
+ this.nameOfPersonToEdit = name;
+ this.targetIndex = null;
this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
+ this.isEditByIndex = false;
}
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
+ Person personToEdit;
+ Person editedPerson;
+
+ if (isEditByIndex) { //edit by index
+ personToEdit = this.getPersonByFilteredIndex(model, targetIndex);
+ editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+ } else { //edit by name
+ personToEdit = this.getPersonByName(model, nameOfPersonToEdit);
+ editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+ }
if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+
model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
@@ -93,13 +122,15 @@ public CommandResult execute(Model model) throws CommandException {
private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
assert personToEdit != null;
- Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
+ FriendName updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Description updatedDescription = editPersonDescriptor.getDescription().orElse(personToEdit.getDescription());
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ List updatedLogs = editPersonDescriptor.getLogs().orElse(personToEdit.getLogs());
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedDescription, updatedTags,
+ updatedLogs);
}
@Override
@@ -107,17 +138,21 @@ public boolean equals(Object other) {
// short circuit if same object
if (other == this) {
return true;
+ } else if (other instanceof EditCommand) { //state check
+ EditCommand otherEditCommand = (EditCommand) other;
+ if (otherEditCommand.isEditByIndex && this.isEditByIndex) {
+ //assertion to ensure that if it is edit by index, then targetIndex will not be null
+ assert (otherEditCommand.targetIndex != null && this.targetIndex != null);
+ return otherEditCommand.targetIndex.equals(this.targetIndex)
+ && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor);
+ } else if (!otherEditCommand.isEditByIndex && !this.isEditByIndex) {
+ //assertion to ensure that if it is deletion by name, then name will not be null
+ assert (otherEditCommand.nameOfPersonToEdit != null && this.nameOfPersonToEdit != null);
+ return otherEditCommand.nameOfPersonToEdit.equals(this.nameOfPersonToEdit)
+ && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor);
+ }
}
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
+ return false;
}
/**
@@ -125,13 +160,16 @@ public boolean equals(Object other) {
* corresponding field value of the person.
*/
public static class EditPersonDescriptor {
- private Name name;
+ private FriendName name;
private Phone phone;
private Email email;
private Address address;
+ private Description description;
private Set tags;
+ private List logs;
- public EditPersonDescriptor() {}
+ public EditPersonDescriptor() {
+ }
/**
* Copy constructor.
@@ -142,21 +180,23 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setPhone(toCopy.phone);
setEmail(toCopy.email);
setAddress(toCopy.address);
+ setDescription(toCopy.description);
setTags(toCopy.tags);
+ setLogs(toCopy.logs);
}
/**
* 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, description, tags);
}
- public void setName(Name name) {
+ public void setName(FriendName name) {
this.name = name;
}
- public Optional getName() {
+ public Optional getName() {
return Optional.ofNullable(name);
}
@@ -184,6 +224,14 @@ public Optional getAddress() {
return Optional.ofNullable(address);
}
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
/**
* Sets {@code tags} to this object's {@code tags}.
* A defensive copy of {@code tags} is used internally.
@@ -201,6 +249,23 @@ public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ /**
+ * Sets {@code logs} to this object's {@code logs}.
+ */
+ public void setLogs(List logs) {
+ this.logs = logs;
+ }
+
+ /**
+ * Returns an unmodifiable list of logs, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ *
+ * @return {@code Optional#empty()} if {@code logs} is null.
+ */
+ public Optional> getLogs() {
+ return (logs != null) ? Optional.of(Collections.unmodifiableList(logs)) : Optional.empty();
+ }
+
@Override
public boolean equals(Object other) {
// short circuit if same object
@@ -220,6 +285,7 @@ public boolean equals(Object other) {
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
&& getAddress().equals(e.getAddress())
+ && getDescription().equals(e.getDescription())
&& getTags().equals(e.getTags());
}
}
diff --git a/src/main/java/seedu/address/logic/commands/EditEventCommand.java b/src/main/java/seedu/address/logic/commands/EditEventCommand.java
new file mode 100644
index 00000000000..fb9af3a328d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditEventCommand.java
@@ -0,0 +1,263 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EVENT_FRIENDS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADD_FRIENDNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMOVE_FRIENDNAME;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.common.Description;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventName;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Edits the details of an existing event in the address book.
+ */
+public class EditEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "editevent";
+ public static final String COMMAND_ALIAS = "ee";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Edits the details of the person identified "
+ + "by the index number used in the displayed person list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_NAME + "NEW_EVENT_NAME] "
+ + "[" + PREFIX_DATETIME + "NEW_DATE_TIME] "
+ + "[" + PREFIX_DESCRIPTION + "NEW_DESCRIPTION] "
+ + "[" + PREFIX_ADD_FRIENDNAME + "ADD_FRIEND_NAME]… "
+ + "[" + PREFIX_REMOVE_FRIENDNAME + "REMOVE_FRIEND_NAME]…\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_NAME + "2nd Birthday "
+ + PREFIX_DATETIME + "16-08-2021 1600";
+
+ public static final String MESSAGE_EDIT_EVENT_SUCCESS = "Edited Event: %1$s";
+ public static final String MESSAGE_PAST_EVENT_WARNING =
+ "Warning: The edited event is in the past. Use 'listevents -a' if it is not visible.";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists in the address book.";
+ public static final String MESSAGE_INVALID_FRIENDS_TO_REMOVE =
+ "One of the friends specified for removal does not exist.";
+
+ public final Index index;
+ public final EditEventDescriptor editEventDescriptor;
+
+ /**
+ * @param index of the event in the event list to edit
+ * @param editEventDescriptor details to edit the event with
+ */
+ public EditEventCommand(Index index, EditEventDescriptor editEventDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editEventDescriptor);
+
+ this.index = index;
+ this.editEventDescriptor = new EditEventDescriptor(editEventDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredEventList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX);
+ }
+
+ Event eventToEdit = lastShownList.get(index.getZeroBased());
+ Event editedEvent = createEditedEvent(eventToEdit, editEventDescriptor);
+
+ if (!eventToEdit.isSameEvent(editedEvent) && model.hasEvent(editedEvent)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EVENT);
+ }
+ if (!model.areEventFriendsValid(editedEvent)) {
+ throw new CommandException(MESSAGE_INVALID_EVENT_FRIENDS);
+ }
+
+ model.setEvent(eventToEdit, editedEvent);
+
+ String output = String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent);
+ if (editedEvent.isBeforeNow()) {
+ output += "\n" + MESSAGE_PAST_EVENT_WARNING;
+ }
+
+ return new CommandResult(output, false, false, true);
+ }
+
+ /**
+ * Creates and returns a {@code Event} with the details of {@code eventToEdit}
+ * edited with {@code editEventDescriptor}.
+ */
+ private static Event createEditedEvent(Event eventToEdit, EditEventDescriptor editEventDescriptor)
+ throws CommandException {
+ assert eventToEdit != null;
+
+ EventName updatedName = editEventDescriptor.getName().orElse(eventToEdit.getName());
+ DateTime updatedDateTime = editEventDescriptor.getDateTime().orElse(eventToEdit.getDateTime());
+ Description updatedDescription = editEventDescriptor.getDescription().orElse(eventToEdit.getDescription());
+ Set addFriendNames = editEventDescriptor.getAddFriendNames().orElse(null);
+ Set removeFriendNames = editEventDescriptor.getRemoveFriendNames().orElse(null);
+ Set currentFriendName = eventToEdit.getFriendNames();
+ Set updatedFriendNames = new HashSet<>(currentFriendName);
+ if (removeFriendNames != null) {
+ for (FriendName friendNameToRemove : removeFriendNames) {
+ if (!currentFriendName.contains(friendNameToRemove)) {
+ throw new CommandException(MESSAGE_INVALID_FRIENDS_TO_REMOVE);
+ }
+ }
+ updatedFriendNames.removeAll(removeFriendNames);
+ }
+ if (addFriendNames != null) {
+ updatedFriendNames.addAll(addFriendNames);
+ }
+
+ return new Event(updatedName, updatedDateTime, updatedDescription, updatedFriendNames);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditEventCommand)) {
+ return false;
+ }
+
+ // state check
+ EditEventCommand e = (EditEventCommand) other;
+ return index.equals(e.index)
+ && editEventDescriptor.equals(e.editEventDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the event with. Each non-empty field value will replace the
+ * corresponding field value of the event.
+ */
+ public static class EditEventDescriptor {
+ private EventName name;
+ private DateTime dateTime;
+ private Description description;
+ private Set addFriendNames;
+ private Set removeFriendNames;
+
+
+ public EditEventDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code EventDescriptor} is used internally.
+ */
+ public EditEventDescriptor(EditEventDescriptor toCopy) {
+ setName(toCopy.name);
+ setDateTime(toCopy.dateTime);
+ setDescription(toCopy.description);
+ setAddFriendNames(toCopy.addFriendNames);
+ setRemoveFriendNames(toCopy.removeFriendNames);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, dateTime, description, addFriendNames, removeFriendNames);
+ }
+
+ public void setName(EventName name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setDateTime(DateTime dateTime) {
+ this.dateTime = dateTime;
+ }
+
+ public Optional getDateTime() {
+ return Optional.ofNullable(dateTime);
+ }
+
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ /**
+ * Sets {@code addFriendsNames} to this object's {@code addFriendsNames}.
+ * A defensive copy of {@code friendsNames} is used internally.
+ */
+ public void setAddFriendNames(Set addFriendsNames) {
+ this.addFriendNames = (addFriendsNames != null) ? new HashSet<>(addFriendsNames) : null;
+ }
+
+ /**
+ * Returns an unmodifiable name set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code names} is null.
+ */
+ public Optional> getAddFriendNames() {
+ return (addFriendNames != null) ? Optional.of(Collections.unmodifiableSet(addFriendNames)) : Optional.empty();
+ }
+
+ /**
+ * Sets {@code removeFriendsNames} to this object's {@code removeFriendsNames}.
+ * A defensive copy of {@code friendsNames} is used internally.
+ */
+ public void setRemoveFriendNames(Set removeFriendNames) {
+ this.removeFriendNames = (removeFriendNames != null) ? new HashSet<>(removeFriendNames) : null;
+ }
+
+ /**
+ * Returns an unmodifiable name set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code names} is null.
+ */
+ public Optional> getRemoveFriendNames() {
+ return (removeFriendNames != null) ? Optional.of(Collections.unmodifiableSet(removeFriendNames)) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditEventDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditEventDescriptor e = (EditEventDescriptor) other;
+
+ return getName().equals(e.getName())
+ && getDateTime().equals(e.getDateTime())
+ && getDescription().equals(e.getDescription())
+ && getAddFriendNames().equals(e.getAddFriendNames())
+ && getRemoveFriendNames().equals(e.getRemoveFriendNames());
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditLogCommand.java b/src/main/java/seedu/address/logic/commands/EditLogCommand.java
new file mode 100644
index 00000000000..d683aa56282
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditLogCommand.java
@@ -0,0 +1,259 @@
+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_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LOG_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.common.Description;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Log;
+import seedu.address.model.person.LogName;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * Edits a log of a person in the address book.
+ */
+public class EditLogCommand extends ByIndexByNameCommand {
+
+ public static final String COMMAND_WORD = "editlog";
+ public static final String COMMAND_ALIAS = "el";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Edits an existing log "
+ + "of an existing friend in Amigos. "
+ + "Parameters: "
+ + "INDEX ? " + PREFIX_NAME + "NAME "
+ + PREFIX_LOG_INDEX + "LOG_INDEX ["
+ + PREFIX_TITLE + "NEW_TITLE] ["
+ + PREFIX_DESCRIPTION + "NEW DESCRIPTION]\n"
+ + "Note that at least one of title and description must be provided.\n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 "
+ + PREFIX_LOG_INDEX + "1 "
+ + PREFIX_DESCRIPTION + "Likes apples";
+
+ public static final String MESSAGE_EDIT_LOG_SUCCESS = "Log successfully edited!";
+ public static final String MESSAGE_LOG_NOT_FOUND = "The specified log does not exist!";
+ public static final String MESSAGE_DUPLICATE_LOG = "This log already exists for this friend.";
+
+ // data fields
+ private final Index index;
+ private final Index logIndex;
+ private final FriendName nameToEditLog;
+ private final EditLogDescriptor editLogDescriptor;
+ private final boolean byName;
+
+ /**
+ * Creates an EditLogCommand to add the specified {@code Log} to the
+ * specified {@code Person}.
+ */
+ public EditLogCommand(Index index, Index logIndex, EditLogDescriptor editLogDescriptor) {
+ requireAllNonNull(index, logIndex, editLogDescriptor);
+ this.index = index;
+ this.nameToEditLog = null;
+ this.logIndex = logIndex;
+ this.editLogDescriptor = editLogDescriptor;
+ this.byName = false;
+ }
+
+ /**
+ * Creates an EditLogCommand to add the specified {@code Log} to the specified
+ * {@code Person}.
+ */
+ public EditLogCommand(FriendName name, Index logIndex, EditLogDescriptor editLogDescriptor) {
+ requireAllNonNull(name, logIndex, editLogDescriptor);
+ this.nameToEditLog = name;
+ this.index = null;
+ this.logIndex = logIndex;
+ this.editLogDescriptor = editLogDescriptor;
+ this.byName = true;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ Person personToEdit;
+
+ if (this.byName) {
+ personToEdit = EditLogCommand.getPersonByName(model, this.nameToEditLog);
+ } else {
+ personToEdit = EditLogCommand.getPersonByFilteredIndex(model, this.index);
+ }
+
+ // create person with edited logs
+ Person addedLogPerson = createEditedLogPerson(personToEdit, this.logIndex, this.editLogDescriptor);
+
+ // add to address book
+ model.setPerson(personToEdit, addedLogPerson);
+ return new CommandResult(MESSAGE_EDIT_LOG_SUCCESS);
+ }
+
+ private static Person createEditedLogPerson(
+ Person personToEdit, Index logIndex, EditLogDescriptor editLogDescriptor)
+ throws CommandException {
+ requireAllNonNull(personToEdit, logIndex, editLogDescriptor);
+ FriendName name = personToEdit.getName();
+ Phone phone = personToEdit.getPhone();
+ Email email = personToEdit.getEmail();
+ Address address = personToEdit.getAddress();
+ Description description = personToEdit.getDescription();
+ Set tags = personToEdit.getTags();
+ List updatedLogs = editLogDescriptor.getLogsAfterEdit(personToEdit, logIndex); // main logic encompassed here
+ return new Person(name, phone, email, address, description, tags, updatedLogs);
+ }
+
+
+ @Override
+ public boolean equals (Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditLogCommand)) {
+ return false;
+ }
+
+ // cast
+ EditLogCommand e = (EditLogCommand) other;
+
+ // compare descriptors and log index
+ if (!this.editLogDescriptor.equals(e.editLogDescriptor)
+ || !(this.logIndex.equals(e.logIndex))) {
+ return false;
+ }
+
+ // compare name
+ if ((this.byName) && (e.byName)) {
+ assert ((this.index == null) && (e.index == null));
+ return this.nameToEditLog.equals(e.nameToEditLog);
+
+ } else if ((!this.byName) && (!e.byName)) {
+ assert ((this.nameToEditLog == null) && (e.nameToEditLog == null));
+ return this.index.equals(e.index);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Index: " + this.index.toString() + ", Log Index: " + this.logIndex.toString()
+ + "\nContent:\n" + this.editLogDescriptor.toString();
+ }
+
+ /**
+ * Stores the details of the edited log to edit a person's logs with, as well as the person's
+ * original details.
+ */
+ public static class EditLogDescriptor {
+
+ private LogName newTitle;
+ private Description newDescription;
+
+ /**
+ * Constructs a new {@code AddLogDescriptor} object.
+ */
+ public EditLogDescriptor() {
+ this.newTitle = null;
+ this.newDescription = null;
+ }
+
+ public void setNewTitle(String newTitle) {
+ this.newTitle = new LogName(newTitle);
+ }
+
+ public void setNewTitle(LogName newTitle) {
+ this.newTitle = newTitle;
+ }
+
+ public void setNewDescription(String newDescription) {
+ this.newDescription = new Description(newDescription);
+ }
+
+ public void setNewDescription(Description newDescription) {
+ this.newDescription = newDescription;
+ }
+
+ /**
+ * Returns true if title or description has been edited.
+ */
+ public boolean isEdited() {
+ return this.newTitle != null || this.newDescription != null;
+ }
+
+ /**
+ * Returns a list of {@code Log} objects that include the {@code Person}'s original logs
+ * as well as the new logs.
+ */
+ public List getLogsAfterEdit(Person personToEdit, Index logIndex) throws CommandException {
+
+ // sanity checks
+ assert (this.isEdited());
+ List logs = personToEdit.getLogs();
+ if (logIndex.getZeroBased() >= logs.size()) {
+ throw new CommandException(MESSAGE_LOG_NOT_FOUND);
+ }
+
+ // get log to edit
+ Log initialLog = logs.get(logIndex.getZeroBased());
+ Log editedLog = new Log(
+ this.newTitle != null ? this.newTitle : initialLog.getTitle(),
+ this.newDescription != null ? this.newDescription : initialLog.getDescription()); // create log to be added
+
+ // another sanity check
+ if (personToEdit.containsLog(editedLog) && !initialLog.isSameLog(editedLog)) {
+ throw new CommandException(MESSAGE_DUPLICATE_LOG); // ensure edited log does not duplicate with existing
+ }
+
+ // set log
+ List newLogs = new ArrayList<>(personToEdit.getLogs());
+ newLogs.set(logIndex.getZeroBased(), editedLog);
+
+ return newLogs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof
+ if (!(other instanceof EditLogCommand.EditLogDescriptor)) {
+ return false;
+ }
+
+ // cast and check by wrapping into log object
+ EditLogCommand.EditLogDescriptor e = (EditLogCommand.EditLogDescriptor) other;
+ Log thisLog = new Log(this.newTitle, this.newDescription);
+ Log otherLog = new Log(e.newTitle, e.newDescription);
+
+ return thisLog.equals(otherLog);
+ }
+
+ @Override
+ public String toString() {
+ return "Title: " + this.newTitle + "\nDescription: \n" + this.newDescription;
+ }
+
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index d6b19b0a0de..07374eeb9b0 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -1,27 +1,34 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
import seedu.address.commons.core.Messages;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.FriendFilterPredicate;
/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
+ * Finds and lists all friends in address book whose name contains any of the argument keywords.
* Keyword matching is case insensitive.
*/
public class FindCommand extends Command {
- public static final String COMMAND_WORD = "find";
+ public static final String COMMAND_WORD = "findfriend";
+ public static final String COMMAND_ALIAS = "ff";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Finds all friends whose names contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
+ + "Parameters: " + "[" + PREFIX_NAME + "NAME_KEYWORD" + "]... "
+ + "[" + PREFIX_TITLE + "LOG_TITLE_KEYWORD" + "]... "
+ + "[" + PREFIX_TAG + "TAG_KEYWORD" + "]... "
+ + "Example: " + COMMAND_WORD + " n/alice n/bob n/charlie";
- private final NameContainsKeywordsPredicate predicate;
+ private final FriendFilterPredicate predicate;
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ public FindCommand(FriendFilterPredicate predicate) {
this.predicate = predicate;
}
diff --git a/src/main/java/seedu/address/logic/commands/FindEventCommand.java b/src/main/java/seedu/address/logic/commands/FindEventCommand.java
new file mode 100644
index 00000000000..54bcf41c074
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FindEventCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FRIEND_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+/**
+ * Finds and lists all events in Amigos which match all the predicates in the given list.
+ */
+public class FindEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "findevent";
+ public static final String COMMAND_ALIAS = "fe";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + " / " + COMMAND_ALIAS
+ + ": Finds all events whose fields match all of "
+ + "the specified search terms (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: "
+ + "[" + PREFIX_NAME + "EVENT_NAME_SUBSTRING] "
+ + "[" + PREFIX_DATE_START + "DATE_START] "
+ + "[" + PREFIX_DATE_END + "DATE_END] "
+ + "[" + PREFIX_FRIEND_NAME + "FRIEND_NAME_SUBSTRING]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "dinner "
+ + PREFIX_DATE_START + "15-08-2022 "
+ + PREFIX_FRIEND_NAME + "john "
+ + PREFIX_FRIEND_NAME + "joe ";
+
+ private final List> predicates;
+
+ public FindEventCommand(List> predicates) {
+ this.predicates = predicates;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredEventList(this::isAllPredicatesPassed);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size()),
+ false, false, true);
+ }
+
+ private boolean isAllPredicatesPassed(Event event) {
+ for (Predicate predicate : predicates) {
+ if (!predicate.test(event)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindEventCommand // instanceof handles nulls
+ && predicates.equals(((FindEventCommand) other).predicates)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..2217d9b15d6 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -6,13 +6,14 @@
import seedu.address.model.Model;
/**
- * Lists all persons in the address book to the user.
+ * Lists all friends in the address book to the user.
*/
public class ListCommand extends Command {
- public static final String COMMAND_WORD = "list";
+ public static final String COMMAND_WORD = "listfriends";
+ public static final String COMMAND_ALIAS = "lf";
- public static final String MESSAGE_SUCCESS = "Listed all persons";
+ public static final String MESSAGE_SUCCESS = "Listed all friends";
@Override
diff --git a/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java b/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java
new file mode 100644
index 00000000000..93efe2ea106
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ShowEventsCommand.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS;
+
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+
+/**
+ * Lists all events in the address book to the user.
+ */
+public class ShowEventsCommand extends Command {
+
+ public static final String COMMAND_WORD = "listevents";
+ public static final String COMMAND_ALIAS = "le";
+
+ public static final String MESSAGE_USAGE = "listevents [-a]";
+
+ public static final String MESSAGE_SUCCESS = "Listed all upcoming events. To see past events use the -a flag";
+
+ public static final String MESSAGE_SUCCESS_ALL = "Listed all events, including past ones";
+
+ private final boolean isShowAllEvents;
+
+ public ShowEventsCommand(Boolean showAllEvents) {
+ this.isShowAllEvents = showAllEvents;
+ }
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ if (!isShowAllEvents) {
+ model.updateFilteredEventList(Event::isAfterNow);
+ return new CommandResult(MESSAGE_SUCCESS, false, false, true);
+ }
+ model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
+ return new CommandResult(MESSAGE_SUCCESS_ALL, false, false, true);
+ }
+
+ @Override
+ public String toString() {
+ return COMMAND_WORD;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof ShowEventsCommand);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/ShowFriendCommand.java b/src/main/java/seedu/address/logic/commands/ShowFriendCommand.java
new file mode 100644
index 00000000000..21e77d60334
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ShowFriendCommand.java
@@ -0,0 +1,107 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.time.LocalDate;
+import java.util.function.Predicate;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDateIsAfterPredicate;
+import seedu.address.model.event.EventFriendNamesContainSubstringPredicate;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Person;
+
+
+
+public class ShowFriendCommand extends ByIndexByNameCommand {
+
+ public static final String COMMAND_WORD = "showfriend";
+ public static final String COMMAND_ALIAS = "sf";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows full details of a friend in"
+ + " the address book. "
+ + "Parameters: "
+ + "INDEX ? "
+ + PREFIX_NAME + " NAME \n"
+ + "Example 1: " + COMMAND_WORD + " 1 \n"
+ + "Example 2: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe\n";
+
+
+ public static final String MESSAGE_SUCCESS = "Details of %1$s shown below";
+
+ private final FriendName nameOfPersonToShow;
+ private final Index targetIndex;
+ private final boolean isShowByIndex;
+
+ /**
+ * Constructs a ShowFriend for show by name
+ *
+ * @param nameOfPersonToShow The name of the person to be shown.
+ */
+ public ShowFriendCommand(FriendName nameOfPersonToShow) {
+ this.nameOfPersonToShow = nameOfPersonToShow;
+ this.targetIndex = null;
+ this.isShowByIndex = false;
+ }
+
+ /**
+ * Constructs a ShowFriendCommand for show by index.
+ *
+ * @param targetIndex The index of the person to be shown on the expanded person card on GUI.
+ */
+ public ShowFriendCommand(Index targetIndex) {
+ this.nameOfPersonToShow = null;
+ this.targetIndex = targetIndex;
+ this.isShowByIndex = true;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Person personToShow;
+
+ if (isShowByIndex) {
+ personToShow = this.getPersonByFilteredIndex(model, targetIndex);
+ } else { //show by name
+ personToShow = this.getPersonByName(model, nameOfPersonToShow);
+ }
+ assert(personToShow != null);
+
+
+ //updates UI to only show a single person
+ model.updateFilteredPersonList(x -> x.isSamePerson(personToShow));
+
+ // updates UI to show upcoming events tied to this person
+ // TODO: Known bug, will show upcoming events for today that have already passed...
+ Predicate upcomingEventPredicate = new EventDateIsAfterPredicate(LocalDate.now());
+ Predicate friendPredicate = new EventFriendNamesContainSubstringPredicate(personToShow.getName());
+
+ model.updateFilteredEventList(event -> upcomingEventPredicate.test(event) && friendPredicate.test(event));
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, personToShow.getName()), false,
+ false, false, true, false);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof ShowFriendCommand) {
+ ShowFriendCommand otherShowFriendCommand = (ShowFriendCommand) other;
+ if (otherShowFriendCommand.isShowByIndex && this.isShowByIndex) {
+ //assertion to ensure that if it is show by index, then targetIndex will not be null
+ assert(otherShowFriendCommand.targetIndex != null && this.targetIndex != null);
+ return otherShowFriendCommand.targetIndex.equals(this.targetIndex);
+ } else if (!otherShowFriendCommand.isShowByIndex && !this.isShowByIndex) {
+ //assertion to ensure that if it is show by name, then name will not be null
+ assert(otherShowFriendCommand.nameOfPersonToShow != null && this.nameOfPersonToShow != null);
+ return otherShowFriendCommand.nameOfPersonToShow.equals(this.nameOfPersonToShow);
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ShowInsightsCommand.java b/src/main/java/seedu/address/logic/commands/ShowInsightsCommand.java
new file mode 100644
index 00000000000..0deebed7c41
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ShowInsightsCommand.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.model.Model;
+
+public class ShowInsightsCommand extends Command {
+
+ public static final String COMMAND_WORD = "showinsights";
+ public static final String COMMAND_ALIAS = "si";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows insights about friends in Amigos.\n";
+
+ public static final String MESSAGE_SUCCESS = "Insights of friends shown below!";
+
+ /**
+ * Creates a ShowInsightsCommand.
+ */
+ public ShowInsightsCommand() {}
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model); // sanity check
+
+ // simply directs UI to show, since UI handles updating
+ return new CommandResult(MESSAGE_SUCCESS, false, false, false, false, true);
+ }
+
+ @Override
+ public String toString() {
+ return COMMAND_WORD;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ShowInsightsCommand;
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 3b8bfa035e8..0199dd9fbd3 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,20 +1,22 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.ArgumentMultimap.arePrefixesPresent;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Set;
-import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.common.Description;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
+import seedu.address.model.person.FriendName;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -27,34 +29,35 @@ public class AddCommandParser implements Parser {
/**
* Parses the given {@code String} of arguments in the context of the AddCommand
* and returns an AddCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_DESCRIPTION, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ FriendName name = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ Phone phone = argMultimap.getValue(PREFIX_PHONE).isPresent()
+ ? ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())
+ : null;
+ Email email = argMultimap.getValue(PREFIX_EMAIL).isPresent()
+ ? ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())
+ : null;
+ Address address = argMultimap.getValue(PREFIX_ADDRESS).isPresent()
+ ? ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())
+ : null;
+ Description description = argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()
+ ? ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())
+ : new Description(null);
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, address, description, tagList, null); // explicitly no logs
return new AddCommand(person);
}
-
- /**
- * Returns true if none of the prefixes contains empty {@code Optional} values in the given
- * {@code ArgumentMultimap}.
- */
- private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
- return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
- }
-
}
diff --git a/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java
new file mode 100644
index 00000000000..7c558f5e5f0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.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_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FRIEND_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.Set;
+
+import seedu.address.logic.commands.AddEventCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.common.Description;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventName;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses input arguments and creates a new AddEventCommand object
+ */
+public class AddEventCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddEventCommand
+ * and returns an AddEventCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddEventCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATETIME, PREFIX_DESCRIPTION, PREFIX_FRIEND_NAME);
+
+ if (!argMultimap.arePrefixesPresent(PREFIX_NAME, PREFIX_DATETIME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEventCommand.MESSAGE_USAGE));
+ }
+
+ EventName name = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get());
+ DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get());
+
+ // Optional Fields
+ Description description;
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get());
+ } else {
+ description = new Description(null);
+ }
+
+ Set friendNames = ParserUtil.parseFriendNames(argMultimap.getAllValues(PREFIX_FRIEND_NAME));
+
+ Event event = new Event(name, dateTime, description, friendNames);
+
+ return new AddEventCommand(event);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddLogCommandParser.java b/src/main/java/seedu/address/logic/parser/AddLogCommandParser.java
new file mode 100644
index 00000000000..b7b36e9828f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddLogCommandParser.java
@@ -0,0 +1,82 @@
+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.ArgumentMultimap.arePrefixesPresent;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.AddLogCommand;
+import seedu.address.logic.commands.AddLogCommand.AddLogDescriptor;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses input arguments and creates a new AddLogCommand object
+ */
+public class AddLogCommandParser implements Parser {
+
+ public static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddLogCommand.MESSAGE_USAGE);
+
+ /**
+ * Parses a string of arguments and creates an {@code AddLogCommand}.
+ *
+ * @throws ParseException if command string is formatted wrongly
+ */
+ public AddLogCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TITLE, PREFIX_DESCRIPTION);
+
+ FriendName name = null;
+ Index index = null;
+
+ // ensure title prefix is present
+ if (!arePrefixesPresent(argMultimap, PREFIX_TITLE)) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // ensure exactly one of index or name prefix is present
+ boolean hasIndex = !argMultimap.getPreamble().isEmpty();
+ boolean hasName = arePrefixesPresent(argMultimap, PREFIX_NAME);
+ if ((!hasIndex && !hasName)
+ || (hasIndex && hasName)) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // parse index or name
+ if (hasIndex) {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } else {
+ name = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ }
+
+ // read other arguments
+ AddLogDescriptor addLogDescriptor = new AddLogDescriptor();
+
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ addLogDescriptor.setNewTitle(ParserUtil.parseTitle(argMultimap
+ .getValue(PREFIX_TITLE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ addLogDescriptor.setNewDescription(ParserUtil.parseDescription(argMultimap
+ .getValue(PREFIX_DESCRIPTION).get()));
+ }
+
+ // check validity
+ if (!addLogDescriptor.isTitleEdited()) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ AddLogCommand command;
+ if (hasIndex) {
+ command = new AddLogCommand(index, addLogDescriptor);
+ } else {
+ command = new AddLogCommand(name, addLogDescriptor);
+ }
+ return command;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..d7dd733ab1e 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -7,14 +7,24 @@
import java.util.regex.Pattern;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddEventCommand;
+import seedu.address.logic.commands.AddLogCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteEventCommand;
+import seedu.address.logic.commands.DeleteLogCommand;
import seedu.address.logic.commands.EditCommand;
+import seedu.address.logic.commands.EditEventCommand;
+import seedu.address.logic.commands.EditLogCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FindEventCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ShowEventsCommand;
+import seedu.address.logic.commands.ShowFriendCommand;
+import seedu.address.logic.commands.ShowInsightsCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -45,29 +55,75 @@ public Command parseCommand(String userInput) throws ParseException {
switch (commandWord) {
case AddCommand.COMMAND_WORD:
+ case AddCommand.COMMAND_ALIAS:
return new AddCommandParser().parse(arguments);
case EditCommand.COMMAND_WORD:
+ case EditCommand.COMMAND_ALIAS:
return new EditCommandParser().parse(arguments);
case DeleteCommand.COMMAND_WORD:
+ case DeleteCommand.COMMAND_ALIAS:
return new DeleteCommandParser().parse(arguments);
case ClearCommand.COMMAND_WORD:
return new ClearCommand();
case FindCommand.COMMAND_WORD:
+ case FindCommand.COMMAND_ALIAS:
return new FindCommandParser().parse(arguments);
case ListCommand.COMMAND_WORD:
+ case ListCommand.COMMAND_ALIAS:
return new ListCommand();
+ case ShowFriendCommand.COMMAND_WORD:
+ case ShowFriendCommand.COMMAND_ALIAS:
+ return new ShowFriendCommandParser().parse(arguments);
+
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case AddLogCommand.COMMAND_WORD:
+ case AddLogCommand.COMMAND_ALIAS:
+ return new AddLogCommandParser().parse(arguments);
+
+ case DeleteLogCommand.COMMAND_WORD:
+ case DeleteLogCommand.COMMAND_ALIAS:
+ return new DeleteLogCommandParser().parse(arguments);
+
+ case EditLogCommand.COMMAND_WORD:
+ case EditLogCommand.COMMAND_ALIAS:
+ return new EditLogCommandParser().parse(arguments);
+
+ case AddEventCommand.COMMAND_WORD:
+ case AddEventCommand.COMMAND_ALIAS:
+ return new AddEventCommandParser().parse(arguments);
+
+ case EditEventCommand.COMMAND_WORD:
+ case EditEventCommand.COMMAND_ALIAS:
+ return new EditEventCommandParser().parse(arguments);
+
+ case DeleteEventCommand.COMMAND_WORD:
+ case DeleteEventCommand.COMMAND_ALIAS:
+ return new DeleteEventCommandParser().parse(arguments);
+
+ case ShowEventsCommand.COMMAND_WORD:
+ case ShowEventsCommand.COMMAND_ALIAS:
+ return new ShowEventsCommandParser().parse(arguments);
+
+
+ case ShowInsightsCommand.COMMAND_WORD:
+ case ShowInsightsCommand.COMMAND_ALIAS:
+ return new ShowInsightsCommand();
+
+ case FindEventCommand.COMMAND_WORD:
+ case FindEventCommand.COMMAND_ALIAS:
+ return new FindEventCommandParser().parse(arguments);
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..09d39c98832 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
@@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Stream;
/**
* Stores mapping of prefixes to their respective arguments.
@@ -57,4 +58,25 @@ public List getAllValues(Prefix prefix) {
public String getPreamble() {
return getValue(new Prefix("")).orElse("");
}
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the
+ * {@code ArgumentMultimap}.
+ */
+ public boolean arePrefixesPresent(Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> this.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return argumentMultimap.arePrefixesPresent(prefixes);
+ }
+
+ @Override
+ public String toString() {
+ return this.argMultimap.toString();
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..b1c96bdb301 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -10,6 +10,26 @@ 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_DESCRIPTION = new Prefix("d/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_CURRENT_NAME = new Prefix("cn/");
+ public static final Prefix PREFIX_NEW_NAME = new Prefix("nn/");
+ public static final Prefix PREFIX_NEW_PHONE = new Prefix("np/");
+ public static final Prefix PREFIX_NEW_EMAIL = new Prefix("ne/");
+ public static final Prefix PREFIX_NEW_ADDRESS = new Prefix("na/");
+ public static final Prefix PREFIX_NEW_DESCRIPTION = new Prefix("nd/");
+ public static final Prefix PREFIX_NEW_TAG = new Prefix("nt/");
+
+ public static final Prefix PREFIX_TITLE = new Prefix("ttl/");
+ public static final Prefix PREFIX_LOG_INDEX = new Prefix("id/");
+ public static final Prefix FLAG_ALL = new Prefix("-a");
+
+ public static final Prefix PREFIX_DATETIME = new Prefix("dt/");
+ public static final Prefix PREFIX_DATE = new Prefix("da/");
+ public static final Prefix PREFIX_DATE_START = new Prefix("ds/");
+ public static final Prefix PREFIX_DATE_END = new Prefix("de/");
+ public static final Prefix PREFIX_FRIEND_NAME = new Prefix("f/");
+ public static final Prefix PREFIX_ADD_FRIENDNAME = new Prefix("af/");
+ public static final Prefix PREFIX_REMOVE_FRIENDNAME = new Prefix("rf/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 522b93081cc..e90ed21d188 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -1,10 +1,17 @@
package seedu.address.logic.parser;
+import static seedu.address.commons.core.Messages.MESSAGE_INDEX_IS_NOT_NON_ZERO_UNSIGNED_INTEGER;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.DeleteCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
/**
* Parses input arguments and creates a new DeleteCommand object
@@ -17,13 +24,47 @@ public class DeleteCommandParser implements Parser {
* @throws ParseException if the user input does not conform the expected format
*/
public DeleteCommand parse(String args) throws ParseException {
- try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
- } catch (ParseException pe) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+
+ Pattern p = Pattern.compile("^-?\\d*\\.{0,1}\\d+$"); //Regex to match all numeric Strings
+ boolean isDeletionByIndex = isNumeric(p, args);
+
+ if (isDeletionByIndex) { //deletion by index
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INDEX_IS_NOT_NON_ZERO_UNSIGNED_INTEGER);
+ }
+ } else { //deletion by name
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ }
+
+ FriendName name = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ return new DeleteCommand(name);
}
}
+ /**
+ * 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());
+ }
+
+ /**
+ * Checks whether the argument entered by user is numeric
+ * @param p Regex to check if the argument entered by user is numeric
+ * @param strNum Argument entered by user.
+ * @return True if the argument entered by user is numeric
+ */
+ private static boolean isNumeric(Pattern p, String strNum) {
+ String strNumTrimmed = strNum.trim();
+ return p.matcher(strNumTrimmed).matches();
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java
new file mode 100644
index 00000000000..6fb596cb453
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteEventCommandParser.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteEventCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteEventCommand object
+ */
+public class DeleteEventCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteEventCommand
+ * and returns a DeleteEventCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteEventCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteEventCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(pe.getMessage() + "\n" + DeleteEventCommand.MESSAGE_USAGE);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteLogCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteLogCommandParser.java
new file mode 100644
index 00000000000..40f1f68b7a5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteLogCommandParser.java
@@ -0,0 +1,91 @@
+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.ArgumentMultimap.arePrefixesPresent;
+import static seedu.address.logic.parser.CliSyntax.FLAG_ALL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LOG_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteLogCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses the input arguments and creates a new DeleteLogCommand object.
+ */
+public class DeleteLogCommandParser implements Parser {
+
+ public static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteLogCommand.MESSAGE_USAGE);
+
+
+ /**
+ * Parses a string of arguments and creates a {@code DeleteLogCommand}.
+ *
+ * @throws ParseException if arguments are formatted wrongly.
+ */
+ public DeleteLogCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ // tokenize
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_LOG_INDEX, FLAG_ALL);
+
+ // check that log index is present, or all flag present, but not both
+ if (arePrefixesPresent(argMultimap, PREFIX_LOG_INDEX) == arePrefixesPresent(argMultimap, FLAG_ALL)) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // initialize
+ Index personIndex = null;
+ Index logIndex = null;
+ FriendName personName = null;
+ boolean hasIndex = !argMultimap.getPreamble().isEmpty();
+ boolean hasName = arePrefixesPresent(argMultimap, PREFIX_NAME);
+ boolean isForOnePerson = hasIndex ^ hasName;
+ boolean isForDeletingAllLogs = false;
+
+ // parse index or name
+ if (hasIndex) {
+ personIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } else if (hasName) {
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ personName = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ }
+ }
+
+ // read other arguments
+ if (argMultimap.getValue(PREFIX_LOG_INDEX).isPresent()) {
+ logIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_LOG_INDEX).get());
+ }
+ if (arePrefixesPresent(argMultimap, FLAG_ALL)) {
+ isForDeletingAllLogs = true;
+ }
+
+ // check for validity
+ // case 1: delete specific log, specific person
+ // case 2: delete all logs, specific person
+ // case 3: delete all logs
+ if (!(isForOnePerson && !isForDeletingAllLogs && logIndex != null
+ || isForOnePerson && isForDeletingAllLogs && logIndex == null
+ || !isForOnePerson && isForDeletingAllLogs && logIndex == null && !hasIndex)) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // call correct constructor
+ DeleteLogCommand command;
+ if (hasIndex) {
+ command = new DeleteLogCommand(isForOnePerson, isForDeletingAllLogs, personIndex, logIndex);
+ } else {
+ if (personName == null) { // means delete all
+ command = new DeleteLogCommand(isForDeletingAllLogs);
+ } else {
+ command = new DeleteLogCommand(isForOnePerson, isForDeletingAllLogs, personName, logIndex);
+ }
+
+ }
+ return command;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..4a314542bef 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -3,8 +3,10 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CURRENT_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -17,6 +19,7 @@
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
import seedu.address.model.tag.Tag;
/**
@@ -27,24 +30,54 @@ public class EditCommandParser implements Parser {
/**
* Parses the given {@code String} of arguments in the context of the EditCommand
* and returns an EditCommand object for execution.
+ *
* @throws ParseException if the user input does not conform the expected format
*/
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_CURRENT_NAME, PREFIX_NEW_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_DESCRIPTION, PREFIX_TAG);
+
+ //throws exception if neither current name and index are given
+ if (!argMultimap.getValue(PREFIX_CURRENT_NAME).isPresent() && argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE));
+ }
+
+ //throws exception if both current name and index are given
+ if (argMultimap.getValue(PREFIX_CURRENT_NAME).isPresent() && !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE));
+ }
Index index;
+ FriendName friendName;
+
+ if (!argMultimap.getPreamble().isEmpty()) {
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ }
+ EditPersonDescriptor editPersonDescriptor = getEditPersonDescriptor(argMultimap);
+ return new EditCommand(index, editPersonDescriptor);
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ } else {
+ friendName = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_CURRENT_NAME).get());
+ EditPersonDescriptor editPersonDescriptor = getEditPersonDescriptor(argMultimap);
+ return new EditCommand(friendName, editPersonDescriptor);
}
+ }
+
+ /**
+ * Constructs an EditPersonDescriptor based on the new fields input by the user.
+ *
+ * @throws ParseException when any field given is invalid.
+ */
+ private EditPersonDescriptor getEditPersonDescriptor(ArgumentMultimap argMultimap) throws ParseException {
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
- if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
- editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
+ if (argMultimap.getValue(PREFIX_NEW_NAME).isPresent()) {
+ editPersonDescriptor.setName(ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NEW_NAME).get()));
}
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
@@ -55,13 +88,16 @@ public EditCommand parse(String args) throws ParseException {
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editPersonDescriptor.setDescription(ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()));
+ }
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}
- return new EditCommand(index, editPersonDescriptor);
+ return editPersonDescriptor;
}
/**
@@ -78,5 +114,4 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars
Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
return Optional.of(ParserUtil.parseTags(tagSet));
}
-
}
diff --git a/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java b/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java
new file mode 100644
index 00000000000..88d13336b64
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditEventCommandParser.java
@@ -0,0 +1,80 @@
+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_ADD_FRIENDNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMOVE_FRIENDNAME;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditEventCommand;
+import seedu.address.logic.commands.EditEventCommand.EditEventDescriptor;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses input arguments and creates a new EditEventCommand object
+ */
+public class EditEventCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditEventCommand
+ * and returns an EditEventCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditEventCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATETIME, PREFIX_DESCRIPTION,
+ PREFIX_ADD_FRIENDNAME, PREFIX_REMOVE_FRIENDNAME);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditEventCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditEventDescriptor editEventDescriptor = new EditEventDescriptor();
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editEventDescriptor.setName(ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DATETIME).isPresent()) {
+ editEventDescriptor.setDateTime(ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editEventDescriptor.setDescription(ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()));
+ }
+ parseNamesForEdit(argMultimap.getAllValues(PREFIX_ADD_FRIENDNAME)).ifPresent(editEventDescriptor::setAddFriendNames);
+ parseNamesForEdit(argMultimap.getAllValues(PREFIX_REMOVE_FRIENDNAME)).ifPresent(editEventDescriptor::setRemoveFriendNames);
+
+ if (!editEventDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditEventCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditEventCommand(index, editEventDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection names} into a {@code Set} if {@code names} is non-empty.
+ * If {@code names} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero names.
+ */
+ private Optional> parseNamesForEdit(Collection names) throws ParseException {
+ assert names != null;
+
+ if (names.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(ParserUtil.parseFriendNames(names));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditLogCommandParser.java b/src/main/java/seedu/address/logic/parser/EditLogCommandParser.java
new file mode 100644
index 00000000000..d1863855bde
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditLogCommandParser.java
@@ -0,0 +1,92 @@
+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.ArgumentMultimap.arePrefixesPresent;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LOG_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditLogCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
+public class EditLogCommandParser implements Parser {
+ public static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditLogCommand.MESSAGE_USAGE);
+ public static final String MESSAGE_NO_EDITS_DETECTED = "No edits made!";
+
+ /**
+ * Parses a string argument and returns an EditLogCommand.
+ */
+ public EditLogCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ // tokenize
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args,
+ PREFIX_NAME, PREFIX_LOG_INDEX,
+ PREFIX_TITLE, PREFIX_DESCRIPTION);
+
+ // check that log index, and at least one of new title/description is present
+ if (!arePrefixesPresent(argMultimap, PREFIX_LOG_INDEX)
+ || (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION)
+ && !arePrefixesPresent(argMultimap, PREFIX_TITLE))) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // initialize
+ Index personIndex = null;
+ Index logIndex = null;
+ FriendName personName = null;
+ EditLogCommand.EditLogDescriptor descriptor = new EditLogCommand.EditLogDescriptor();
+ boolean hasIndex = !argMultimap.getPreamble().isEmpty();
+ boolean hasName = arePrefixesPresent(argMultimap, PREFIX_NAME);
+
+ // ensure exactly one of index or name prefix is present
+ if ((!hasIndex && !hasName)
+ || (hasIndex && hasName)) {
+ throw new ParseException(MESSAGE_INVALID_FORMAT);
+ }
+
+ // parse index or name
+ if (hasIndex) {
+ personIndex = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } else if (hasName) {
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ personName = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ }
+ }
+
+
+
+ // parse other arguments
+ if (argMultimap.getValue(PREFIX_LOG_INDEX).isPresent()) {
+ logIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_LOG_INDEX).get());
+ }
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ descriptor.setNewTitle(ParserUtil.parseTitle(
+ argMultimap.getValue(PREFIX_TITLE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ descriptor.setNewDescription(ParserUtil.parseDescription(
+ argMultimap.getValue(PREFIX_DESCRIPTION).get()));
+ }
+
+ // check that an actual edit has taken place
+ if (!descriptor.isEdited()) {
+ throw new ParseException(MESSAGE_NO_EDITS_DETECTED);
+ }
+
+ // create command
+ EditLogCommand command;
+ if (hasIndex) {
+ command = new EditLogCommand(personIndex, logIndex, descriptor);
+ } else {
+ command = new EditLogCommand(personName, logIndex, descriptor);
+ }
+ return command;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 4fb71f23103..866cc5fe080 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,12 +1,18 @@
package seedu.address.logic.parser;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE;
-import java.util.Arrays;
+import java.util.Set;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.FriendFilterPredicate;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.LogName;
+import seedu.address.model.tag.Tag;
/**
* Parses input arguments and creates a new FindCommand object
@@ -19,15 +25,24 @@ public class FindCommandParser implements Parser {
* @throws ParseException if the user input does not conform the expected format
*/
public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TITLE, PREFIX_TAG);
+
+ boolean isAnyArgumentGiven = argMultimap.arePrefixesPresent(PREFIX_NAME)
+ || argMultimap.arePrefixesPresent(PREFIX_TITLE)
+ || argMultimap.arePrefixesPresent(PREFIX_TAG);
+
+ if (!isAnyArgumentGiven || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ Set nameKeywords = ParserUtil.parseFriendNames(argMultimap.getAllValues(PREFIX_NAME));
+
+ Set logTitleKeywords = ParserUtil.parseTitles(argMultimap.getAllValues(PREFIX_TITLE));
+
+ Set tagKeywords = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ return new FindCommand(new FriendFilterPredicate(nameKeywords, logTitleKeywords, tagKeywords));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java b/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java
new file mode 100644
index 00000000000..5588fe489d2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FindEventCommandParser.java
@@ -0,0 +1,84 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_FRIEND_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.logic.commands.FindEventCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventDateIsAfterPredicate;
+import seedu.address.model.event.EventDateIsBeforePredicate;
+import seedu.address.model.event.EventFriendNamesContainSubstringPredicate;
+import seedu.address.model.event.EventName;
+import seedu.address.model.event.EventNameContainsSubstringPredicate;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses input arguments and creates a new FindEventCommand object
+ */
+public class FindEventCommandParser implements Parser {
+
+ public static final String MESSAGE_INVALID_DATE_RANGE = "The start date cannot be after the end date";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindEventCommand
+ * and returns a FindEventCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindEventCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE_START, PREFIX_DATE_END, PREFIX_FRIEND_NAME);
+
+ boolean isAnyArgumentGiven = argMultimap.arePrefixesPresent(PREFIX_NAME)
+ || argMultimap.arePrefixesPresent(PREFIX_DATE_START)
+ || argMultimap.arePrefixesPresent(PREFIX_DATE_END)
+ || argMultimap.arePrefixesPresent(PREFIX_FRIEND_NAME);
+
+ if (!isAnyArgumentGiven || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindEventCommand.MESSAGE_USAGE));
+ }
+
+ ArrayList> eventPredicates = new ArrayList<>();
+
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ EventName eventNameSubstring = ParserUtil.parseEventName(argMultimap.getValue(PREFIX_NAME).get());
+ eventPredicates.add(new EventNameContainsSubstringPredicate(eventNameSubstring));
+ }
+
+ LocalDate startDate = null;
+ LocalDate endDate = null;
+
+ if (argMultimap.getValue(PREFIX_DATE_START).isPresent()) {
+ startDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE_START).get());
+ LocalDate nonInclusiveStartDate = startDate.minusDays(1);
+ eventPredicates.add(new EventDateIsAfterPredicate(nonInclusiveStartDate));
+ }
+
+ if (argMultimap.getValue(PREFIX_DATE_END).isPresent()) {
+ endDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE_END).get());
+ LocalDate nonInclusiveEndDate = endDate.plusDays(1);
+ eventPredicates.add(new EventDateIsBeforePredicate(nonInclusiveEndDate));
+ }
+
+ if (startDate != null && endDate != null && startDate.isAfter(endDate)) {
+ throw new ParseException(MESSAGE_INVALID_DATE_RANGE);
+ }
+
+ List friendNameSubstrings = argMultimap.getAllValues(PREFIX_FRIEND_NAME);
+ for (String substring : friendNameSubstrings) {
+ FriendName friendNameSubstring = ParserUtil.parseFriendName(substring);
+ eventPredicates.add(new EventFriendNamesContainSubstringPredicate(friendNameSubstring));
+ }
+
+ return new FindEventCommand(Collections.unmodifiableList(eventPredicates));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..a095813e60b 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,6 +2,9 @@
import static java.util.Objects.requireNonNull;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -9,9 +12,13 @@
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.common.Description;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.EventName;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.LogName;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -20,11 +27,14 @@
*/
public class ParserUtil {
- public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+ public static final String MESSAGE_INVALID_INDEX = "The person index provided is invalid";
+ public static final DateTimeFormatter INPUT_DATE_FORMATTER = DateTimeFormatter.ofPattern("d-M-yyyy");
+ public static final String MESSAGE_INVALID_DATE = "Dates should be given in the format DD-MM-YYYY.";
/**
* 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 {
@@ -41,13 +51,28 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
*
* @throws ParseException if the given {@code name} is invalid.
*/
- public static Name parseName(String name) throws ParseException {
- requireNonNull(name);
+ public static FriendName parseFriendName(String name) throws ParseException {
+ requireNonNull(name); //when a name is entered by user, it should not be null
+ String trimmedName = name.trim();
+ if (!FriendName.isValidFriendName(trimmedName)) {
+ throw new ParseException(FriendName.MESSAGE_CONSTRAINTS);
+ }
+ return new FriendName(trimmedName);
+ }
+
+ /**
+ * Parses a {@code String name} into a {@code EventName}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static EventName parseEventName(String name) throws ParseException {
+ requireNonNull(name); //when a name is entered by user, it should not be null
String trimmedName = name.trim();
- if (!Name.isValidName(trimmedName)) {
- throw new ParseException(Name.MESSAGE_CONSTRAINTS);
+ if (!EventName.isValidEventName(trimmedName)) {
+ throw new ParseException(EventName.MESSAGE_CONSTRAINTS);
}
- return new Name(trimmedName);
+ return new EventName(trimmedName);
}
/**
@@ -57,7 +82,7 @@ public static Name parseName(String name) throws ParseException {
* @throws ParseException if the given {@code phone} is invalid.
*/
public static Phone parsePhone(String phone) throws ParseException {
- requireNonNull(phone);
+ requireNonNull(phone); //when a phone number is entered by user, it should not be null
String trimmedPhone = phone.trim();
if (!Phone.isValidPhone(trimmedPhone)) {
throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
@@ -72,7 +97,7 @@ public static Phone parsePhone(String phone) throws ParseException {
* @throws ParseException if the given {@code address} is invalid.
*/
public static Address parseAddress(String address) throws ParseException {
- requireNonNull(address);
+ requireNonNull(address); //when an address is entered by user, it should not be null
String trimmedAddress = address.trim();
if (!Address.isValidAddress(trimmedAddress)) {
throw new ParseException(Address.MESSAGE_CONSTRAINTS);
@@ -87,7 +112,7 @@ public static Address parseAddress(String address) throws ParseException {
* @throws ParseException if the given {@code email} is invalid.
*/
public static Email parseEmail(String email) throws ParseException {
- requireNonNull(email);
+ requireNonNull(email); //when an email is entered by user, it should not be null
String trimmedEmail = email.trim();
if (!Email.isValidEmail(trimmedEmail)) {
throw new ParseException(Email.MESSAGE_CONSTRAINTS);
@@ -95,6 +120,64 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String dateTime} into a {@code DateTime}.
+ *
+ * @throws ParseException if the given {@code String dateTime} is invalid.
+ */
+ public static DateTime parseDateTime(String dateTime) throws ParseException {
+ requireNonNull(dateTime);
+ String trimmedDateTime = dateTime.trim();
+ if (!DateTime.isValidDateTime(trimmedDateTime)) {
+ throw new ParseException(DateTime.MESSAGE_CONSTRAINTS);
+ }
+ return new DateTime(trimmedDateTime);
+ }
+
+ /**
+ * Parses a {@code String date} into a {@code LocalDate}.
+ *
+ * @throws ParseException if the given {@code String date} is invalid.
+ */
+ public static LocalDate parseDate(String date) throws ParseException {
+ requireNonNull(date);
+ String trimmedDate = date.trim();
+ try {
+ return LocalDate.parse(trimmedDate, INPUT_DATE_FORMATTER);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(MESSAGE_INVALID_DATE);
+ }
+ }
+
+ /**
+ * Parses {@code Collection friend names} into a {@code Set}.
+ *
+ * @throws ParseException if a given {@code names} is invalid
+ */
+ public static Set parseFriendNames(Collection names) throws ParseException {
+ requireNonNull(names);
+ final Set nameSet = new HashSet<>();
+ for (String name: names) {
+ nameSet.add(parseFriendName(name));
+ }
+ return nameSet;
+ }
+
+ /**
+ * Parses a {@code String description} into an {@code Description}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code description} is invalid.
+ */
+ public static Description parseDescription(String description) throws ParseException {
+ requireNonNull(description); //when a description is entered by user, it should not be null
+ String trimmedDescription = description.trim();
+ if (!Description.isValidDescription(trimmedDescription)) {
+ throw new ParseException(Description.MESSAGE_CONSTRAINTS);
+ }
+ return new Description(trimmedDescription);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +204,30 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses a String title.
+ *
+ * @throws ParseException if the given {@code title} is invalid.
+ */
+ public static LogName parseTitle(String title) throws ParseException {
+ requireNonNull(title);
+ if (!LogName.isValidLogName(title)) {
+ throw new ParseException(LogName.MESSAGE_CONSTRAINTS);
+ }
+ return new LogName(title);
+ }
+
+ /**
+ * Parses {@code Collection titles} into a {@code Set}.
+ */
+ public static Set parseTitles(Collection titles) throws ParseException {
+ requireNonNull(titles);
+ final Set logNameSet = new HashSet<>();
+ for (String title : titles) {
+ logNameSet.add(parseTitle(title));
+ }
+ return logNameSet;
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java
new file mode 100644
index 00000000000..05658e28c26
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ShowEventsCommandParser.java
@@ -0,0 +1,29 @@
+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.ArgumentMultimap.arePrefixesPresent;
+import static seedu.address.logic.parser.CliSyntax.FLAG_ALL;
+
+import seedu.address.logic.commands.ShowEventsCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class ShowEventsCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ShowEventsCommand
+ * and returns a ShowEventsCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ShowEventsCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, FLAG_ALL);
+
+ if (!argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowEventsCommand.MESSAGE_USAGE));
+ }
+
+ return new ShowEventsCommand(arePrefixesPresent(argMultimap, FLAG_ALL));
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ShowFriendCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowFriendCommandParser.java
new file mode 100644
index 00000000000..353c53060db
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ShowFriendCommandParser.java
@@ -0,0 +1,56 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.ArgumentMultimap.arePrefixesPresent;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import java.util.regex.Pattern;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.ShowFriendCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Parses input arguments and creates a new ShowFriendCommand object
+ */
+public class ShowFriendCommandParser implements Parser {
+
+ /**
+ * Parses string input argument entered by user
+ * @param args Input entered by user.
+ * @return A new ShowFriendCommand containing the person to be shown.
+ * @throws ParseException If the input entered is invalid.
+ */
+ public ShowFriendCommand parse(String args) throws ParseException {
+
+ Pattern p = Pattern.compile("^-?\\d*\\.{0,1}\\d+$"); //Regex to match all numeric Strings
+ boolean isShowByIndex = isNumeric(p, args);
+
+ if (isShowByIndex) {
+ Index index = ParserUtil.parseIndex(args);
+ return new ShowFriendCommand(index);
+ } else { //deletion by name
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowFriendCommand.MESSAGE_USAGE));
+ }
+
+ FriendName name = ParserUtil.parseFriendName(argMultimap.getValue(PREFIX_NAME).get());
+ return new ShowFriendCommand(name);
+ }
+ }
+
+ /**
+ * Checks whether the argument entered by user is numeric
+ * @param p Regex to check if the argument entered by user is numeric
+ * @param strNum Argument entered by user.
+ * @return True if the argument entered by user is numeric
+ */
+ private static boolean isNumeric(Pattern p, String strNum) {
+ String strNumTrimmed = strNum.trim();
+ return p.matcher(strNumTrimmed).matches();
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 1a943a0781a..c3b430a1242 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -2,19 +2,29 @@
import static java.util.Objects.requireNonNull;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.UniqueEventList;
+import seedu.address.model.person.FriendName;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.person.insights.PersonInsight;
/**
* Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
+ * Duplicates are not allowed for both persons and events (by .isSamePerson/.isSameEvent comparison)
*/
public class AddressBook implements ReadOnlyAddressBook {
private final UniquePersonList persons;
+ private final UniqueEventList events;
/*
* The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
@@ -25,38 +35,50 @@ public class AddressBook implements ReadOnlyAddressBook {
*/
{
persons = new UniquePersonList();
+ events = new UniqueEventList();
}
public AddressBook() {}
/**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
+ * Creates an AddressBook using the data in {@code toBeCopied}.
+ * Assumes that the given data is valid.
*/
public AddressBook(ReadOnlyAddressBook toBeCopied) {
this();
resetData(toBeCopied);
}
- //// list overwrite operations
+ //=========== List Override Operations ================================================
/**
* Replaces the contents of the person list with {@code persons}.
* {@code persons} must not contain duplicate persons.
*/
- public void setPersons(List persons) {
+ private void setPersons(List persons) {
this.persons.setPersons(persons);
}
+ /**
+ * Replaces the contents of the event list with {@code events}.
+ * {@code events} must not contain duplicate events.
+ */
+ private void setEvents(List events) {
+ this.events.setEvents(events);
+ }
+
/**
* Resets the existing data of this {@code AddressBook} with {@code newData}.
+ * Assumptions: Given data is valid.
*/
public void resetData(ReadOnlyAddressBook newData) {
requireNonNull(newData);
setPersons(newData.getPersonList());
+ setEvents(newData.getEventList());
}
- //// person-level operations
+ //=========== Person/Event Level Operations ================================================
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
@@ -83,6 +105,10 @@ public void setPerson(Person target, Person editedPerson) {
requireNonNull(editedPerson);
persons.setPerson(target, editedPerson);
+
+ if (!(target.hasSameName(editedPerson))) {
+ events.changeFriendName(target.getName(), editedPerson.getName());
+ }
}
/**
@@ -91,13 +117,92 @@ public void setPerson(Person target, Person editedPerson) {
*/
public void removePerson(Person key) {
persons.remove(key);
+ events.removeFriendName(key.getName());
+ }
+
+ /**
+ * Returns true if a event with the same identity as {@code event} exists in the address book.
+ */
+ public boolean hasEvent(Event event) {
+ requireNonNull(event);
+ return events.contains(event);
+ }
+
+ /**
+ * Adds an event to the address book.
+ * The event must not already exist in the address book and its friend names must be already in the address book.
+ */
+ public void addEvent(Event toAdd) {
+ requireNonNull(toAdd);
+ assert(areFriendNamesValid(toAdd));
+ alignFriendNames(toAdd);
+
+ events.add(toAdd);
+ }
+
+ /**
+ * Removes {@code key} from this {@code AddressBook}.
+ * {@code key} must exist in the address book.
+ */
+ public void removeEvent(Event key) {
+ events.remove(key);
+ }
+
+ /**
+ * Replaces the given event {@code target} in the list with {@code editedEvent}.
+ * {@code target} must exist in the address book.
+ * The identity of {@code editedEvent} must not be the same as another existing event in the address book.
+ * Also, the friend names of {@code editedEvent} must be already in the address book.
+ */
+ public void setEvent(Event target, Event editedEvent) {
+ requireNonNull(editedEvent);
+ assert(areFriendNamesValid(editedEvent));
+ alignFriendNames(editedEvent);
+
+ events.setEvent(target, editedEvent);
+ }
+
+ //=========== Person-Event Consistency Methods ================================================
+
+ /**
+ * Aligns event's friend names to be exactly the same as actual friend names, including capitalisation.
+ * Assumes that all event friend names are valid.
+ *
+ * @param event Event object containing friend names to be aligned.
+ */
+ private void alignFriendNames(Event event) {
+ requireNonNull(event);
+ List originalNames = new ArrayList<>(event.getFriendNames());
+
+ for (FriendName originalName : originalNames) {
+ FriendName actualName = persons.getExactName(originalName);
+ event.changeFriendNameIfPresent(originalName, actualName);
+ }
+ }
+
+ /**
+ * Returns true if all friend names in the given event correspond to actual Friends in the AddressBook.
+ *
+ * @param event Event to check friend names for validity.
+ * @return true if all friend names correspond to actual Friends in the AddressBook.
+ */
+ public boolean areFriendNamesValid(Event event) {
+ for (FriendName name : event.getFriendNames()) {
+ if (!persons.containsPersonWithName(name)) {
+ return false;
+ }
+ }
+ return true;
}
- //// util methods
+ //=========== Utility Methods ================================================
@Override
public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
+ final StringBuilder builder = new StringBuilder();
+ builder.append(persons.asUnmodifiableObservableList().size()).append(" persons");
+ builder.append(events.asUnmodifiableObservableList().size()).append(" events");
+ return builder.toString();
// TODO: refine later
}
@@ -106,15 +211,35 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ @Override
+ public ObservableList getEventList() {
+ return events.asUnmodifiableObservableList();
+ }
+
+
+ @Override
+ public ObservableList getInsightsList(Model model) {
+ return FXCollections.observableArrayList(this.getPersonList()
+ .stream()
+ .map(person -> new PersonInsight(person, model))
+ .sorted(Comparator.reverseOrder())
+ .collect(Collectors.toList()));
+ }
+
@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));
+ if (other == this) { // short circuit if same object
+ return true;
+ }
+ if (!(other instanceof AddressBook)) { // handles nulls
+ return false;
+ }
+ AddressBook otherBook = (AddressBook) other;
+ return persons.equals(otherBook.persons) && events.equals(otherBook.events);
}
@Override
public int hashCode() {
- return persons.hashCode();
+ return Objects.hash(persons, events);
}
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..ad148bb8220 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,7 +5,9 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
/**
* The API of the Model component.
@@ -13,6 +15,7 @@
public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true;
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -52,6 +55,8 @@ public interface Model {
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();
+ //=========== Person Operations =============================================================
+
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
@@ -76,12 +81,51 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
+ //=========== Event Operations =============================================================
+
+ /**
+ * Returns true if an event with the same identity as {@code event} exists in the address book.
+ */
+ boolean hasEvent(Event event);
+
+ /**
+ * Adds the given event.
+ * {@code event} must not already exist in the address book.
+ */
+ void addEvent(Event event);
+
+ /**
+ * Deletes the given event.
+ * The event must exist in the address book.
+ */
+ void deleteEvent(Event target);
+
+ void setEvent(Event target, Event editedPerson);
+
+ /**
+ * Returns true if all friend names in Event correspond to actual Friend objects.
+ */
+ boolean areEventFriendsValid(Event toAdd);
+
+ //=========== List Accessors =============================================================
+
/** Returns an unmodifiable view of the filtered person list */
ObservableList getFilteredPersonList();
+ ObservableList getInsightsList();
+
/**
* 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);
+
+ /** Returns an unmodifiable view of the filtered event list */
+ ObservableList getFilteredEventList();
+
+ /**
+ * Updates the filter of the filtered events list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredEventList(Predicate predicate);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..521a84376bd 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,7 +11,9 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
/**
* Represents the in-memory model of the address book data.
@@ -22,6 +24,7 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final FilteredList filteredEvents;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -29,11 +32,14 @@ public class ModelManager implements Model {
public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
requireAllNonNull(addressBook, userPrefs);
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
+ logger.fine("Initializing with address book: " + addressBook
+ + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredEvents = new FilteredList<>(this.addressBook.getEventList());
+ filteredEvents.setPredicate(Event::isAfterNow);
}
public ModelManager() {
@@ -87,6 +93,8 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ //=========== AddressBook - Person =======================================================================
+
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
@@ -95,11 +103,14 @@ public boolean hasPerson(Person person) {
@Override
public void deletePerson(Person target) {
+ requireNonNull(target);
addressBook.removePerson(target);
+ forceEventListUpdate();
}
@Override
public void addPerson(Person person) {
+ requireNonNull(person);
addressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}
@@ -107,11 +118,42 @@ public void addPerson(Person person) {
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
-
addressBook.setPerson(target, editedPerson);
+ forceEventListUpdate();
+ }
+
+ //=========== AddressBook - Event =======================================================================
+
+ @Override
+ public boolean hasEvent(Event event) {
+ requireNonNull(event);
+ return addressBook.hasEvent(event);
}
- //=========== Filtered Person List Accessors =============================================================
+ @Override
+ public void addEvent(Event event) {
+ addressBook.addEvent(event);
+ }
+
+ @Override
+ public void deleteEvent(Event target) {
+ addressBook.removeEvent(target);
+ }
+
+
+ @Override
+ public boolean areEventFriendsValid(Event toAdd) {
+ return addressBook.areFriendNamesValid(toAdd);
+ }
+
+ @Override
+ public void setEvent(Event target, Event editedEvent) {
+ requireAllNonNull(target, editedEvent);
+
+ addressBook.setEvent(target, editedEvent);
+ }
+
+ //=========== List Accessors ===========================================================================
/**
* Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
@@ -122,12 +164,39 @@ public ObservableList getFilteredPersonList() {
return filteredPersons;
}
+ @Override
+ public ObservableList getFilteredEventList() {
+ return filteredEvents;
+ }
+
+ @Override
+ public ObservableList getInsightsList() {
+ return this.addressBook.getInsightsList(this);
+ }
+
@Override
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
filteredPersons.setPredicate(predicate);
}
+ @Override
+ public void updateFilteredEventList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredEvents.setPredicate(predicate);
+ }
+
+ //=========== Misc ===========================================================================
+
+ /**
+ * Forces the view of the events list to be updated in the UI. Temporary workaround until
+ * we find a better way to do this - replace events in the event list perhaps?
+ * TODO Make sure it maintains previous filters when we implement find.
+ */
+ private void forceEventListUpdate() {
+ updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS);
+ }
+
@Override
public boolean equals(Object obj) {
// short circuit if same object
@@ -144,7 +213,7 @@ 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)
+ && filteredEvents.equals(other.filteredEvents);
}
-
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..d745391d6a6 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -1,7 +1,9 @@
package seedu.address.model;
import javafx.collections.ObservableList;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
/**
* Unmodifiable view of an address book
@@ -14,4 +16,12 @@ public interface ReadOnlyAddressBook {
*/
ObservableList getPersonList();
+ /**
+ * Returns an unmodifiable view of the events list.
+ * This list will not contain any duplicate events.
+ */
+ ObservableList getEventList();
+
+ ObservableList getInsightsList(Model model);
+
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..8fd8ba8a120 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -61,7 +61,7 @@ public boolean equals(Object other) {
if (other == this) {
return true;
}
- if (!(other instanceof UserPrefs)) { //this handles null as well.
+ if (!(other instanceof UserPrefs)) { // this handles null as well.
return false;
}
@@ -80,8 +80,7 @@ public int hashCode() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal address book file location : " + addressBookFilePath);
return sb.toString();
}
-
}
diff --git a/src/main/java/seedu/address/model/common/Description.java b/src/main/java/seedu/address/model/common/Description.java
new file mode 100644
index 00000000000..53fc489f771
--- /dev/null
+++ b/src/main/java/seedu/address/model/common/Description.java
@@ -0,0 +1,72 @@
+package seedu.address.model.common;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.Objects;
+
+
+/**
+ * Represents a Person's description in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)}
+ */
+public class Description {
+ public static final String MESSAGE_CONSTRAINTS = "Description can take any values, "
+ + "and it should not be empty if the /d flag has been input by user";
+
+ /*
+ * The first character of the description must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[^\\s][\\S\\s]*";
+
+ public final String value;
+
+ /**
+ * Constructs an {@code Description}.
+ *
+ * @param description A valid description.
+ */
+ public Description(String description) {
+ if (description == null) {
+ value = null;
+ } else {
+ checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS);
+ value = description;
+ }
+ }
+
+ /**
+ * Returns true if a given string is a valid description.
+ */
+ public static boolean isValidDescription(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof Description) {
+ if (value == null) {
+ return Objects.isNull(((Description) other).value);
+ } else if (Objects.isNull(((Description) other).value)) {
+ return false;
+ } else {
+ return value.equals(((Description) other).value);
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/common/Name.java b/src/main/java/seedu/address/model/common/Name.java
new file mode 100644
index 00000000000..3b63751f364
--- /dev/null
+++ b/src/main/java/seedu/address/model/common/Name.java
@@ -0,0 +1,43 @@
+package seedu.address.model.common;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class Name implements Comparable {
+
+ public final String fullName;
+
+ /**
+ * Constructs a {@code Name}
+ *
+ * @param name a valid name
+ */
+ public Name(String name) {
+ requireNonNull(name);
+ this.fullName = name;
+ }
+
+ /**
+ * Returns true if given {@code nameSubstring} is part of this {@code Name}.
+ * This method is case-insensitive.
+ *
+ * @param nameSubstring Name substring to match this Name against.
+ * @return True if given name substring is part of this Name.
+ */
+ public boolean containsIgnoreCase(Name nameSubstring) {
+ requireNonNull(nameSubstring);
+ return fullName.toLowerCase().contains(nameSubstring.fullName.toLowerCase());
+ }
+
+ public String toString() {
+ return fullName;
+ }
+
+ public int hashCode() {
+ return fullName.hashCode();
+ }
+
+ @Override
+ public int compareTo(Name name) {
+ return this.fullName.compareTo(name.fullName);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/DateTime.java b/src/main/java/seedu/address/model/event/DateTime.java
new file mode 100644
index 00000000000..3c62a900304
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/DateTime.java
@@ -0,0 +1,108 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+/**
+ * Represents an Event's date-time.
+ * Guarantees: immutable; is valid as declared in {@link #isValidDateTime(String)}
+ */
+public class DateTime implements Comparable {
+ public static final String MESSAGE_CONSTRAINTS = "The date & time needs to be in the following format: "
+ + "DD-MM-YYYY hhmm\n"
+ + "Tip: 0s can be omitted where ambiguity will not be created.\n"
+ + "e.g. 5-5-2021 instead of 05-05-2021 is a valid input.";
+ private static final DateTimeFormatter INPUT_FORMATTER =
+ DateTimeFormatter.ofPattern("d-M-yyyy Hmm");
+ private static final DateTimeFormatter OUTPUT_FORMATTER =
+ DateTimeFormatter.ofPattern("MMM d yyyy hh:mma");
+
+ private final LocalDateTime value; // please retrieve string via toInputFormat() instead.
+
+ /**
+ * Constructs an {@code DateTime}.
+ *
+ * @param dateTimeString A valid date-time String, in the format DD-MM-YYYY hhmm.
+ */
+ public DateTime(String dateTimeString) {
+ requireNonNull(dateTimeString);
+ checkArgument(isValidDateTime(dateTimeString), MESSAGE_CONSTRAINTS);
+ value = LocalDateTime.parse(dateTimeString, INPUT_FORMATTER);
+ }
+
+ /**
+ * Returns true if a given string is in a valid date time format.
+ *
+ * @param dateTimeString String to check validity for.
+ * @return True if the given string is in a valid date time format.
+ */
+ public static Boolean isValidDateTime(String dateTimeString) {
+ try {
+ LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, INPUT_FORMATTER);
+ } catch (DateTimeParseException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isAfterNow() {
+ return this.value.isAfter(LocalDateTime.now());
+ }
+
+ public boolean hasDateBefore(LocalDate date) {
+ return value.toLocalDate().isBefore(date);
+ }
+
+ public boolean hasDateAfter(LocalDate date) {
+ return value.toLocalDate().isAfter(date);
+ }
+
+ public boolean isBeforeNow() {
+ return this.value.isBefore(LocalDateTime.now());
+ }
+
+ public boolean hasSameDate(DateTime dateTime) {
+ return value.toLocalDate().equals(dateTime.value.toLocalDate());
+ }
+
+ /**
+ * Returns the dateTime in the correct input format for the DateTime constructor.
+ *
+ * @return dateTime in the correct input format for the DateTime constructor.
+ */
+ public String toInputFormat() {
+ return value.format(INPUT_FORMATTER);
+ }
+
+ @Override
+ public String toString() {
+ return value.format(OUTPUT_FORMATTER);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof DateTime)) {
+ return false;
+ }
+ DateTime otherDateTime = (DateTime) other;
+ return this.value.equals(otherDateTime.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public int compareTo(DateTime dateTime) {
+ return this.value.compareTo(dateTime.value);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/Event.java b/src/main/java/seedu/address/model/event/Event.java
new file mode 100644
index 00000000000..83e835f3153
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/Event.java
@@ -0,0 +1,228 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.model.common.Description;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents an Event in Amigos.
+ * Guarantees: fields are present and not null, field values are validated, immutable. (except for friendNames)
+ */
+public class Event implements Comparable {
+
+ // Identity fields
+ private final EventName name;
+ private final DateTime dateTime;
+
+ // Data fields
+ private final Description description;
+ private final HashSet friendNames = new HashSet<>(); // mutable to allow for easier updating
+
+ /**
+ * Constructor for event.
+ *
+ * @param name name of event
+ * @param description description of event
+ * @param dateTime date and time of event
+ * @param friendNames set of friend's Names linked with the event.
+ */
+ public Event(EventName name, DateTime dateTime, Description description, Set friendNames) {
+ requireAllNonNull(name, dateTime, description, friendNames);
+ this.name = name;
+ this.dateTime = dateTime;
+ this.description = description;
+ this.friendNames.addAll(friendNames);
+ }
+
+ public EventName getName() {
+ return name;
+ }
+
+ public DateTime getDateTime() {
+ return dateTime;
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns true if the given {@code nameSubstring} is contained within this event's name.
+ * Case-insensitive.
+ *
+ * @param nameSubstring Name substring to search for in this event's name.
+ * @return True if given name substring is contained within this event's name.
+ */
+ public boolean hasNameSubstring(EventName nameSubstring) {
+ requireNonNull(nameSubstring);
+ return getName().containsIgnoreCase(nameSubstring);
+ }
+
+ /**
+ * Returns true if the given {@code nameSubstring} is contained within this event's friend names.
+ * Case-insensitive.
+ *
+ * @param nameSubstring Substring to search for in this event's friend names.
+ * @return True if the given name substring is contained within this event's friend names.
+ */
+ public boolean hasFriendNameSubstring(FriendName nameSubstring) {
+ requireNonNull(nameSubstring);
+ return getFriendNames().stream().anyMatch(name -> name.containsIgnoreCase(nameSubstring));
+ }
+
+ /**
+ * Returns true if this event's date is before the given {@code date}.
+ *
+ * @param date Date to check this event's date against.
+ * @return True if this event's date is before the given date.
+ */
+ public boolean isBeforeDate(LocalDate date) {
+ requireNonNull(date);
+ return getDateTime().hasDateBefore(date);
+ }
+
+ /**
+ * Returns true if this event's date is after the given {@code date}.
+ *
+ * @param date Date to check this event's date against.
+ * @return True if this event's date is after the given date.
+ */
+ public boolean isAfterDate(LocalDate date) {
+ requireNonNull(date);
+ return getDateTime().hasDateAfter(date);
+ }
+
+ /**
+ * Returns true if this event's date and time is before the system's date and time.
+ *
+ * @return True if this event's date and time is before the system's date and time.
+ */
+ public boolean isBeforeNow() {
+ return getDateTime().isBeforeNow();
+ }
+
+ /**
+ * Returns true if this event's date and time is after the system's date and time.
+ *
+ * @return True if this event's date and time is after the system's date and time.
+ */
+ public boolean isAfterNow() {
+ return getDateTime().isAfterNow();
+ }
+
+ /**
+ * Changes the given FriendName from {@code original} to {@code replacement} if
+ * it is present in this Event's set of friend names.
+ *
+ * @param original Friend name to be changed.
+ * @param replacement Name to change the friend name to.
+ */
+ public void changeFriendNameIfPresent(FriendName original, FriendName replacement) {
+ if (getFriendNames().contains(original)) {
+ friendNames.remove(original);
+ friendNames.add(replacement);
+ }
+ }
+
+ /**
+ * Removes the given FriendName {@code toRemove} from the set of friend names in Event if present.
+ *
+ * @param toRemove Name of friend to remove from the Event if present.
+ */
+ public void removeFriendNameIfPresent(FriendName toRemove) {
+ if (getFriendNames().contains(toRemove)) {
+ friendNames.remove(toRemove);
+ }
+ }
+
+ /**
+ * Returns an immutable person set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getFriendNames() {
+ return Collections.unmodifiableSet(friendNames);
+ }
+
+ /**
+ * Returns true if the event has a friend with the same name as the specified person.
+ */
+ public boolean hasFriendWithName(Person person) {
+ requireNonNull(person);
+ return this.getFriendNames().stream().anyMatch(person::hasName);
+ }
+
+ /**
+ * Returns true if both events have the same name and date.
+ * This defines a weaker notion of equality between two events and is used to
+ * prevent duplication.
+ *
+ * @param otherEvent The other event to compare to.
+ * @return True if both events have the same name and date.
+ */
+ public boolean isSameEvent(Event otherEvent) {
+ if (otherEvent == this) {
+ return true;
+ }
+
+ return otherEvent != null
+ && otherEvent.getName().equals(getName())
+ && otherEvent.getDateTime().hasSameDate(getDateTime());
+ }
+
+ /**
+ * Returns true if both events have the exact same fields.
+ * This defines a stronger notion of equality between two events.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Event)) {
+ return false;
+ }
+
+ Event otherEvent = (Event) other;
+ return otherEvent.getName().equals(getName())
+ && otherEvent.getDateTime().equals(getDateTime())
+ && otherEvent.getDescription().equals(getDescription())
+ && otherEvent.getFriendNames().equals(getFriendNames());
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(name, dateTime, description, friendNames);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(getName())
+ .append("; DateTime: ")
+ .append(getDateTime())
+ .append("; Description: ")
+ .append(getDescription());
+ Set friendNames = getFriendNames();
+ if (!friendNames.isEmpty()) {
+ builder.append("; Friends: ");
+ friendNames.forEach(name -> builder.append(name).append(" "));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public int compareTo(Event o) {
+ return this.dateTime.compareTo(o.dateTime);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/EventDateIsAfterPredicate.java b/src/main/java/seedu/address/model/event/EventDateIsAfterPredicate.java
new file mode 100644
index 00000000000..15c41cb8ddb
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/EventDateIsAfterPredicate.java
@@ -0,0 +1,42 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.LocalDate;
+import java.util.function.Predicate;
+
+/**
+ * Tests that {@code Event}'s {@code date} is after the given date.
+ */
+public class EventDateIsAfterPredicate implements Predicate {
+ private final LocalDate date;
+
+ /**
+ * Creates an EventDateIsAfterPredicate object with the given date.
+ *
+ * @param date Date to check that event falls on or after.
+ */
+ public EventDateIsAfterPredicate(LocalDate date) {
+ requireNonNull(date);
+ this.date = date;
+ }
+
+ @Override
+ public boolean test(Event event) {
+ return event.isAfterDate(date);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EventDateIsAfterPredicate)) {
+ return false;
+ }
+
+ EventDateIsAfterPredicate otherPredicate = (EventDateIsAfterPredicate) other;
+ return otherPredicate.date.equals(date);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/EventDateIsBeforePredicate.java b/src/main/java/seedu/address/model/event/EventDateIsBeforePredicate.java
new file mode 100644
index 00000000000..0cd22b8950f
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/EventDateIsBeforePredicate.java
@@ -0,0 +1,42 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.LocalDate;
+import java.util.function.Predicate;
+
+/**
+ * Tests that {@code Event}'s {@code date} is before the given date.
+ */
+public class EventDateIsBeforePredicate implements Predicate {
+ private final LocalDate date;
+
+ /**
+ * Creates an EventDateIsBeforePredicate object with the given date.
+ *
+ * @param date Date to check that event falls on or before.
+ */
+ public EventDateIsBeforePredicate(LocalDate date) {
+ requireNonNull(date);
+ this.date = date;
+ }
+
+ @Override
+ public boolean test(Event event) {
+ return event.isBeforeDate(date);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EventDateIsBeforePredicate)) {
+ return false;
+ }
+
+ EventDateIsBeforePredicate otherPredicate = (EventDateIsBeforePredicate) other;
+ return otherPredicate.date.equals(date);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/EventFriendNamesContainSubstringPredicate.java b/src/main/java/seedu/address/model/event/EventFriendNamesContainSubstringPredicate.java
new file mode 100644
index 00000000000..fce8548de75
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/EventFriendNamesContainSubstringPredicate.java
@@ -0,0 +1,45 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Predicate;
+
+import seedu.address.model.person.FriendName;
+
+/**
+ * Tests that at least one of {@code Event}'s {@code friend names} contains the given substring. Matching is
+ * case-insensitive.
+ */
+public class EventFriendNamesContainSubstringPredicate implements Predicate {
+ private final FriendName nameSubstring;
+
+ /**
+ * Creates an EventNameContainsSubstringPredicate object with the given substring. If empty
+ * string provided, this will return true for all Events.
+ *
+ * @param nameSubstring Substring to check event friend names for.
+ */
+ public EventFriendNamesContainSubstringPredicate(FriendName nameSubstring) {
+ requireNonNull(nameSubstring);
+ this.nameSubstring = nameSubstring;
+ }
+
+ @Override
+ public boolean test(Event event) {
+ return event.hasFriendNameSubstring(nameSubstring);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EventFriendNamesContainSubstringPredicate)) {
+ return false;
+ }
+
+ EventFriendNamesContainSubstringPredicate otherPredicate = (EventFriendNamesContainSubstringPredicate) other;
+ return otherPredicate.nameSubstring.equals(nameSubstring);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/EventName.java b/src/main/java/seedu/address/model/event/EventName.java
new file mode 100644
index 00000000000..e8692b29e98
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/EventName.java
@@ -0,0 +1,46 @@
+package seedu.address.model.event;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import seedu.address.model.common.Name;
+
+/**
+ * Represents an Event's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidEventName(String)}
+ */
+public class EventName extends Name {
+
+ public static final String MESSAGE_CONSTRAINTS = "Event names should only contain alphanumeric characters, "
+ + "special characters and spaces, and it should not be blank or consisting only of whitespace.";
+
+ /*
+ * The first character of the name must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^[a-zA-Z0-9][^\\t\\n\\r\\f]*";
+
+ /**
+ * Constructs a {@code FriendName}.
+ *
+ * @param name A valid name.
+ */
+ public EventName(String name) {
+ super(name);
+ checkArgument(isValidEventName(name), MESSAGE_CONSTRAINTS);
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidEventName(String test) {
+ // Ensure in implementation Regex is not null
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof EventName // instanceof handles nulls
+ && fullName.equalsIgnoreCase(((EventName) other).fullName)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/EventNameContainsSubstringPredicate.java b/src/main/java/seedu/address/model/event/EventNameContainsSubstringPredicate.java
new file mode 100644
index 00000000000..fa1bf5c3df2
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/EventNameContainsSubstringPredicate.java
@@ -0,0 +1,43 @@
+package seedu.address.model.event;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Predicate;
+
+/**
+ * Tests that a {@code Event}'s {@code name} contains the given substring. Matching is
+ * case-insensitive.
+ */
+public class EventNameContainsSubstringPredicate implements Predicate {
+ private final EventName nameSubstring;
+
+ /**
+ * Creates an EventNameContainsSubstringPredicate object with the given substring. If empty
+ * string provided, this will return true for all Events.
+ *
+ * @param nameSubstring Substring to check event name for.
+ */
+ public EventNameContainsSubstringPredicate(EventName nameSubstring) {
+ requireNonNull(nameSubstring);
+ this.nameSubstring = nameSubstring;
+ }
+
+ @Override
+ public boolean test(Event event) {
+ return event.hasNameSubstring(nameSubstring);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EventNameContainsSubstringPredicate)) {
+ return false;
+ }
+
+ EventNameContainsSubstringPredicate otherPredicate = (EventNameContainsSubstringPredicate) other;
+ return otherPredicate.nameSubstring.equals(nameSubstring);
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/UniqueEventList.java b/src/main/java/seedu/address/model/event/UniqueEventList.java
new file mode 100644
index 00000000000..e96c639efef
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/UniqueEventList.java
@@ -0,0 +1,164 @@
+package seedu.address.model.event;
+
+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.event.exceptions.DuplicateEventException;
+import seedu.address.model.event.exceptions.EventNotFoundException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * A list of events that enforces uniqueness between its elements and does not allow nulls.
+ * An event is considered unique by comparing using {@code Event#isSameEvent(Event)}. As such, adding and updating of
+ * events uses Event#isSameEvent(Event) for equality so as to ensure that the event being added or updated is
+ * unique in terms of identity in the UniqueEventList. However, the removal of a event uses Event#equals(Object) so
+ * as to ensure that the event with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Event#isSameEvent(Event)
+ */
+public class UniqueEventList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent event as the given argument.
+ */
+ public boolean contains(Event toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameEvent);
+ }
+
+ /**
+ * Adds an event to the list.
+ * The event must not already exist in the list.
+ */
+ public void add(Event toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateEventException();
+ }
+ internalList.add(toAdd);
+ FXCollections.sort(internalList);
+ }
+
+
+ /**
+ * Removes the equivalent event from the list.
+ * The event must exist in the list.
+ */
+ public void remove(Event toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new EventNotFoundException();
+ }
+ FXCollections.sort(internalList);
+ }
+
+ /**
+ * Removes the given friend name {@code toRemove} from all Events in the list.
+ *
+ * @param toRemove Friend name to remove from all events.
+ */
+ public void removeFriendName(FriendName toRemove) {
+ requireNonNull(toRemove);
+ internalList.forEach(event -> event.removeFriendNameIfPresent(toRemove));
+ }
+
+ /**
+ * Changes the given Name from {@code original} to {@code replacement} in all Events in the list.
+ *
+ * @param original Friend name to be changed.
+ * @param replacement Friend name to change to.
+ */
+ public void changeFriendName(FriendName original, FriendName replacement) {
+ requireAllNonNull(original, replacement);
+ internalList.forEach(event -> event.changeFriendNameIfPresent(original, replacement));
+ }
+
+ /**
+ * Replaces the event {@code target} in the list with {@code editedEvent}.
+ * {@code target} must exist in the list.
+ * The person identity of {@code editedEvent} must not be the same as another existing event in the list.
+ */
+ public void setEvent(Event target, Event editedEvent) {
+ requireAllNonNull(target, editedEvent);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new EventNotFoundException();
+ }
+
+ if (!target.isSameEvent(editedEvent) && contains(editedEvent)) {
+ throw new DuplicateEventException();
+ }
+
+ internalList.set(index, editedEvent);
+ FXCollections.sort(internalList);
+ }
+
+ public void setEvents(UniqueEventList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code events}.
+ * {@code events} must not contain duplicate events.
+ */
+ public void setEvents(List events) {
+ requireAllNonNull(events);
+ if (!eventsAreUnique(events)) {
+ throw new DuplicateEventException();
+ }
+ internalList.setAll(events);
+ FXCollections.sort(internalList);
+ }
+
+ /**
+ * 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 UniqueEventList // instanceof handles nulls
+ && internalList.equals(((UniqueEventList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code events} contains only unique events.
+ * @param events
+ */
+ private boolean eventsAreUnique(List events) {
+ for (int i = 0; i < events.size() - 1; i++) {
+ for (int j = i + 1; j < events.size(); j++) {
+ if (events.get(i).isSameEvent(events.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java
new file mode 100644
index 00000000000..c8d487e6f2b
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.event.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Events (Events are considered duplicates if they have the same
+ * name and date).
+ */
+public class DuplicateEventException extends RuntimeException {
+ public DuplicateEventException() {
+ super("Operation would result in duplicate events");
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java
new file mode 100644
index 00000000000..5117db006ea
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.event.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified event.
+ */
+public class EventNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 60472ca22a0..ded2137c843 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -1,15 +1,18 @@
package seedu.address.model.person;
-import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.Objects;
+
+
/**
* Represents a Person's address in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
*/
public class Address {
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank";
+ public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, "
+ + "and it should not be empty if the /a flag has been entered by user. ";
/*
* The first character of the address must not be a whitespace,
@@ -25,9 +28,12 @@ public class Address {
* @param address A valid address.
*/
public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
+ if (address == null) {
+ value = null;
+ } else {
+ checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
+ value = address;
+ }
}
/**
@@ -44,9 +50,19 @@ public String toString() {
@Override
public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Address // instanceof handles nulls
- && value.equals(((Address) other).value)); // state check
+ if (other == this) {
+ return true;
+ } else if (other instanceof Address) {
+ if (value == null) {
+ return Objects.isNull(((Address) other).value);
+ } else if (Objects.isNull(((Address) other).value)) {
+ return false;
+ } else {
+ return value.equals(((Address) other).value);
+ }
+ } else {
+ return false;
+ }
}
@Override
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index f866e7133de..26cd5b3b69b 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -1,8 +1,9 @@
package seedu.address.model.person;
-import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.Objects;
+
/**
* Represents a Person's email in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
@@ -23,12 +24,13 @@ public class Email {
+ " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
// alphanumeric and special characters
private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore
- private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
+ private static final String ALPHANUMERIC_WITH_UNDERSCORE = "[^\\W]+"; // alphanumeric characters
+ private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_WITH_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]"
+ ALPHANUMERIC_NO_UNDERSCORE + ")*";
private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE
+ "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
- private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
+ private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)+" + DOMAIN_LAST_PART_REGEX;
public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
public final String value;
@@ -39,9 +41,12 @@ public class Email {
* @param email A valid email address.
*/
public Email(String email) {
- requireNonNull(email);
- checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
- value = email;
+ if (email == null) {
+ value = null;
+ } else {
+ checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS);
+ value = email;
+ }
}
/**
@@ -58,9 +63,19 @@ public String toString() {
@Override
public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Email // instanceof handles nulls
- && value.equals(((Email) other).value)); // state check
+ if (other == this) {
+ return true;
+ } else if (other instanceof Email) {
+ if (value == null) {
+ return Objects.isNull(((Email) other).value);
+ } else if (Objects.isNull(((Email) other).value)) {
+ return false;
+ } else {
+ return value.equals(((Email) other).value);
+ }
+ } else {
+ return false;
+ }
}
@Override
diff --git a/src/main/java/seedu/address/model/person/FriendFilterPredicate.java b/src/main/java/seedu/address/model/person/FriendFilterPredicate.java
new file mode 100644
index 00000000000..b8fe7ee7d85
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/FriendFilterPredicate.java
@@ -0,0 +1,71 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * Tests that a {@code Person}'s {@code name, tags, logs title} matches any of the keywords given.
+ */
+public class FriendFilterPredicate implements Predicate {
+ private final Set nameKeywords;
+ private final Set logTitleKeywords;
+ private final Set tagKeywords;
+
+ /**
+ * Constructs a FriendFilterPredicate based on the given keywords.
+ */
+ public FriendFilterPredicate(Set nameKeywords, Set logTitleKeywords, Set tagKeywords) {
+ requireAllNonNull(nameKeywords, logTitleKeywords, tagKeywords);
+ this.nameKeywords = nameKeywords;
+ this.logTitleKeywords = logTitleKeywords;
+ this.tagKeywords = tagKeywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+
+ //check if (substring) name of a person matches name keyword
+ boolean nameMatch = nameKeywords.stream().anyMatch(nameKeyword -> {
+ assert (!nameKeyword.equals(" ") && !nameKeyword.equals(""));
+ return containsIgnoreCase(person.getName().fullName, nameKeyword.fullName);
+ });
+
+ //check if (substring) logs title of a person matches keyword
+ boolean logTitleMatch = logTitleKeywords.stream().anyMatch(logTitleKeyword -> {
+ return person.getLogs().stream().anyMatch(log ->
+ containsIgnoreCase(log.getTitle().toString(), logTitleKeyword.fullName));
+ });
+
+ //check if (substring) tag of a person matches tag keyword
+ boolean tagMatch = tagKeywords.stream().anyMatch(tagKeyword -> {
+ return person.getTags().stream().anyMatch(tag ->
+ containsIgnoreCase(tag.tagName, tagKeyword.tagName));
+ });
+
+ return nameMatch || logTitleMatch || tagMatch;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FriendFilterPredicate // instanceof handles nulls
+ && nameKeywords.equals(((FriendFilterPredicate) other).nameKeywords)
+ && logTitleKeywords.equals(((FriendFilterPredicate) other).logTitleKeywords)
+ && tagKeywords.equals(((FriendFilterPredicate) other).tagKeywords)); // state check
+ }
+
+ /**
+ * Checks if a string contains a given substring.
+ */
+ private boolean containsIgnoreCase(String str, String substring) {
+ return str.toLowerCase(Locale.ROOT).contains(substring.toLowerCase(Locale.ROOT));
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/model/person/FriendName.java b/src/main/java/seedu/address/model/person/FriendName.java
new file mode 100644
index 00000000000..d19d9e97df2
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/FriendName.java
@@ -0,0 +1,50 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import seedu.address.model.common.Name;
+
+
+
+/**
+ * Represents a Friend's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidFriendName(String)}
+ */
+public class FriendName extends Name {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Friend names should only contain alphanumeric characters and spaces, and it should not be blank"
+ + " or consisting only of whitespace";
+
+ /*
+ * The first character of the name must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^[a-zA-Z][\\p{Alnum} ]*";
+
+ /**
+ * Constructs a {@code FriendName}.
+ *
+ * @param name A valid name.
+ */
+ public FriendName(String name) {
+ super(name);
+ checkArgument(isValidFriendName(name), MESSAGE_CONSTRAINTS);
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidFriendName(String test) {
+ // Ensure in implementation Regex is not null
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FriendName // instanceof handles nulls
+ && fullName
+ .equalsIgnoreCase(((FriendName) other).fullName));
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Log.java b/src/main/java/seedu/address/model/person/Log.java
new file mode 100644
index 00000000000..55e3bf2f550
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Log.java
@@ -0,0 +1,84 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.model.common.Description;
+
+
+/**
+ * Represents a note or log tied to a Person in the address book.
+ */
+public class Log {
+
+ // default values
+ public static final String DEFAULT_NO_DESCRIPTION = null;
+
+ // immutable attributes
+ private final LogName title;
+ private final Description description;
+
+ /**
+ * Constructs a Log object.
+ *
+ * @param title Title of the log.
+ * @param description Description tied to the log. Can be null.
+ */
+ public Log(String title, String description) {
+ requireNonNull(title);
+ this.title = new LogName(title);
+ this.description = new Description(description);
+ }
+
+ /**
+ * Constructs a Log object.
+ *
+ * @param title Title of the log.
+ * @param description Description tied to the log. Can be null.
+ */
+ public Log(LogName title, Description description) {
+ requireNonNull(title);
+ this.title = title;
+ this.description = description == null ? new Description(null) : description;
+ }
+
+ public Description getDescription() {
+ requireNonNull(this.description);
+ return this.description;
+ }
+
+ public LogName getTitle() {
+ requireNonNull(this.title);
+ return this.title;
+ }
+
+ /**
+ * Returns true if other Log is considered the same as this Log.
+ * By convention, we consider two Logs the same if their titles are the same.
+ *
+ * @param other The other log.
+ * @return True if other log is considered the same.
+ */
+ public boolean isSameLog(Log other) {
+ return this.getTitle().equals(other.getTitle());
+ }
+
+ @Override
+ public String toString() {
+ return this.title + "\n"
+ + (this.description.value == null ? "" : this.description + "\n");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Log
+ && (this.getTitle().equals(((Log) other).getTitle())) //getters ensure non-null
+ && (this.getDescription().equals(((Log) other).getDescription())));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.title.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/LogName.java b/src/main/java/seedu/address/model/person/LogName.java
new file mode 100644
index 00000000000..3780f31ff3b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/LogName.java
@@ -0,0 +1,50 @@
+package seedu.address.model.person;
+
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import seedu.address.model.common.Name;
+
+/**
+ * Represents a Person's log name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidLogName(String)}
+ */
+public class LogName extends Name {
+
+ public static final int TITLE_LENGTH_CONSTRAINT = 50;
+
+ public static final String MESSAGE_CONSTRAINTS = "Titles of logs must satisfy:\n"
+ + "1. Not be trivial (i.e. not empty or contain only spaces)\n"
+ + "2. Be at most " + TITLE_LENGTH_CONSTRAINT + " characters long. "
+ + "This is because of display limitations.";
+
+ /*
+ * The first character of the name must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^[\\p{Graph}][\\p{Alnum}\\p{Punct}\\s]{0," + (TITLE_LENGTH_CONSTRAINT - 1) + "}";
+
+ /**
+ * Constructs a {@code FriendName}.
+ *
+ * @param name A valid name.
+ */
+ public LogName(String name) {
+ super(name);
+ checkArgument(isValidLogName(name), MESSAGE_CONSTRAINTS);
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidLogName(String test) {
+ // Ensure in implementation Regex is not null
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof LogName // instanceof handles nulls
+ && fullName.equals(((LogName) other).fullName)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
deleted file mode 100644
index 79244d71cf7..00000000000
--- a/src/main/java/seedu/address/model/person/Name.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's name in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
- */
-public class Name {
-
- public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
-
- /*
- * The first character of the address must not be a whitespace,
- * otherwise " " (a blank string) becomes a valid input.
- */
- public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
-
- public final String fullName;
-
- /**
- * Constructs a {@code Name}.
- *
- * @param name A valid name.
- */
- public Name(String name) {
- requireNonNull(name);
- checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
- fullName = name;
- }
-
- /**
- * Returns true if a given string is a valid name.
- */
- public static boolean isValidName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
-
- @Override
- public String toString() {
- return fullName;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Name // instanceof handles nulls
- && fullName.equals(((Name) other).fullName)); // state check
- }
-
- @Override
- public int hashCode() {
- return fullName.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
deleted file mode 100644
index c9b5868427c..00000000000
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-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 Name} matches any of the keywords given.
- */
-public class NameContainsKeywordsPredicate implements Predicate {
- private final List keywords;
-
- public NameContainsKeywordsPredicate(List keywords) {
- this.keywords = keywords;
- }
-
- @Override
- public boolean test(Person person) {
- return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls
- && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 8ff1d83fe89..ae45ea2211e 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -1,42 +1,121 @@
package seedu.address.model.person;
+import static java.util.Objects.isNull;
+import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
+import seedu.address.model.common.Description;
+import seedu.address.model.common.Name;
import seedu.address.model.tag.Tag;
/**
* Represents a Person in the address book.
* Guarantees: details are present and not null, field values are validated, immutable.
*/
-public class Person {
+public class Person implements Comparable {
// Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
+ private final FriendName name;
// Data fields
+ private final Phone phone;
+ private final Email email;
private final Address address;
+ private final Description description;
private final Set tags = new HashSet<>();
+ private final UniqueLogList logs = new UniqueLogList();
+
+ /**
+ * Constructs a person.
+ *
+ * @param name Name of the person. Compulsory.
+ * @param phone Phone object of the person. If null, default to no phone.
+ * @param email Phone object of the person. If null, default to no email.
+ * @param address Phone object of the person. If null, default to no address.
+ * @param description Phone object of the person. If null, default to no description.
+ * @param tags Set of tags of the person. If null, default to no tags.
+ * @param logs Log list of the person. if null, default to no logs.
+ */
+ public Person(FriendName name, Phone phone, Email email, Address address, Description description, Set tags,
+ List logs) {
+ requireAllNonNull(name);
+ this.name = name;
+ this.phone = isNull(phone) ? new Phone(null) : phone;
+ this.email = isNull(email) ? new Email(null) : email;
+ this.address = isNull(address) ? new Address(null) : address;
+ this.description = isNull(description) ? new Description(null) : description;
+ this.tags.addAll(isNull(tags) ? new HashSet<>() : tags);
+ this.logs.setLogs(isNull(logs) ? new ArrayList<>() : logs);
+ requireAllNonNull(this.name, this.phone, this.email,
+ this.address, this.description, this.tags, this.logs);
+ }
+
+ /**
+ * Overloaded constructor for person without tags and logs.
+ */
+ public Person(FriendName name, Phone phone, Email email, Address address) {
+ requireAllNonNull(name, phone, email, address);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.description = new Description(null);
+ this.tags.addAll(new HashSet<>());
+ this.logs.setLogs(new ArrayList<>());
+ }
+
/**
- * Every field must be present and not null.
+ * Overloaded method to construct a person with only a name.
+ *
+ * @param name Name of the person. Compulsory.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(FriendName name) {
+ this.name = name;
+ this.phone = new Phone(null);
+ this.email = new Email(null);
+ this.address = new Address(null);
+ this.description = new Description(null);
+ this.tags.addAll(new HashSet<>());
+ this.logs.setLogs(new ArrayList<>());
+ }
+
+ /**
+ * Overloaded constructor for person with no logs.
+ */
+ public Person(FriendName name, Phone phone, Email email, Address address, Description description, Set tags) {
+ requireAllNonNull(name, phone, address, description, tags);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.description = description;
this.tags.addAll(tags);
+ this.logs.setLogs(new ArrayList<>());
}
- public Name getName() {
+ /**
+ * Overloaded constructor for person with no description.
+ */
+ public Person(FriendName name, Phone phone, Email email, Address address, Set tags, List logs) {
+ requireAllNonNull(name, phone, email, address, tags, logs);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.description = new Description(null);
+ this.tags.addAll(tags);
+ this.logs.setLogs(logs);
+ }
+
+ public FriendName getName() {
return name;
}
@@ -52,6 +131,10 @@ public Address getAddress() {
return address;
}
+ public Description getDescription() {
+ return description;
+ }
+
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
@@ -60,9 +143,21 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ public List getLogs() {
+ return this.logs.asUnmodifiableObservableList();
+ }
+
+ public boolean containsLog(Log log) {
+ return this.logs.contains(log);
+ }
+
+ public boolean containsLogExactly(Log log) {
+ return this.logs.containsExactly(log);
+ }
+
/**
- * Returns true if both persons have the same name.
- * This defines a weaker notion of equality between two persons.
+ * Returns true if both persons are the same, which we define to be
+ * having the same name.
*/
public boolean isSamePerson(Person otherPerson) {
if (otherPerson == this) {
@@ -73,6 +168,21 @@ public boolean isSamePerson(Person otherPerson) {
&& otherPerson.getName().equals(getName());
}
+ /**
+ * Returns true if both persons explicitly have the same name.
+ */
+ public boolean hasSameName(Person otherPerson) {
+ return this.name.equals(otherPerson.name);
+ }
+
+ /**
+ * Returns true if the person has a name equal to the specified name.
+ */
+ public boolean hasName(Name name) {
+ requireNonNull(name);
+ return this.name.equals(name);
+ }
+
/**
* Returns true if both persons have the same identity and data fields.
* This defines a stronger notion of equality between two persons.
@@ -92,32 +202,42 @@ public boolean equals(Object other) {
&& otherPerson.getPhone().equals(getPhone())
&& otherPerson.getEmail().equals(getEmail())
&& otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
+ && otherPerson.getDescription().equals(getDescription())
+ && otherPerson.getTags().equals(getTags())
+ && otherPerson.getLogs().equals(getLogs());
}
@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, description, tags, logs);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(getName())
- .append("; Phone: ")
- .append(getPhone())
- .append("; Email: ")
- .append(getEmail())
- .append("; Address: ")
- .append(getAddress());
+ .append(getPhone().value == null ? "" : ("; Phone: " + getPhone()))
+ .append(getEmail().value == null ? "" : ("; Email: " + getEmail()))
+ .append(getAddress().value == null ? "" : ("; Address: " + getAddress()))
+ .append(getDescription().value == null ? ""
+ : ("; Description: " + getDescription()));
Set tags = getTags();
if (!tags.isEmpty()) {
builder.append("; Tags: ");
tags.forEach(builder::append);
}
+ List logs = this.getLogs();
+ if (!logs.isEmpty()) {
+ builder.append(": Logs: \n");
+ logs.forEach(builder::append);
+ }
return builder.toString();
}
+ @Override
+ public int compareTo(Person o) {
+ return this.name.compareTo(o.name);
+ }
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index 872c76b382f..1db763a96ea 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -1,8 +1,9 @@
package seedu.address.model.person;
-import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.Objects;
+
/**
* Represents a Person's phone number in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
@@ -21,9 +22,12 @@ public class Phone {
* @param phone A valid phone number.
*/
public Phone(String phone) {
- requireNonNull(phone);
- checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
- value = phone;
+ if (phone == null) {
+ value = null;
+ } else {
+ checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
+ value = phone;
+ }
}
/**
@@ -40,9 +44,19 @@ public String toString() {
@Override
public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Phone // instanceof handles nulls
- && value.equals(((Phone) other).value)); // state check
+ if (other == this) {
+ return true;
+ } else if (other instanceof Phone) {
+ if (value == null) {
+ return Objects.isNull(((Phone) other).value);
+ } else if (Objects.isNull(((Phone) other).value)) {
+ return false;
+ } else {
+ return value.equals(((Phone) other).value);
+ }
+ } else {
+ return false;
+ }
}
@Override
diff --git a/src/main/java/seedu/address/model/person/UniqueLogList.java b/src/main/java/seedu/address/model/person/UniqueLogList.java
new file mode 100644
index 00000000000..0d1ebb8a8fc
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/UniqueLogList.java
@@ -0,0 +1,132 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.person.exceptions.DuplicateLogException;
+import seedu.address.model.person.exceptions.LogNotFoundException;
+
+/**
+ * A list of Logs that enforces the uniqueness between its elements and does not
+ * allow nulls.
+ *
+ *
A log is considered unique by comparing {@code Log#isSameLog(Log)}. As such, adding and updating
+ * of Logs uses Log#isSameLog(Log) for equality to ensure that the Log being added or updated is unique
+ * in terms of "identity" in the UniqueLogList.Deleting of Logs uses Log#equals(Object) for equality to ensure
+ * that the Log being deleted is exactly correct.
+ *
+ *
Supports a minimal set of list operations.
+ *
+ * @see Log#isSameLog(Log)
+ */
+public class UniqueLogList {
+
+ // observable list allows Java FX listeners to track changes
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent log as the given argument.
+ */
+ public boolean contains(Log log) {
+ requireAllNonNull(log);
+ return this.internalList.stream().anyMatch(log::isSameLog);
+ }
+
+ /**
+ * Returns true if the list contains a log that matches exactly the
+ * given {@code Log}. A stronger form of contains.
+ */
+ public boolean containsExactly(Log log) {
+ requireAllNonNull(log);
+ return this.internalList.stream().anyMatch(log::equals);
+ }
+
+ /**
+ * Adds a log to the list.
+ * An identical log must not already exist in the list.
+ */
+ public void addLog(Log log) {
+ requireNonNull(log);
+ if (this.contains(log)) {
+ throw new DuplicateLogException();
+ }
+ internalList.add(log);
+ }
+
+ /**
+ * Replaces the {@code target} in the list with the {@code editedLog}.
+ * The {@code target} log must exist in the list, and the identity of the {@code editedLog}
+ * must be different from all existing logs in the list.
+ */
+ public void setLog(Log targetLog, Log editedLog) {
+ requireAllNonNull(targetLog, editedLog);
+
+ if (!targetLog.isSameLog(editedLog) && this.contains(editedLog)) { // trying to insert something already inside
+ throw new DuplicateLogException();
+ }
+
+ int index = this.internalList.indexOf(targetLog);
+ if (index == -1) {
+ throw new LogNotFoundException();
+ }
+
+ this.internalList.set(index, editedLog);
+ }
+
+ /**
+ * Removes the equivalent log from the list.
+ * The log must exist in the list.
+ */
+ public void removeLog(Log toRemove) {
+ requireNonNull(toRemove);
+ if (!this.internalList.remove(toRemove)) {
+ throw new LogNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the contents of the list with all of {@code logs}.
+ * {@code logs} must not contain duplicate log.
+ */
+ public void setLogs(List logs) {
+ requireAllNonNull(logs);
+ if (!this.logsAreUnique(logs)) {
+ throw new DuplicateLogException();
+ }
+ this.internalList.setAll(logs);
+ }
+
+ /**
+ * Returns true if and only if {@code logs} contains only unique (by title) logs.
+ */
+ public boolean logsAreUnique(List logs) {
+ int n = logs.size();
+ for (int i = 0; i < n - 1; i++) { // iterate like N choose 2
+ for (int j = i + 1; j < n; j++) {
+ if (logs.get(i).isSameLog(logs.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the list of logs as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return this.internalUnmodifiableList;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.internalList.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index 0fee4fe57e6..2c66438438d 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -36,6 +36,14 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns true if the list contains a person with the given name.
+ */
+ public boolean containsPersonWithName(FriendName toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(person -> person.hasName(toCheck));
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
@@ -46,6 +54,7 @@ public void add(Person toAdd) {
throw new DuplicatePersonException();
}
internalList.add(toAdd);
+ FXCollections.sort(internalList);
}
/**
@@ -66,6 +75,7 @@ public void setPerson(Person target, Person editedPerson) {
}
internalList.set(index, editedPerson);
+ FXCollections.sort(internalList);
}
/**
@@ -77,6 +87,7 @@ public void remove(Person toRemove) {
if (!internalList.remove(toRemove)) {
throw new PersonNotFoundException();
}
+ FXCollections.sort(internalList);
}
public void setPersons(UniquePersonList replacement) {
@@ -95,6 +106,26 @@ public void setPersons(List persons) {
}
internalList.setAll(persons);
+ FXCollections.sort(internalList);
+ }
+
+ /**
+ * Return the exact name used by a person in the list that matches the given name.
+ * Assumes that the given friend name matches the name of a person in the list.
+ *
+ * @param name Name to match with.
+ * @return Exact name used by a person in the list that matches the given name.
+ * @throws PersonNotFoundException If no person in the list is found matching the given name.
+ */
+ public FriendName getExactName(FriendName name) {
+ requireNonNull(name);
+
+ for (Person person : internalList) {
+ if (person.hasName(name)) {
+ return person.getName();
+ }
+ }
+ throw new PersonNotFoundException();
}
/**
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicateLogException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicateLogException.java
new file mode 100644
index 00000000000..8354adc32a4
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/DuplicateLogException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
+ * identity).
+ */
+public class DuplicateLogException extends RuntimeException {
+ public DuplicateLogException() {
+ super("Operation would result in duplicate logs");
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/LogNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/LogNotFoundException.java
new file mode 100644
index 00000000000..a6f1b636d93
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/LogNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified log.
+ */
+public class LogNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/person/insights/Insights.java b/src/main/java/seedu/address/model/person/insights/Insights.java
new file mode 100644
index 00000000000..b3b7dca6fc5
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/insights/Insights.java
@@ -0,0 +1,63 @@
+package seedu.address.model.person.insights;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+
+/**
+ * Encapsulates an insight.
+ */
+public class Insights {
+
+ public abstract static class Insight {
+
+ // instantiate flag
+ private boolean initialized = false;
+
+ /**
+ * Returns an insight given a {@code Person} and {@code Model}.
+ */
+ public abstract Insight getInsight(Person person, Model model);
+
+ /**
+ * Returns the string representation to be shown in the UI.
+ */
+ public abstract String getAsString();
+
+ protected void markInitialized() {
+ this.initialized = true;
+ }
+
+ protected boolean isInitialized() {
+ return this.initialized;
+ }
+ }
+
+ /**
+ * Statically constructs a MostRecentEventInsight object.
+ */
+ public static MostRecentEventInsight createMostRecentEventInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ return new MostRecentEventInsight().getInsight(person, model);
+ }
+
+ /**
+ * Constructs a NumEventsInsight object.
+ */
+ public static NumEventsInsight createNumEventsInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ return new NumEventsInsight().getInsight(person, model);
+ }
+
+ /**
+ * Statically constructs a NumLogsInsight object.
+ */
+ public static NumLogsInsight createNumLogsInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ return new NumLogsInsight().getInsight(person, model);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/model/person/insights/MostRecentEventInsight.java b/src/main/java/seedu/address/model/person/insights/MostRecentEventInsight.java
new file mode 100644
index 00000000000..1e942204e0c
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/insights/MostRecentEventInsight.java
@@ -0,0 +1,115 @@
+package seedu.address.model.person.insights;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import seedu.address.model.Model;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.Insights.Insight;
+
+/**
+ * This class encapsulates the insight of the most recent event.
+ */
+public class MostRecentEventInsight extends Insight implements Comparable {
+
+
+ // constants
+ public static final String DEFAULT_NO_EVENT_MESSAGE = "Most recent event: Never had an event (yet)!";
+ public static final String DEFAULT_HAS_EVENT_PREFIX = "Most recent event: ";
+
+ // data field
+ private final DateTime dateTime;
+ private final boolean hasAtLeastOneEvent;
+
+ private MostRecentEventInsight(boolean hasAtLeastOneEvent) {
+ assert(!hasAtLeastOneEvent);
+ this.hasAtLeastOneEvent = false;
+ this.dateTime = null;
+ super.markInitialized();
+ }
+
+ private MostRecentEventInsight(DateTime dateTime) {
+ requireNonNull(dateTime);
+ this.dateTime = dateTime;
+ this.hasAtLeastOneEvent = true;
+ super.markInitialized();
+ }
+
+ protected MostRecentEventInsight() {
+ this.dateTime = null;
+ this.hasAtLeastOneEvent = false;
+ };
+
+
+ @Override
+ public MostRecentEventInsight getInsight(Person person, Model model) {
+
+ // sanity check
+ requireAllNonNull(person, model);
+
+ // get event
+ List eventsWithPerson = model.getAddressBook()
+ .getEventList()
+ .stream()
+ .filter(event -> event.hasFriendWithName(person))
+ .filter(event -> event.getDateTime().isBeforeNow()) // only past events
+ .collect(Collectors.toList());
+
+ // if have past event, update that
+ if (eventsWithPerson.size() > 0) {
+ eventsWithPerson.sort(Event::compareTo);
+ DateTime dateTime = eventsWithPerson.get(eventsWithPerson.size() - 1).getDateTime();
+ assert (dateTime != null);
+ return new MostRecentEventInsight(dateTime);
+ }
+ return new MostRecentEventInsight(false);
+ }
+
+ @Override
+ public String getAsString() {
+ assert(super.isInitialized());
+ if (!this.hasAtLeastOneEvent) {
+ return DEFAULT_NO_EVENT_MESSAGE;
+ }
+ assert (this.dateTime != null);
+ return "Most recent event: " + this.dateTime.toString();
+ }
+
+ @Override
+ public int compareTo(MostRecentEventInsight other) {
+ if (this.dateTime == null && other.dateTime != null) {
+ return -1;
+ } else if (other.dateTime == null && this.dateTime != null) {
+ return 1;
+ } else if (other.dateTime == null && this.dateTime == null) {
+ return 0;
+ }
+ return this.dateTime.compareTo(other.dateTime);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof MostRecentEventInsight) {
+ MostRecentEventInsight otherInsight = (MostRecentEventInsight) other;
+ if ((otherInsight.dateTime == null) && (this.dateTime == null)) {
+ return true;
+ } else if ((otherInsight.dateTime == null) ^ (this.dateTime == null)) {
+ return false;
+ }
+ return this.dateTime.equals(((MostRecentEventInsight) other).dateTime);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return this.getAsString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/insights/NumEventsInsight.java b/src/main/java/seedu/address/model/person/insights/NumEventsInsight.java
new file mode 100644
index 00000000000..19634308d07
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/insights/NumEventsInsight.java
@@ -0,0 +1,68 @@
+package seedu.address.model.person.insights;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.Insights.Insight;
+
+
+/**
+ * This class encapsulates the number of events a person participates in as an insight.
+ */
+public class NumEventsInsight extends Insight implements Comparable {
+
+ public static final String DEFAULT_HAS_EVENT_PREFIX = "Number of events: ";
+
+ private final int number;
+
+ private NumEventsInsight(int number) {
+ this.number = number;
+ super.markInitialized();
+ }
+
+ protected NumEventsInsight() {
+ this.number = -1;
+ }
+
+ /**
+ * Constructs a NumEventsInsight object.
+ */
+
+ @Override
+ public NumEventsInsight getInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ int numberOfEvents = (int) model.getAddressBook()
+ .getEventList()
+ .stream()
+ .filter(event -> event.hasFriendWithName(person))
+ .count();
+ return new NumEventsInsight(numberOfEvents);
+ }
+
+ @Override
+ public String getAsString() {
+ assert(super.isInitialized());
+ return DEFAULT_HAS_EVENT_PREFIX + this.number;
+ }
+
+ @Override
+ public int compareTo(NumEventsInsight other) {
+ return this.number - other.number;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof NumEventsInsight) {
+ return this.number == ((NumEventsInsight) other).number;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return this.getAsString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/insights/NumLogsInsight.java b/src/main/java/seedu/address/model/person/insights/NumLogsInsight.java
new file mode 100644
index 00000000000..95a7f9a36da
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/insights/NumLogsInsight.java
@@ -0,0 +1,62 @@
+package seedu.address.model.person.insights;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.Insights.Insight;
+
+
+/**
+ * This class encapsulates an insight that reflects the number of logs
+ * a person has.
+ */
+public class NumLogsInsight extends Insight implements Comparable {
+
+ public static final String DEFAULT_HAS_LOG_PREFIX = "Number of logs: ";
+
+ private final int number;
+
+ protected NumLogsInsight() {
+ // dummy
+ this.number = -1;
+ }
+
+ private NumLogsInsight(int number) {
+ this.number = number;
+ super.markInitialized();
+ }
+
+
+ @Override
+ public NumLogsInsight getInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ return new NumLogsInsight(person.getLogs().size());
+ }
+
+ @Override
+ public String getAsString() {
+ assert(super.isInitialized());
+ return DEFAULT_HAS_LOG_PREFIX + this.number;
+ }
+
+ @Override
+ public int compareTo(NumLogsInsight other) {
+ return this.number - other.number;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof NumLogsInsight) {
+ return this.number == ((NumLogsInsight) other).number;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return this.getAsString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/insights/PersonInsight.java b/src/main/java/seedu/address/model/person/insights/PersonInsight.java
new file mode 100644
index 00000000000..838ddbbd31f
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/insights/PersonInsight.java
@@ -0,0 +1,68 @@
+package seedu.address.model.person.insights;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+
+/**
+ * This class encapsulates insights of a person.
+ */
+public class PersonInsight implements Comparable {
+
+ // Data fields
+ private final Person person;
+ private final NumLogsInsight numberOfLogs;
+ private final NumEventsInsight numberOfEvents;
+ private final MostRecentEventInsight lastEvent;
+
+ /**
+ * Constructs a PersonInsight object.
+ */
+ public PersonInsight(Person person, Model model) {
+ requireAllNonNull(person, model);
+ this.person = person;
+ this.numberOfEvents = Insights.createNumEventsInsight(person, model);
+ this.numberOfLogs = Insights.createNumLogsInsight(person, model);
+ this.lastEvent = Insights.createMostRecentEventInsight(person, model);
+
+ }
+
+ public String getNumLogsInsightAsString() {
+ return this.numberOfLogs.getAsString();
+ }
+
+ public String getNumEventsInsightAsString() {
+ return this.numberOfEvents.getAsString();
+ }
+
+ public String getLastEventInsightAsString() {
+ return this.lastEvent.getAsString();
+ }
+
+ public Person getPerson() {
+ return this.person;
+ }
+
+ @Override
+ public int compareTo(PersonInsight other) {
+ return this.numberOfEvents.compareTo(other.numberOfEvents)
+ + this.numberOfLogs.compareTo(other.numberOfLogs)
+ + this.lastEvent.compareTo(other.lastEvent);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else {
+ PersonInsight o = (PersonInsight) other;
+ return (this.lastEvent.equals(o.lastEvent)
+ && this.numberOfEvents.equals(o.numberOfEvents)
+ && this.numberOfLogs.equals(o.numberOfLogs)
+ && this.person.equals(o.person));
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..f17bdf4b9df 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -6,9 +6,13 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.common.Description;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventName;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
+import seedu.address.model.person.FriendName;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -17,26 +21,39 @@
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
public class SampleDataUtil {
+
public static Person[] getSamplePersons() {
return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
+ new Person(new FriendName("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
+ new Address("Blk 30 Geylang Street 29, #06-40"), new Description("Friends since high school"),
+ getTagSet("friends"), null),
+ new Person(new FriendName("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Description("Friday lunch buddy"),
+ getTagSet("colleagues", "friends"), null),
+ new Person(new FriendName("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Description("Deskmate in CS1010S"),
+ getTagSet("neighbours"), null),
+ new Person(new FriendName("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Description("Loves to steal my food"),
+ getTagSet("family"), null),
+ new Person(new FriendName("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
+ new Address("Blk 47 Tampines Street 20, #17-35"), new Description("Friends since junior college"),
+ getTagSet("classmates"), null),
+ new Person(new FriendName("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"), new Description("Takes the same bus as me"),
+ getTagSet("colleagues"), null)
+ };
+ }
+
+ public static Event[] getSampleEvents() {
+ return new Event[] {
+ new Event(new EventName("Alex's Birthday"), new DateTime("10-03-2022 1430"),
+ new Description("Remember to bring cake."), getNameSet("Alex Yeoh", "David Li")),
+ new Event(new EventName("Weekends at Bernice's"), new DateTime("10-04-2022 1835"),
+ new Description(null), getNameSet("Bernice Yu")),
+ new Event(new EventName("Boardgame Night"), new DateTime("12-04-2022 1945"),
+ new Description("We will be playing Monopoly."),
+ getNameSet("Roy Balakrishnan", "Irfan Ibrahim"))
};
}
@@ -45,6 +62,9 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
for (Person samplePerson : getSamplePersons()) {
sampleAb.addPerson(samplePerson);
}
+ for (Event sampleEvent : getSampleEvents()) {
+ sampleAb.addEvent(sampleEvent);
+ }
return sampleAb;
}
@@ -57,4 +77,12 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ /**
+ * Returns a name set containing the list of strings given.
+ */
+ public static Set getNameSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(FriendName::new)
+ .collect(Collectors.toSet());
+ }
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedEvent.java b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java
new file mode 100644
index 00000000000..30e4003f68b
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedEvent.java
@@ -0,0 +1,94 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.common.Description;
+import seedu.address.model.event.DateTime;
+import seedu.address.model.event.Event;
+import seedu.address.model.event.EventName;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Jackson-friendly version of {@link Event}.
+ */
+class JsonAdaptedEvent {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!";
+
+ private final String name;
+ private final String dateTime;
+ private final String description;
+ private final List friendNames = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedEvent} with the given event details.
+ */
+ @JsonCreator
+ public JsonAdaptedEvent(@JsonProperty("name") String name, @JsonProperty("dateTime") String dateTime,
+ @JsonProperty("description") String description,
+ @JsonProperty("friendNames") List friendNames) {
+ this.name = name;
+ this.dateTime = dateTime;
+ this.description = description;
+ if (friendNames != null) {
+ this.friendNames.addAll(friendNames);
+ }
+ }
+
+ /**
+ * Converts a given {@code Event} into this class for Jackson use.
+ */
+ public JsonAdaptedEvent(Event source) {
+ name = source.getName().fullName;
+ dateTime = source.getDateTime().toInputFormat();
+ description = source.getDescription().value;
+ friendNames.addAll(source.getFriendNames().stream()
+ .map(JsonAdaptedName::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted event object into the model's {@code Event} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted event.
+ */
+ public Event toModelType() throws IllegalValueException {
+ final List eventFriendNames = new ArrayList<>();
+ for (JsonAdaptedName friendName : friendNames) {
+ eventFriendNames.add(friendName.toModelType());
+ }
+ final Set modelFriendNames = new HashSet<>(eventFriendNames);
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, EventName.class.getSimpleName()));
+ }
+ if (!EventName.isValidEventName(name)) {
+ throw new IllegalValueException(EventName.MESSAGE_CONSTRAINTS);
+ }
+ final EventName modelName = new EventName(name);
+
+ if (dateTime == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ DateTime.class.getSimpleName()));
+ }
+ if (!DateTime.isValidDateTime(dateTime)) {
+ throw new IllegalValueException(DateTime.MESSAGE_CONSTRAINTS);
+ }
+ final DateTime modelDateTime = new DateTime(dateTime);
+
+ if (description != null && !Description.isValidDescription(description)) {
+ throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS);
+ }
+ final Description modelDescription = new Description(description);
+
+ return new Event(modelName, modelDateTime, modelDescription, modelFriendNames);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedLog.java b/src/main/java/seedu/address/storage/JsonAdaptedLog.java
new file mode 100644
index 00000000000..ad464c9ab45
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedLog.java
@@ -0,0 +1,48 @@
+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.Log;
+import seedu.address.model.person.LogName;
+
+
+/**
+ * Jackson-friendly version of {@link Log}
+ */
+public class JsonAdaptedLog {
+ private final String title;
+ private final String description;
+
+ /**
+ * Constructs a {@code JsonAdaptedLog} with the given {@code title} and {@code desccription}.
+ * Assumes that title and description are both valid and non-null.
+ */
+ @JsonCreator
+ public JsonAdaptedLog(@JsonProperty("title") String title, @JsonProperty("description") String description) {
+ this.title = title;
+ this.description = description;
+ }
+
+ /**
+ * Constructs a {@code JsonAdaptedLog} with the given {@code Log}.
+ */
+ public JsonAdaptedLog(Log log) {
+ this.title = log.getTitle().toString();
+ this.description = log.getDescription().toString();
+ }
+
+ /**
+ * Converts a {@code JsonAdoptedLog} to a {@code Log} object.
+ *
+ * @throws IllegalValueException if constructed {@code Log} contains
+ * illegal values.
+ */
+ public Log toModelType() throws IllegalValueException {
+ if (!LogName.isValidLogName(this.title)) {
+ throw new IllegalValueException(LogName.MESSAGE_CONSTRAINTS);
+ }
+ return new Log(this.title, this.description);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedName.java b/src/main/java/seedu/address/storage/JsonAdaptedName.java
new file mode 100644
index 00000000000..96a1ccc1feb
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedName.java
@@ -0,0 +1,47 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.FriendName;
+
+/**
+ * Jackson-friendly version of {@link FriendName}.
+ */
+class JsonAdaptedName {
+
+ private final String name;
+
+ /**
+ * Constructs a {@code JsonAdaptedName} with the given {@code name}.
+ */
+ @JsonCreator
+ public JsonAdaptedName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Converts a given {@code Name} into this class for Jackson use.
+ */
+ public JsonAdaptedName(FriendName source) {
+ name = source.fullName;
+ }
+
+ @JsonValue
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted name object into the model's {@code Name} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted name.
+ */
+ public FriendName toModelType() throws IllegalValueException {
+ if (!FriendName.isValidFriendName(name)) {
+ throw new IllegalValueException(FriendName.MESSAGE_CONSTRAINTS);
+ }
+ return new FriendName(name);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index a6321cec2ea..982a713dd1f 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,9 +10,11 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.common.Description;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
+import seedu.address.model.person.FriendName;
+import seedu.address.model.person.Log;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
@@ -28,7 +30,9 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final String description;
private final List tagged = new ArrayList<>();
+ private final List logs = new ArrayList<>();
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
@@ -36,14 +40,21 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tagged") List tagged) {
+ @JsonProperty("description") String description,
+ @JsonProperty("tagged") List tagged,
+ @JsonProperty("logs") List logs) {
+
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.description = description;
if (tagged != null) {
this.tagged.addAll(tagged);
}
+ if (logs != null) {
+ this.logs.addAll(logs);
+ }
}
/**
@@ -54,9 +65,13 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ description = source.getDescription().value;
tagged.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ logs.addAll(source.getLogs().stream()
+ .map(JsonAdaptedLog::new)
+ .collect(Collectors.toList()));
}
/**
@@ -65,45 +80,49 @@ public JsonAdaptedPerson(Person source) {
* @throws IllegalValueException if there were any data constraints violated in the adapted person.
*/
public Person toModelType() throws IllegalValueException {
- final List personTags = new ArrayList<>();
- for (JsonAdaptedTag tag : tagged) {
- personTags.add(tag.toModelType());
- }
if (name == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, FriendName.class.getSimpleName()));
}
- if (!Name.isValidName(name)) {
- throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ if (!FriendName.isValidFriendName(name)) {
+ throw new IllegalValueException(FriendName.MESSAGE_CONSTRAINTS);
}
- final Name modelName = new Name(name);
+ final FriendName modelName = new FriendName(name);
- if (phone == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
- }
- if (!Phone.isValidPhone(phone)) {
+ if (phone != null && !Phone.isValidPhone(phone)) {
throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
}
final Phone modelPhone = new Phone(phone);
- if (email == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
- }
- if (!Email.isValidEmail(email)) {
+ if (email != null && !Email.isValidEmail(email)) {
throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
}
final Email modelEmail = new Email(email);
- if (address == null) {
- throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
- }
- if (!Address.isValidAddress(address)) {
+ if (address != null && !Address.isValidAddress(address)) {
throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
}
final Address modelAddress = new Address(address);
+
+ if (description != null && !Description.isValidDescription(description)) {
+ throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS);
+ }
+ final Description modelDescription = new Description(description);
+
+ final List personTags = new ArrayList<>();
+ for (JsonAdaptedTag tag : tagged) {
+ personTags.add(tag.toModelType());
+ }
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
- }
+ final List personLogs = new ArrayList<>();
+ for (JsonAdaptedLog log : logs) {
+ personLogs.add(log.toModelType());
+ }
+ final List modelLogs = personLogs;
+
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelDescription,
+ modelTags, modelLogs);
+ }
}
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index dfab9daaa0d..1778619fc87 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -56,6 +56,9 @@ public Optional readAddressBook(Path filePath) throws DataC
} catch (IllegalValueException ive) {
logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
throw new DataConversionException(ive);
+ } catch (NullPointerException ne) {
+ logger.info("Null values found in " + filePath + ": " + ne.getMessage());
+ throw new DataConversionException(ne);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..37028f5f6a2 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -1,5 +1,7 @@
package seedu.address.storage;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EVENT_FRIENDS;
+
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -11,6 +13,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.event.Event;
import seedu.address.model.person.Person;
/**
@@ -20,15 +23,19 @@
class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_EVENT = "Events list contains duplicate event(s).";
private final List persons = new ArrayList<>();
+ private final List events = new ArrayList<>();
/**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
+ * Constructs a {@code JsonSerializableAddressBook} with the given persons and events
*/
@JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
+ public JsonSerializableAddressBook(@JsonProperty("persons") List persons,
+ @JsonProperty("events") List events) {
this.persons.addAll(persons);
+ this.events.addAll(events);
}
/**
@@ -38,6 +45,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List {
private final CommandExecutor commandExecutor;
@FXML
- private TextField commandTextField;
+ private TextArea commandTextArea;
/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
@@ -27,8 +30,20 @@ public class CommandBox extends UiPart {
public CommandBox(CommandExecutor commandExecutor) {
super(FXML);
this.commandExecutor = commandExecutor;
+ commandTextArea.setWrapText(true);
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
- commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+ commandTextArea.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
+ commandTextArea.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler() {
+ @Override
+ public void handle(KeyEvent ke) {
+ if (ke.getCode().equals(KeyCode.ENTER) && !ke.isShiftDown()) {
+ handleCommandEntered();
+ ke.consume();
+ } else if (ke.getCode().equals(KeyCode.ENTER)) {
+ commandTextArea.insertText(commandTextArea.getCaretPosition(), "\n");
+ }
+ }
+ });
}
/**
@@ -36,14 +51,14 @@ public CommandBox(CommandExecutor commandExecutor) {
*/
@FXML
private void handleCommandEntered() {
- String commandText = commandTextField.getText();
+ String commandText = commandTextArea.getText();
if (commandText.equals("")) {
return;
}
try {
commandExecutor.execute(commandText);
- commandTextField.setText("");
+ commandTextArea.setText("");
} catch (CommandException | ParseException e) {
setStyleToIndicateCommandFailure();
}
@@ -53,14 +68,14 @@ private void handleCommandEntered() {
* Sets the command box style to use the default style.
*/
private void setStyleToDefault() {
- commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
+ commandTextArea.getStyleClass().remove(ERROR_STYLE_CLASS);
}
/**
* Sets the command box style to indicate a failed command.
*/
private void setStyleToIndicateCommandFailure() {
- ObservableList styleClass = commandTextField.getStyleClass();
+ ObservableList styleClass = commandTextArea.getStyleClass();
if (styleClass.contains(ERROR_STYLE_CLASS)) {
return;
@@ -82,4 +97,8 @@ public interface CommandExecutor {
CommandResult execute(String commandText) throws CommandException, ParseException;
}
+ public void requestTextAreaFocus() {
+ commandTextArea.requestFocus();
+ }
+
}
diff --git a/src/main/java/seedu/address/ui/EventCard.java b/src/main/java/seedu/address/ui/EventCard.java
new file mode 100644
index 00000000000..3d6743d3297
--- /dev/null
+++ b/src/main/java/seedu/address/ui/EventCard.java
@@ -0,0 +1,112 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
+import seedu.address.model.event.Event;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class EventCard extends UiPart {
+
+ private static final Font font = new Font("Segoe UI Semibold", 13);
+
+ private static final String FXML = "EventListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Event event;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private TextFlow dateTime;
+ @FXML
+ private TextFlow description;
+ @FXML
+ private FlowPane friends;
+
+ /**
+ * Creates a {@code EventCard} with the given {@code Event} and index to display.
+ */
+ public EventCard(Event event, int displayedIndex) {
+ super(FXML);
+ this.event = event;
+ id.setText(displayedIndex + ". ");
+ name.setText(event.getName().fullName);
+
+ Text dateTimeText = new Text(" : " + event.getDateTime().toString());
+ Text dateTimeLabel = new Text("Event Date");
+ dateTimeText.setFill(Color.WHITE);
+ dateTimeLabel.setFill(Color.WHITE);
+ dateTimeText.setFont(font);
+ dateTimeLabel.setFont(font);
+ dateTimeLabel.setUnderline(true);
+ dateTime.getChildren().addAll(dateTimeLabel, dateTimeText);
+
+
+ Text descriptionText = new Text(" : " + (event.getDescription().value == null ? "-" : event.getDescription().value));
+ Text descriptionLabel = new Text("Event description");
+ descriptionText.setFill(Color.WHITE);
+ descriptionLabel.setFill(Color.WHITE);
+ descriptionText.setFont(font);
+ descriptionLabel.setFont(font);
+ descriptionLabel.setUnderline(true);
+ description.getChildren().addAll(descriptionLabel, descriptionText);
+
+
+ Text friendsText = new Text("Friends");
+ Text colon = new Text(" : ");
+ friendsText.setUnderline(true);
+ friendsText.setFill(Color.WHITE);
+ friendsText.setFont(font);
+ colon.setFill(Color.WHITE);
+ colon.setFont(font);
+ friends.getChildren().addAll(friendsText, colon);
+ friends.setHgap(4);
+ if (event.getFriendNames().size() == 0) {
+ Text empty = new Text("-");
+ empty.setFill(Color.WHITE);
+ empty.setFont(font);
+ friends.getChildren().add(empty);
+ } else {
+ event.getFriendNames().stream()
+ .forEach(friend -> friends.getChildren().add(new Label(friend.fullName)));
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EventCard)) {
+ return false;
+ }
+
+ // state check
+ EventCard card = (EventCard) other;
+ return id.getText().equals(card.id.getText())
+ && event.equals(card.event);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/EventListPanel.java b/src/main/java/seedu/address/ui/EventListPanel.java
new file mode 100644
index 00000000000..e52b4f571b7
--- /dev/null
+++ b/src/main/java/seedu/address/ui/EventListPanel.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.event.Event;
+
+/**
+ * Panel containing the list of Events.
+ */
+public class EventListPanel extends UiPart {
+ private static final String FXML = "EventListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(EventListPanel.class);
+
+ @FXML
+ private ListView eventListView;
+
+ /**
+ * Creates a {@code EventListPanel} with the given {@code ObservableList}.
+ */
+ public EventListPanel(ObservableList eventList) {
+ super(FXML);
+ eventListView.setItems(eventList);
+ eventListView.setCellFactory(listView -> new EventListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Event} using a {@code EventCard}.
+ */
+ private class EventListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Event event, boolean empty) {
+ super.updateItem(event, empty);
+
+ if (empty || event == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new EventCard(event, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/ExpandedPersonCard.java b/src/main/java/seedu/address/ui/ExpandedPersonCard.java
new file mode 100644
index 00000000000..b85d2c39519
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ExpandedPersonCard.java
@@ -0,0 +1,189 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
+import seedu.address.model.event.Event;
+import seedu.address.model.person.Log;
+import seedu.address.model.person.Person;
+
+/**
+ * An UI component that displays the full information of a {@code Person}.
+ */
+public class ExpandedPersonCard extends UiPart {
+
+ private static final String FXML = "ExpandedPersonListCard.fxml";
+ private static final Font font = new Font("Segoe UI Semibold", 16);
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ private final Person person;
+ private EventListPanel upcomingEventsPanel;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private TextFlow phone;
+ @FXML
+ private TextFlow address;
+ @FXML
+ private TextFlow email;
+ @FXML
+ private TextFlow description;
+ @FXML
+ private FlowPane tags;
+ @FXML
+ private Label eventsHeader;
+ @FXML
+ private Label noEventsText;
+ @FXML
+ private StackPane upcomingEventsPanelPlaceholder;
+ @FXML
+ private Label logsHeader;
+ @FXML
+ private Label logs;
+
+ /**
+ * Creates a {@code PersonCard} with the given {@code Person} and index to display.
+ */
+ public ExpandedPersonCard(Person person, ObservableList eventList) {
+ super(FXML);
+ this.person = person;
+ name.setText("1. " + person.getName().fullName);
+
+ Text colon1 = new Text(" : ");
+ colon1.setFill(Color.WHITE);
+ colon1.setFont(font);
+
+
+ Text phoneNumText = new Text(" : " + (person.getPhone().value == null ? " -" : person.getPhone().value));
+ Text phoneLabel = new Text("Phone");
+ phoneNumText.setFill(Color.WHITE);
+ phoneLabel.setFill(Color.WHITE);
+ phoneNumText.setFont(font);
+ phoneLabel.setFont(font);
+ phoneLabel.setUnderline(true);
+ phone.getChildren().addAll(phoneLabel, phoneNumText);
+
+ Text addressText = new Text(" : " + (person.getAddress().value == null ? " -" : person.getAddress().value));
+ Text addressLabel = new Text("Address");
+ addressLabel.setFill(Color.WHITE);
+ addressText.setFill(Color.WHITE);
+ addressText.setFont(font);
+ addressLabel.setFont(font);
+ addressLabel.setUnderline(true);
+ address.getChildren().addAll(addressLabel, addressText);
+
+ Text emailText = new Text(" : " + (person.getEmail().value == null ? " -" : person.getEmail().value));
+ Text emailLabel = new Text("Email");
+ emailLabel.setFill(Color.WHITE);
+ emailText.setFill(Color.WHITE);
+ emailText.setFont(font);
+ emailLabel.setFont(font);
+ emailLabel.setUnderline(true);
+ email.getChildren().addAll(emailLabel, emailText);
+
+ Text descriptionText = new Text(" : " + (person.getDescription().value == null ? " -" : person.getDescription().value));
+ Text descriptionLabel = new Text("Description");
+ descriptionLabel.setFill(Color.WHITE);
+ descriptionText.setFill(Color.WHITE);
+ descriptionText.setFont(font);
+ descriptionLabel.setFont(font);
+ descriptionLabel.setUnderline(true);
+ description.getChildren().addAll(descriptionLabel, descriptionText);
+
+ Text tagsText = new Text("Tags");
+ tagsText.setFill(Color.WHITE);
+ tagsText.setFont(font);
+ tagsText.setUnderline(true);
+ tags.getChildren().addAll(tagsText, new Text(" "), colon1);
+ if (person.getTags().size() == 0) {
+ Text empty = new Text("-");
+ empty.setFill(Color.WHITE);
+ empty.setFont(font);
+ tags.getChildren().add(empty);
+ } else {
+ person.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag ->
+ tags.getChildren().add(new Label(tag.tagName)));
+ }
+
+ // displaying upcoming events
+ upcomingEventsPanelPlaceholder.setStyle("-fx-background-radius:10;"
+ + " -fx-border-radius: 10px; -fx-border-color: #05d1e8;");
+ upcomingEventsPanel = new EventListPanel(eventList);
+
+ if (eventList.size() > 0) {
+ eventsHeader.setText("Upcoming Events :");
+ eventsHeader.setUnderline(true);
+ noEventsText.setMaxSize(0, 0);
+ noEventsText.setMinSize(0, 0);
+ upcomingEventsPanelPlaceholder.getChildren().add(upcomingEventsPanel.getRoot());
+
+ } else {
+ eventsHeader.setText("Upcoming Events : ");
+ eventsHeader.setUnderline(true);
+ noEventsText.setText("No upcoming events!");
+
+ //forces the size of upcomingEventsPanelPlaceholder to be (0, 0)
+ upcomingEventsPanelPlaceholder.setMaxSize(0, 0);
+ upcomingEventsPanelPlaceholder.setMinSize(0, 0);
+
+ }
+
+ //displaying each log
+ List logList = person.getLogs();
+
+ if (logList.size() > 0) {
+ logsHeader.setText("Logs :");
+ logsHeader.setUnderline(true);
+ StringBuilder sb = new StringBuilder();
+ int numberOfLogs = logList.size();
+ for (int i = 1; i <= numberOfLogs; i++) {
+ sb.append(i + ". " + logList.get(i - 1).toString() + "\n");
+ }
+ logs.setText(sb.toString());
+ } else {
+ logsHeader.setText("Logs : ");
+ logsHeader.setUnderline(true);
+ logs.setText("No logs!");
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ExpandedPersonCard)) {
+ return false;
+ }
+
+ // state check
+ ExpandedPersonCard card = (ExpandedPersonCard) other;
+ return person.equals(card.person);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/ExpandedPersonListPanel.java b/src/main/java/seedu/address/ui/ExpandedPersonListPanel.java
new file mode 100644
index 00000000000..961895ce44c
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ExpandedPersonListPanel.java
@@ -0,0 +1,50 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.event.Event;
+import seedu.address.model.person.Person;
+
+/**
+ * Panel containing the view of an expanded person card.
+ */
+public class ExpandedPersonListPanel extends UiPart {
+ private static final String FXML = "ExpandedPersonListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(ExpandedPersonListPanel.class);
+ private final ObservableList eventList;
+
+ @FXML
+ private ListView personListView;
+
+ /**
+ * Creates a {@code PersonListPanel} with the given {@code ObservableList} of event and person.
+ */
+ public ExpandedPersonListPanel(ObservableList personList, ObservableList eventList) {
+ super(FXML);
+ personListView.setItems(personList);
+ personListView.setCellFactory(listView -> new PersonListViewCell());
+ this.eventList = eventList;
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
+ */
+ private class PersonListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Person person, boolean empty) {
+ super.updateItem(person, empty);
+ if (empty || person == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ExpandedPersonCard(person, eventList).getRoot());
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 9a665915949..61bb70d6cf2 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,7 +15,7 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2122s2-cs2103-f09-2.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..d9203f22727 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -2,9 +2,13 @@
import java.util.logging.Logger;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.MenuItem;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
@@ -32,8 +36,12 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private EventListPanel eventListPanel;
+ private PersonInsightListPanel personInsightListPanel;
+ private ExpandedPersonListPanel expandedPersonListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private CommandBox commandBox;
@FXML
private StackPane commandBoxPlaceholder;
@@ -44,12 +52,33 @@ public class MainWindow extends UiPart {
@FXML
private StackPane personListPanelPlaceholder;
+ @FXML
+ private StackPane eventListPanelPlaceholder;
+
+ @FXML
+ private StackPane personInsightListPanelPlaceholder;
+
+ @FXML
+ private StackPane expandedPersonListPanelPlaceholder;
+
@FXML
private StackPane resultDisplayPlaceholder;
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private TabPane tabs;
+
+ @FXML
+ private Tab personListTab;
+
+ @FXML
+ private Tab eventsListTab;
+
+ @FXML
+ private Tab personInsightsListTab;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -66,6 +95,17 @@ public MainWindow(Stage primaryStage, Logic logic) {
setAccelerators();
helpWindow = new HelpWindow();
+ commandBox = new CommandBox(this::executeCommand);
+
+ tabs.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
+ @Override
+ public void changed(ObservableValue extends Tab> observable, Tab oldTab, Tab newTab) {
+ if (newTab.equals (personInsightsListTab)) {
+ personInsightListPanel = new PersonInsightListPanel(logic.getInsightsList());
+ personInsightListPanelPlaceholder.getChildren().add(personInsightListPanel.getRoot());
+ }
+ }
+ });
}
public Stage getPrimaryStage() {
@@ -110,17 +150,26 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
+ expandedPersonListPanel =
+ new ExpandedPersonListPanel(logic.getFilteredPersonList(), logic.getFilteredEventList());
+ expandedPersonListPanelPlaceholder.getChildren().add(expandedPersonListPanel.getRoot());
+
personListPanel = new PersonListPanel(logic.getFilteredPersonList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ eventListPanel = new EventListPanel(logic.getFilteredEventList());
+ eventListPanelPlaceholder.getChildren().add(eventListPanel.getRoot());
+
+ personInsightListPanel = new PersonInsightListPanel(logic.getInsightsList());
+ personInsightListPanelPlaceholder.getChildren().add(personInsightListPanel.getRoot());
+
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
- CommandBox commandBox = new CommandBox(this::executeCommand);
- commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+ commandBoxPlaceholder.getChildren().add(this.commandBox.getRoot());
}
/**
@@ -163,8 +212,37 @@ private void handleExit() {
primaryStage.hide();
}
- public PersonListPanel getPersonListPanel() {
- return personListPanel;
+ private void changeInterface(CommandResult commandResult) throws CommandException, ParseException {
+ boolean event = commandResult.isEvent();
+ boolean showInsight = commandResult.isShowInsights();
+ boolean isExpandedCard = commandResult.isShowFriendCommand();
+ if (showInsight) {
+ tabs.getSelectionModel().select(personInsightsListTab);
+ // dynamically reload component
+ personInsightListPanel = new PersonInsightListPanel(logic.getInsightsList());
+ personInsightListPanelPlaceholder.getChildren().set(0, personInsightListPanel.getRoot());
+ personInsightListPanelPlaceholder.requestFocus();
+ } else if (event) {
+ logic.execute("lf");
+ personListPanelPlaceholder.requestFocus();
+ personListPanelPlaceholder.toFront();
+ tabs.getSelectionModel().select(eventsListTab);
+ eventListPanelPlaceholder.requestFocus();
+ } else {
+ logic.execute("le");
+ tabs.getSelectionModel().select(personListTab);
+ if (isExpandedCard) {
+ //force refresh so that size of upcoming events can be detected
+ expandedPersonListPanel = new ExpandedPersonListPanel(logic.getFilteredPersonList(), logic.getFilteredEventList());
+ expandedPersonListPanelPlaceholder.getChildren().set(0, expandedPersonListPanel.getRoot());
+ expandedPersonListPanelPlaceholder.requestFocus();
+ expandedPersonListPanelPlaceholder.toFront();
+
+ } else {
+ personListPanelPlaceholder.requestFocus();
+ personListPanelPlaceholder.toFront();
+ }
+ }
}
/**
@@ -177,6 +255,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
CommandResult commandResult = logic.execute(commandText);
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
+ changeInterface(commandResult);
if (commandResult.isShowHelp()) {
handleHelp();
@@ -186,6 +265,7 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleExit();
}
+ this.commandBox.requestTextAreaFocus();
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("Invalid command: " + commandText);
@@ -193,4 +273,6 @@ private CommandResult executeCommand(String commandText) throws CommandException
throw e;
}
}
+
+
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 7fc927bc5d9..52f4120bb53 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -7,6 +7,11 @@
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
+import seedu.address.model.person.Log;
import seedu.address.model.person.Person;
/**
@@ -15,6 +20,7 @@
public class PersonCard extends UiPart {
private static final String FXML = "PersonListCard.fxml";
+ private static final Font font = new Font("Segoe UI Semibold", 13);
/**
* Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
@@ -24,7 +30,7 @@ public class PersonCard extends UiPart {
* @see The issue on AddressBook level 4
*/
- public final Person person;
+ private final Person person;
@FXML
private HBox cardPane;
@@ -33,13 +39,15 @@ public class PersonCard extends UiPart {
@FXML
private Label id;
@FXML
- private Label phone;
+ private TextFlow phone;
@FXML
- private Label address;
+ private TextFlow address;
@FXML
- private Label email;
+ private TextFlow email;
@FXML
private FlowPane tags;
+ @FXML
+ private FlowPane logs;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
@@ -49,12 +57,76 @@ public PersonCard(Person person, int displayedIndex) {
this.person = person;
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
- phone.setText(person.getPhone().value);
- address.setText(person.getAddress().value);
- email.setText(person.getEmail().value);
- person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ Text colon1 = new Text(" : ");
+ colon1.setFill(Color.WHITE);
+ colon1.setFont(font);
+
+ Text colon2 = new Text(" : ");
+ colon2.setFill(Color.WHITE);
+ colon2.setFont(font);
+
+
+ Text phoneNumText = new Text(" : " + (person.getPhone().value == null ? "-" : person.getPhone().value));
+ Text phoneLabel = new Text("Phone");
+ phoneNumText.setFill(Color.WHITE);
+ phoneLabel.setFill(Color.WHITE);
+ phoneNumText.setFont(font);
+ phoneLabel.setFont(font);
+ phoneLabel.setUnderline(true);
+ phone.getChildren().addAll(phoneLabel, phoneNumText);
+
+ Text addressText = new Text(" : " + (person.getAddress().value == null ? "-" : person.getAddress().value));
+ Text addressLabel = new Text("Address");
+ addressLabel.setFill(Color.WHITE);
+ addressText.setFill(Color.WHITE);
+ addressText.setFont(font);
+ addressLabel.setFont(font);
+ addressLabel.setUnderline(true);
+ address.getChildren().addAll(addressLabel, addressText);
+
+ Text emailText = new Text(" : " + (person.getEmail().value == null ? "-" : person.getEmail().value));
+ Text emailLabel = new Text("Email");
+ emailLabel.setFill(Color.WHITE);
+ emailText.setFill(Color.WHITE);
+ emailText.setFont(font);
+ emailLabel.setFont(font);
+ emailLabel.setUnderline(true);
+ email.getChildren().addAll(emailLabel, emailText);
+
+ Text tagsText = new Text("Tags");
+ tagsText.setFill(Color.WHITE);
+ tagsText.setFont(font);
+ tagsText.setUnderline(true);
+ tags.getChildren().addAll(tagsText, colon1);
+ if (person.getTags().size() == 0) {
+ Text empty = new Text("-");
+ empty.setFill(Color.WHITE);
+ empty.setFont(font);
+ tags.getChildren().add(empty);
+ } else {
+ person.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ }
+ logs.setHgap(4);
+ Text logsText = new Text("Logs");
+ logsText.setFill(Color.WHITE);
+ logsText.setFont(font);
+ logsText.setUnderline(true);
+ logs.getChildren().addAll(logsText, colon2);
+ if (person.getLogs().size() == 0) {
+ Text empty = new Text("-");
+ empty.setFill(Color.WHITE);
+ empty.setFont(font);
+ logs.getChildren().add(empty);
+ } else {
+ int index = 1;
+ for (Log log : person.getLogs()) {
+ logs.getChildren().add(new Label(index + ". " + log.getTitle()));
+ index++;
+ }
+ }
}
@Override
diff --git a/src/main/java/seedu/address/ui/PersonInsightCard.java b/src/main/java/seedu/address/ui/PersonInsightCard.java
new file mode 100644
index 00000000000..ae181944081
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PersonInsightCard.java
@@ -0,0 +1,71 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.insights.PersonInsight;
+
+/**
+ * An UI component that displays information of a {@code Person}.
+ */
+public class PersonInsightCard extends UiPart {
+
+ private static final String FXML = "PersonInsightCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ private final Person person;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label numLogs;
+ @FXML
+ private Label numEvents;
+ @FXML
+ private Label lastEvent;
+
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public PersonInsightCard(PersonInsight personInsight, int displayedIndex) {
+ super(FXML);
+ this.person = personInsight.getPerson();
+ id.setText(displayedIndex + ". ");
+ name.setText(person.getName().fullName);
+ numLogs.setText(personInsight.getNumLogsInsightAsString());
+ numEvents.setText(personInsight.getNumEventsInsightAsString());
+ lastEvent.setText(personInsight.getLastEventInsightAsString());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PersonInsightCard)) {
+ return false;
+ }
+
+ // state check
+ PersonInsightCard card = (PersonInsightCard) other;
+ return id.getText().equals(card.id.getText())
+ && person.equals(card.person);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/PersonInsightListPanel.java b/src/main/java/seedu/address/ui/PersonInsightListPanel.java
new file mode 100644
index 00000000000..defe61bb98c
--- /dev/null
+++ b/src/main/java/seedu/address/ui/PersonInsightListPanel.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.person.insights.PersonInsight;
+
+/**
+ * Panel containing the list of PersonInsight's.
+ */
+public class PersonInsightListPanel extends UiPart {
+ private static final String FXML = "PersonInsightListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(PersonInsightListPanel.class);
+
+ @FXML
+ private ListView personInsightListView;
+
+ /**
+ * Creates a {@code PersonInsightListPanel} with the given {@code ObservableList}.
+ */
+ public PersonInsightListPanel(ObservableList personInsights) {
+ super(FXML);
+ personInsightListView.setItems(personInsights);
+ personInsightListView.setCellFactory(listView -> new PersonInsightListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code PersonInsight} using a {@code PersonInsightCard}.
+ */
+ private class PersonInsightListViewCell extends ListCell {
+ @Override
+ protected void updateItem(PersonInsight insight, boolean empty) {
+ super.updateItem(insight, empty);
+
+ if (empty || insight == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new PersonInsightCard(insight, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..ddf6fcdf267 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -32,11 +32,10 @@ public PersonListPanel(ObservableList personList) {
/**
* Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
*/
- class PersonListViewCell extends ListCell {
+ private class PersonListViewCell extends ListCell {
@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
-
if (empty || person == null) {
setGraphic(null);
setText(null);
@@ -45,5 +44,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..5318b4d684c 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/amigos.png";
private Logic logic;
private MainWindow mainWindow;
diff --git a/src/main/resources/images/amigos.png b/src/main/resources/images/amigos.png
new file mode 100644
index 00000000000..be6b2ec934d
Binary files /dev/null and b/src/main/resources/images/amigos.png differ
diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml
index 09f6d6fe9e4..0cae0212fe6 100644
--- a/src/main/resources/view/CommandBox.fxml
+++ b/src/main/resources/view/CommandBox.fxml
@@ -1,9 +1,8 @@
-
+
-
-
+
+
-
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..924976c743d 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -24,19 +24,58 @@
-fx-opacity: 1;
}
-.text-field {
- -fx-font-size: 12pt;
- -fx-font-family: "Segoe UI Semibold";
+.text-area .content{
+ -fx-background-color: #383838 transparent #383838;
+ -fx-background-insets: 0;
+ -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
}
.tab-pane {
-fx-padding: 0 0 0 1;
+ -fx-alignment: CENTER;
+ -fx-tab-min-width:90px;
}
.tab-pane .tab-header-area {
-fx-padding: 0 0 0 0;
-fx-min-height: 0;
-fx-max-height: 0;
+ -fx-alignment: center;
+}
+
+.tab-pane .tab-header-area .tab-header-background {
+ -fx-opacity: 1;
+ -fx-background-color: #63615c;
+}
+
+.tab-pane .tab {
+ -fx-background-color: #e6e6e6;
+}
+
+.tab-pane .tab:selected
+{
+ -fx-background-color: #63615c;
+}
+
+.tab:selected .tab-label {
+ -fx-alignment: CENTER;
+ -fx-text-fill: #96b946;
+ -fx-font-size: 12px;
+ -fx-font-weight: bold;
+}
+
+.tab .tab-label {
+ -fx-alignment: CENTER;
+ -fx-text-fill: #828282;
+ -fx-font-size: 12px;
+ -fx-font-weight: bold;
+ -fx-font-family: "Segoe UI Light";
+
}
.table-view {
@@ -120,12 +159,31 @@
-fx-text-fill: white;
}
+.cell_header_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 20px;
+ -fx-text-fill: #010504;
+}
+
.cell_big_label {
-fx-font-family: "Segoe UI Semibold";
-fx-font-size: 16px;
-fx-text-fill: #010504;
}
+.cell_name_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 20px;
+ -fx-text-fill: #010504;
+}
+
+.cell_header_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-font-style: bold;
+ -fx-text-fill: #010504;
+}
+
.cell_small_label {
-fx-font-family: "Segoe UI";
-fx-font-size: 13px;
@@ -144,6 +202,7 @@
.status-bar {
-fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-radius: 20;
}
.result-display {
@@ -308,8 +367,13 @@
}
#cardPane {
- -fx-background-color: transparent;
- -fx-border-width: 0;
+ -fx-border-width: 1;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15px;
+}
+
+#cardPane:hover {
+ -fx-border-color: #05d1e8;
}
#commandTypeLabel {
@@ -317,15 +381,16 @@
-fx-text-fill: #F70D1A;
}
-#commandTextField {
+#commandTextArea {
-fx-background-color: transparent #383838 transparent #383838;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
-fx-border-insets: 0;
-fx-border-width: 1;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
+ -fx-border-radius: 20;
+ -fx-background-radius: 20;
}
#filterField, #personListPanel, #personWebpage {
@@ -350,3 +415,23 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#logs .label {
+ -fx-text-fill: white;
+ -fx-background-color: #91693e;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+#friends .label {
+ -fx-text-fill: white;
+ -fx-background-color: #9FCC2E;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+
diff --git a/src/main/resources/view/EventListCard.fxml b/src/main/resources/view/EventListCard.fxml
new file mode 100644
index 00000000000..117bb7dffa5
--- /dev/null
+++ b/src/main/resources/view/EventListCard.fxml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+