diff --git a/.gitignore b/.gitignore
index 71c9194e8bd..fe5f63b8c19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,9 @@ src/main/resources/docs/
/data/
/config.json
/preferences.json
+
+/userData.json
+
/*.log.*
# Test sandbox files
diff --git a/README.md b/README.md
index 13f5c77403f..a87c93f5110 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,129 @@
-[](https://github.com/se-edu/addressbook-level3/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.
+[](https://github.com/AY2223S2-CS2103-F10-3/tp/actions/workflows/gradle.yml)
+
+
+
+# ModCheck
+ModCheck is a contact management app that enables you to manage all your contacts easily that works on Windows, MacOS and Linux!
+Our app is catered towards fast typist and many features are catered for students that need better management of their contacts.
+
+## Main GUI
+
+
+
+
+Main UI
+
+
+## Key Features
+
+### 👁️ View hidden details of contacts
+
+ModCheck allows you to view all the details that the contacts have
+
+### ➕ Add new contacts easily
+
+ModCheck can add new contacts quickly and supports many contacts
+
+### 🧹 Clear all existing contacts
+
+ModCheck can clear all your contacts from the app with just one command!
+
+### ❌ Delete a specific contact
+
+ModCheck allows you to choose which contact to be deleted from the app
+
+### ✍️ Edit a specific contact
+
+ModCheck allows you to edit details of the specified contact
+
+### 🔙 Undo previous changes made in ModCheck
+
+ModCheck can undo all changes that you have made accidentally
+
+### ↩️ Redo what has been undone
+
+ModCheck also allows you to redo changes made if you have undo accidentally
+
+### ⬇️ Load a new data file
+
+ModCheck also allows you to quickly load data from another file to ModCheck
+
+### 📦 Export specified contacts for archiving
+
+ModCheck can export contacts that you want to archive in another place
+
+### 🔦 Toggle between light and dark mode
+
+ModCheck allows you to quickly customize how the overall UI looks!
+
+### 🔍 Filter contacts by name, phone number, description, email address, tags and module tags quickly
+
+ModCheck can quickly filter all your contacts based on the criteria provided
+
+### 💾 Instant saving when changes are made
+
+ModCheck can save all your work immediately on the fly
+
+### ✅ List all contacts quickly without lag
+
+ModCheck can display all your contacts in one go!
+
+## Usage
+
+
+
+**: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`.
+
+* 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`.
+
+* Items with `…` after them can be used multiple times including zero times.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+
+* Parameters can be in any order.
+ e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+
+* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
+ 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`.
+
+* :warning: Unrecognised fields such as `b/` or `c/` will not be picked up as fields, and will be treated as input.
+
+
+
+## Guides
+
+[User Guide](https://ay2223s2-cs2103-f10-3.github.io/tp/UserGuide.html)
+
+[Developer Guide](https://ay2223s2-cs2103-f10-3.github.io/tp/DeveloperGuide.html)
+
+## FAQ
+
+**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer. `load` the contents of the previous data file into your new ModCheck.
+
+## Command summary
+
+| Action | Format, Examples |
+|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL d/DESCRIPTION [t/TAG]… [m/MODULE_TAG]… ` e.g., `add n/Benedict Tan d/Great Friend e/BenedictTan@gmail.com p/98070707 t/Friend m/CS2103 m/CS3230 ` |
+| **View** | `view INDEX` e.g., `view 2` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` or `delete INDEXES` or `delete NAME` e.g., `delete 3` or `delete 1,2,3` or `delete James` |
+| **Edit** | `edit {INDEX or NAME} [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com` or `edit James e/jameslee@example.com` | |
+| **List** | `list` |
+| **Help** | `help` |
+| **Filter** | `filter n/NAME` `filter p/PHONE_NUMBER` `filter e/EMAIL_ADDRESS` `filter d/DESCRIPTION` `filter t/TAG` `filter m/MODULE_TAG` e.g. `filter n/Alex` e.g. `filter p/91031282` e.g. `filter e/royb@example.com` e.g. `filter d/helpful` e.g. `filter t/family` e.g. `filter m/CS2103` |
+| **Undo** | `undo` |
+| **Redo** | `redo` |
+| **Load** | `load` OR `load ` |
+| **Export** | `export INDEX` e.g., `export 2` |
+| **Light** | `light` |
+| **Dark** | `dark` |
+
+---
diff --git a/build.gradle b/build.gradle
index 108397716bd..4db8855ad76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -69,4 +69,8 @@ shadowJar {
archiveFileName = 'addressbook.jar'
}
+run {
+ enableAssertions = true
+}
+
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..6e003bb831e 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,59 +1,57 @@
---
layout: page
-title: About Us
+#About Us
---
+# 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
+### Joel Low
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/joellow88)]
+[[portfolio](team/joellow88.md)]
-* Role: Project Advisor
+* Role: Developer
-### Jane Doe
+### Haiqel
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/acerizm)]
+[[portfolio](team/acerizm.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
-### Johnny Doe
+### Gibson
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/Gibson0918)] [[portfolio](team/gibson0918.md)]
* Role: Developer
-* Responsibilities: Data
-### Jean Doe
+### Toh Wei Hao
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/Statspadders)]
+[[portfolio](team/statspadders.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Java
-### James Doe
+### Elvern Tan
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/elvern18)]
+[[portfolio](team/elvern18.md)]
* Role: Developer
-* Responsibilities: UI
+
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 46eae8ee565..e554a7d0da9 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -73,10 +73,14 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se-

-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc.
+
+The UI also consists of a `LoginWindow` that is made up of parts e.g. `WelcomeSection`,`LoadingSection`,`CreatePasswordSection` etc.
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)
+All these, including the `MainWindow` and `LoginWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+
The `UI` component,
* executes user commands using the `Logic` component.
@@ -116,7 +120,7 @@ How the parsing works:
### Model component
**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
-
+
The `Model` component,
@@ -124,6 +128,9 @@ 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 a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
+* stores a `UserData` object that represents the user's personal data. This is exposed to the outside as a `ReadOnlyUserData` objects.
+* stores a `UndoManager` object that contains previous states of ModCheck. This is exposed to the outside through a
+ `Undoable` interface.
* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
@@ -141,7 +148,8 @@ The `Model` component,
The `Storage` component,
* can save both address book data and user preference data in json format, and read them back into corresponding objects.
-* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
+* can also save the user's personal data in json format, and read them back into corresponding objects
+* inherits from `AddressBookStorage`,`UserPrefStorage` and `UserDataStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
### Common classes
@@ -154,89 +162,283 @@ 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
+### Add Command
+**Purpose:** Allow users to add contact details
+
+**Format:** `add n/NAME p/PHONE_NUMBER e/EMAIL d/DESCRIPTION [t/TAG]… [m/MODULE_TAG]…`
+
+**Fields:**
+
+`n/` : name of the person (COMPULSORY)
+
+`d/` : description of the person
+
+`e/` : email
+
+`p/` : phone number
+
+`t/` : tags
+
+`m/` : module tags
+
+**Constraints:**
+
+n/ : Alphanumeric characters and spaces, and it should not be blank
+
+e/ : Emails should be of the format `local-part@domain`.
+ * The local-part should only contain alphanumeric characters and these special characters, `+_.-`
+ * The local-part may not start or end with any special characters.
+ This is followed by a `@` and then a domain name.
+ * The domain name is made up of domain labels separated by periods.
+ * The domain name must end with a domain label at least 2 characters long, have each domain label start and end with alphanumeric characters, have each domain label consist of alphanumeric characters, separated only by hyphens, if any.
+
+p/ : Phone numbers should contain only numbers, and it should be at least 3 digits long.
+
+t/ or m/ : Alphanumeric characters
+
+:information_source: Not allowed to create new person if name already exist in ModCheck
-#### Proposed Implementation
+#### Implementation
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+The implementation of this feature requires 'AddCommand' and 'AddCommandParser'.
-* `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.
+Below is an activity diagram that shows what happens when a user executes the `add` command
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+
+
+### Undo/redo feature
+
+#### Implementation
+
+The undo/redo mechanism is facilitated by `UndoManager` and an `Undoable` interface. The `UndoManager` is
+responsible for saving the version history of a ModelManager. The `Undoable` interface is implemented by a
+`Model` to indicate that it has support for undo and redo functionality.
+
+
+The `Undoable` interface requires the implementation of the following functions:
+* `Undoable#hasUndoableCommand()`: To allow the user of the `Undoable` object to check if a command exists that can
+ be undone
+* `Undoable#hasRedoableCommand()`: To allow the user of the `Undoable` object to check if a command exists that can
+ be redone
+* `Undoable#executeUndo()`: To allow the user of the `Undoable` object to undo to a previous saved state
+* `Undoable#executeRedo()`: To allow the user of the `Undoable` object to redo to a later saved state
+
+The `ModelManager` object implements the `Undoable` interface. It has an `UndoManager` object to manage the
+implementation in the various required undo and redo functionality. The `UndoManager` object is responsible for
+saving previous versions of `AddressBook`, keeping track of which version is currently shown to the user, and
+yielding saved versions of `AddressBook` objects whenever requested.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+Step 1. The user launches the application for the first time. The `UndoManager` will be initialized with
+`addressBookHistory` containing only the current addressBook state. The `versionTracker` variable is initialized to
+0, indicating 0 undos have been executed so far, and the version of ModCheck shown is the most recent version.
-
+
-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.
+Step 2. The user executes `delete 5` command to delete the 5th person in the address book.
+The `delete` command calls `UndoManager#addToHistory()`, causing the modified state of the address book after the
+`delete 5` command executes to be saved in `addressBookHistory`. The `versionTracker` variable stays at 0 as the
+addressBook state after deleting is still the most recent version.
-
+
-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`.
+Step 3. The user executes `add n/David …` to add a new person.
+The `add` command also calls `UndoManager#addToHistory()`, causing another modified address book state to be saved into
+the `addressBookHistory`. Similarly, `versionTracker` remains at 0.
-
+
-
: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`.
+
:information_source: **Note:** If a command fails its execution, it
+will not call `UndoManager#addToHistory()`, so the address book state will not be saved into the `addressBookHistory`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the
+`undo` command. The `undo` command will call `UndoManager#getPreviousHistory()`. This method increases the
+`versionTracker` variable by 1. Internally, the UndoManager will find the first most recent saved history, and
+returns a copy of the addressBook representing that.
-
+
-
: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
+
:information_source: **Note:** If `versionHistory` is equal to the
+number of number of saved histories, there is no more saved history to undo. The `undo` command uses
+`Model#hasUndoableCommand()` to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
-The following sequence diagram shows how the undo operation works:
+The `redo` command does the opposite — it calls `Undoable#getNextHistory()`, which decreases the `versionTracker` by
+1, and returns a copy of the addressBook representing the state of the addressBook after redoing.
-
-
-
: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.
+
:information_source: **Note:** If the `versionTracker` is 0, pointing
+to the latest address book state, then there are no undone AddressBook states to restore. The
+`redo` command uses `Undoable#hasRedoableCommand()` to check if this is the case. If so, it will return an error to the
+user rather than attempting to perform the redo.
-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.
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as
+`list`, will not call `UndoManager#addToHistory()`. Thus, the `addressBookHistory` remains unchanged.
-
: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.
+
-
+Step 6. The user executes `clear`, which calls `UndoManager#addToHistory()`. Since the `versionTracker` is not
+0, all address book states from index 0 to one before the current version will be purged, and the versionTracker
+will be reset to 0. Reason: It no longer makes sense to redo the "untracked heads". This is the behavior that most
+modern desktop applications follow.
+
+
+
+Below is the sequence diagram showing how a modification is added to the `undoManager`, using `AddCommand` as an
+example.
+
+
+
+Below is the sequence diagram indicating what happens when the user executes `undo`.
+
+
+
+### Login
+**Purpose:** Allow user to login into ModCheck with password.
+
+#### Implementation
+
+Below is an activity diagram that shows what happens when a user logins into ModCheck.
+
+
+
+### Create Password
+**Purpose:** Allow user to create a password to secure ModCheck
+
+#### Implementation
+Below is an activity diagram that shows what happens when a user tries to create a new password
+
+
+
+
+### Delete contact
+**Purpose:** Allow user to delete contacts that a user no longer needs.
+
+Formats:
+1. `delete `
+2. `delete ,,...`
+3. `delete `
+
+#### Implementation
+
+The implementation of this feature requires `DeleteCommand`, `DeleteCommandParser`, `DeleteSingleIndexCommand`, `DeleteMultipleIndexCommand` and `DeleteByNameCommand`.
+
+Below is an activity diagram that shows what happens when a user executes the `delete` command.
+
+
+
+### Edit contact details
+**Purpose:** Allow user to edit contacts that are outdated.
+
+Formats:
+1. `edit `
+2. `edit `
+
+#### Implementation
+
+The implementation of this feature requires `EditCommand`, `EditCommandParser`, `EditByIndexCommand`, and `EditByNameCommand`.
+
+Below is an activity diagram that shows what happens when a user executes the `edit` command.
+
+
+
+### Filtering contacts
+**Purpose:** Allow user to filter contacts based on criteria given.
+
+Formats:
+1. `filter n/NAME `
+2. `filter p/PHONE_NUMBER`
+3. `filter e/EMAIL_ADDRESS`
+4. `filter d/DESCRIPTION`
+5. `filter t/TAG`
+
+#### Implementation
+
+The implementation of this feature requires 'FilterCommand' and 'FilterCommandParser'.
+
+Below is an activity diagram that shows what happens when a user executes the `filter` command
+
+
+
+### Viewing contacts
+**Purpose:** Allow user to view contacts that are hidden by default based on index given
+
+Formats:
+1. `view `
+2. `view ...`
+
+#### Implementation
+
+The implementation of this feature requires `ViewCommand`, `ViewCommandParser` and `MatchNamePredicate`.
+
+Below is an activity diagram that shows what happens when a user executes the `view` command.
+
+
+
+### Export contacts
+**Purpose:** Allow user to export contacts that are hidden by default based on index given
+
+Formats:
+1. `export `
+2. `export ...`
+
+#### Implementation
-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.
+The implementation of this feature requires `ExportCommand` and `ExportCommandParser`.
-
+Below is an activity diagram that shows what happens when a user executes the `export` command.
-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.
+
-
-The following activity diagram summarizes what happens when a user executes a new command:
+### Load contacts
+**Purpose**: Allows users to load data files generated by another ModCheck (either through `export` or copy-pasting
+from _data/addressbook.json_).
-
+Formats:
+1. `load`
+2. `load `
-#### Design considerations:
+**Implementation**: The behaviour of the `load` command is to open a FileChooser window of the current OS. Only
+json files are able to be selected by this FileChooser window. After the user selects a file, the absolute path of
+the file will be returned by the FileChooser. This path is appended onto the `load` command, which is subsequently
+parsed by the AddressBookParser.
-**Aspect: How undo & redo executes:**
+The parsed command will be executed by Logic. During execution, the LoadCommand object parses and reads the input
+data file, and returns an AddressBook. This is then combined with the current working address book using the
+`Model#combine` method.
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+This implementation requires cooperation between the Ui and Logic components of ModCheck, which is achieved using a
+UiInputRequiredException thrown by the parser. This exception is caught by Ui, which is then responsible for showing
+the FileChooser window. This is done so the [architecture](#architecture) of ModCheck is not violated.
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+The possible paths of the `load` command is shown in the activity diagram below.
-_{more aspects and alternatives to be added}_
+
-### \[Proposed\] Data archiving
-_{Explain here how the data archiving feature will be implemented}_
+### Dark/Light mode
+**Purpose**: Allows users to change to their favourite theme
+
+Formats:
+1. `light`
+2. `dark`
+
+**Implementation**:
+
+The implementation of this feature requires `LightCommand` and `DarkCommand`.
+
+ModCheck will save the user's favorite theme automatically. When the user exits the app, the handleExit() method in the MainWindow class will save the user last used theme to preferences.json so that when the user returns to the app. The favorite theme will still remain.
+
+Below is an activity diagram that shows what happens when a user executes the `light or dark` command
+
+
--------------------------------------------------------------------------------------------------------------------
@@ -257,6 +459,7 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
+* students from NUS
* has a need to manage a significant number of contacts
* prefer desktop apps over other types
* can type fast
@@ -270,29 +473,105 @@ _{Explain here how the data archiving feature will be implemented}_
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… |
+|----------|---------|-----------------------------------------------------|------------------------------------------------------------------------|
+| `* * *` | Student | see usage instructions | refer to instructions when I forget how to use the App |
+| `* * *` | Student | add a new contact | |
+| `* * *` | Student | delete a contact | remove a contact that I no longer need |
+| `* * *` | Student | view a person's contact details | contact the person(TA/Professor) to seek help for my tutorials |
+| `* * *` | Student | filter a contact by name | locate details of persons without having to go through the entire list |
+| `* * *` | Student | filter my contacts by tag | find my contacts that is related to the tag quickly |
+| `* * *` | Student | filter my contacts by module | find all the relevant contacts of a module I am taking |
+| `* * *` | Student | filter my contacts by description | find all contacts with the matching description |
+| `* * *` | Student | filter my contacts by phone number | find all contacts that has the phone number |
+| `* * *` | Student | edit a contact | update the contact details of my contacts when they change |
+| `* * *` | Student | assign modules to my contacts | know which of my contacts are in charge of which modules |
+| `* *` | Student | login into ModCheck | access ModCheck privately |
+| `* *` | Student | create a new password | secure ModCheck and prevent other unwanted users from using it |
+| `* *` | Student | undo my last command | reverse my actions if i made a wrong change to ModCheck |
+| `* *` | Student | be able to set certain fields as 'unknown' | add contacts that I may not know all the details of |
+| `* *` | Student | hide private contact details | minimize chance of someone else seeing them by accident |
+| `* *` | Student | delete all contacts | remove all contacts for a fresh start |
+| `*` | Student | use the arrow keys to re-enter my previous commands | enter recently used commands much faster |
+| `*` | Student | export selected contacts | transfer those contacts easily |
### 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 `ModCheck` and the **Actor** is the `Student`, unless specified otherwise)
+
+#### Use case: Login
+
+**MSS**
+
+1. ModCheck displays default login screen
+2. ModCheck requests for Student's password
+3. Student enters password
+4. ModCheck displays loading screen
+ Use case ends.
+
+**Extensions**
+
+* 1a. ModCheck detects that student is a first time user.
+ * 1a1. ModCheck displays welcome screen.
+ * 1a2. ModCheck request for student desire to create password.
+ * 1a3. Student request to create password.
-**Use case: Delete a person**
+ Use case ends.
+
+* 3a. ModCheck detects that password given is wrong.
+ * 3a1. ModCheck displays error message.
+
+ Use case resumes at step 2.
+
+#### Use case: Create password
+
+**MSS**
+
+1. ModCheck request for Student's new password.
+2. Student enters new password.
+3. ModCheck request for the same password for confirmation.
+4. Student enters password.
+5. ModCheck displays success loading screen.
+
+ Use case ends.
+
+**Extensions**
+
+* 4a. ModCheck detects that the confirmation password is not similar to the first password.
+ * 4a1. ModCheck displays error message
+
+ Use case resumes at step 4.
+
+#### Use case: Add a person
**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. Student requests to add person.
+2. Student enters the contact details and submits.
+3. ModCheck creates a new person.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. Person already exists in ModCheck.
+ * 2a1. ModCheck shows an error message.
+ Use case continues at step 2.
+* 2b. Contact details is missing the field NAME.
+ * 2b1. ModCheck shows an error message.
+ Use case continues at step 2.
+* 2c. Contact details given is incorrect
+ * 2c1. ModCheck shows an error message.
+ Use case continues at step 2.
+
+#### Use case: Delete a single person by Index
+
+**MSS**
+
+1. Student requests to list persons
+2. ModCheck shows a list of persons
+3. Student requests to delete a specific person in the list
+4. ModCheck deletes the person
Use case ends.
@@ -304,25 +583,285 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+ * 3a1. ModCheck shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+
+#### Use case: Delete multiple persons by multiple indexes
+
+**MSS**
+
+1. Student requests to list persons
+2. ModCheck shows a list of persons
+3. Student requests to delete multiple persons in the list
+4. ModCheck deletes the persons
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. One of the given indexes is invalid.
+
+ * 3a1. ModCheck shows an error message.
+
+ Use case resumes at step 2.
+
+#### Use case: Delete single person by name
+
+**MSS**
+
+1. Student requests to list persons
+2. ModCheck shows a list of persons
+3. Student requests to delete person in the list by their name.
+4. ModCheck deletes the person
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. There is no person with the specified name.
+
+ * 3a1. ModCheck shows an error message.
+
+ Use case resumes at step 2.
+
+
+* 3b. There are multiple persons with the same name.
+
+ * 3b1. ModCheck shows an error message with a list of persons with the same name.
+
+ Use case ends.
+
+#### Use case: Filter contacts
+
+**MSS**
+
+1. Student requests to filter contacts with the required details
+2. ModCheck shows a list of persons based on details given
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. There are no contacts that matches the details given
+
+ * 2a1. ModCheck shows an error message.
+
+ Use case ends.
+
+
+#### Use case: Export contacts
+
+**MSS**
+
+1. Student requests to export selected contact
+2. ModCheck shows a list of persons selected
+3. ModChecks exports selected contacts
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Student requests to export multiple contacts
+ * Use case resumes at step 2.
+
+* 1b. Student provide invalid request to export contacts
+ * 1b1. ModCheck shows an error message.
+ * Use case ends.
+
+#### Use case: Load contacts
+
+**MSS**
+
+1. Student requests ModCheck to load contacts.
+2. ModCheck shows a FileChooser and requests student to choose a file to load from.
+3. Student selects a file to load.
+4. ModCheck loads the contacts in the file into its database.
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. Student requests to load an invalid file.
+ * 3a1. ModCheck shows an error message.
+
+ Use case ends.
+
+* 3b. Student does not choose a file to load.
+ * 3b1. ModCheck shows an error message.
+
+ Use case ends.
+
+
+#### Use case: View a person's contact details
+
+**MSS**
+
+1. Student requests to list contacts.
+2. ModCheck shows a list of contacts.
+3. Student requests to view a specific contact in the list.
+4. ModCheck displays the person's contact details.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. ModCheck shows an error message.
+
+ Use case ends.
+
+* 3b. User requests to view multiple people in the list.
+
+ * 3b1. ModCheck validates indexes given is correct.
+ * 3b2. ModCheck displays all person's contact details.
+
+ Use case ends.
+
+ * 3b1a. ModCheck validates indexes given is incorrect.
+ * 3b1b. ModChecks shows an error message.
+
+ Use case ends.
+
+
+#### Use case: Edit a contact by index
+
+**MSS**
+
+1. Student requests to list contacts.
+2. ModCheck shows a list of contacts.
+3. Student requests to edit a contact supplying new values for certain fields
+4. ModCheck finds the relevant contact by index and edits it
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a The new value for the specified field is invalid
+ * 3a1. ModCheck informs student that the given value for the specified field is invalid
+
+ Use case resumes at step 2.
+
+* 4a. The given index is invalid.
+
+ * 4a1. ModCheck shows an error message.
+
+ Use case resumes at step 2.
+
+
+#### Use case: Edit a contact by name
+
+**MSS**
+
+1. Student requests to list contacts.
+2. ModCheck shows a list of contacts.
+3. Student requests to edit a contact supplying new values for certain fields
+4. ModCheck finds the relevant contact by name and edits it
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a The new value for the specified field is invalid
+ * 3a1. ModCheck informs student that the given value for the specified field is invalid
+
+ Use case resumes at step 2.
+
+* 4a. The contact does not exist
+ * 4a1. ModCheck informs student that contact does not exist
+
+ Use case resumes at step 2
+
+* 4b. There are multiple contacts with the same specified name.
+ * 4b1. ModCheck informs student that there are multiple contacts with the same specified name, and returns a list of contacts with the same specified name.
+
+ Use case ends.
+
+
+
+
+#### Use case: Switch to light or dark mode
+
+**MSS**
+
+1. Student requests to change to light or dark mode
+2. ModCheck displays the selected outcome
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Already in the selected theme.
+ * 1a1. ModCheck shows an error message.
+
+ Use case ends.
+
+
+#### Use case: Undo commands
+
+**MSS**
+
+1. Student makes a request to undo a command.
+2. ModCheck undoes the most recent command.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. There is no command to be undone.
+ * 3a1. ModCheck shows an error message.
+
+ Use case ends.
+
### Non-Functional Requirements
1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+3. A student 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.
+4. A student should be able to learn and use commands provided by MODCheck easily within an hour of usage.
+5. Should be able to handle exceptions, errors and invalid inputs without crashing.
+6. Should be able to still work even if there is no data file present.
+7. Should be able to function offline - data file and features should work without network connection
+8. Data file should be human-readable and editable with a simple text editor
*{More to be added}*
### Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
+* **Contact**: An entry in ModCheck describing a person, consisting of a name and fields containing contact details of
+ that person
+* **Contact details**: Information regarding a person's phone number, email, office location, telegram, LinkedIn, or
+ any other information relevant for students to communicate with that person
* **Private contact detail**: A contact detail that is not meant to be shared with others
-
+* **TA**: Teaching Assistant - people who assists Professors in conducting tutorial, labs and other classes
+* **GUI**: Graphical User Interface - a system of interactive visual components for computer software
+* **CLI**: Command Line Interface - a text-based user interface (UI) used to run programs, manage computer files and interact with the computer
+* **Index**: A number indicating the order or position in a given list
+* **Person**: Referring to TA or Professor
--------------------------------------------------------------------------------------------------------------------
## **Appendix: Instructions for manual testing**
@@ -359,6 +898,12 @@ testers are expected to do more *exploratory* testing.
1. Test case: `delete 1`
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+
+ 1. Test case: `delete 1,2,3`
+ Expected: First, second and third contacts are deleted from the list if there are at least three contacts in the list. Success shown in status message.
+
+ 1. Test case: `delete Amy`
+ Expected: Amy is deleted from the list if there is only one contact named Amy. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
1. Test case: `delete 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
@@ -366,12 +911,36 @@ testers are expected to do more *exploratory* testing.
1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
+### Viewing a person's contact details
-### Saving data
+1. Viewing a person while all persons are being shown
-1. Dealing with missing/corrupted data files
+ 1. Prerequisites: List all persons using the `view` command. Multiple persons in the list. Contact details not hidden
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Test case: `view 1`
+ Expected: The contact details of the first person in the list is displayed in ModCheck.
+
+ 2. Test case: `view 1 3 4 `
+ Expected: The contact details of the first, third and fourth person in the list is displayed in ModCheck.
-1. _{ more test cases … }_
+ 3. Test case: `view 0`
+ Expected: No person's contact details is displayed. Error details shown in the status message. Status bar remains the same.
+
+ 4. Other incorrect delete commands to try: `view`, `view x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+## **Appendix: Planned Enhancements**
+
+1. `Login feature` will include two other important details that ModCheck will have to capture which is the Student's
+ `username` and a ` question dropdown` such as `What is you best friend name`, `What is your pet name`, `What is your favourite food` etc.
+ so that `Reset Password` feature can be realistically done to allow Students to reset their password safely.
+2. `Reset password feature` to be included in the future to allow Students to reset their password in case they forgot their password.
+3. Hashing of password will be done in the future as current requirements require the data to be readable by human.
+4. `Load` command will include an option to **override** instead of **ignore**, this is to allow the user to update
+ contacts if they know that the data file to be loaded is more recent.
+
+## **Appendix: Known Limitations**
+
+1. `Login feature` does not provide a way for Student to reset password as it is very hard to identify if the user that wants to reset the password is actually the correct user using ModCheck.
+2. The Student is expected to reset the password by going through `userData.json` file and modifying the `hashedPassword` variable directly.
+3. Hashing was not implemented as the Student is only able to reset the password by modifying `userData.json` which requires the data to be humanly readable.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index e7df68b01ea..267dc2dfc39 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,47 +3,91 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+## Table of contents
+1. [ModCheck](#1-modcheck)
+2. [Quick Start](#2-quick-start)
+3. [Features](#3-features)
+ * [Notes about the command format](#3-features)
+ * [Logging in](#32-logging-in)
+ * [Creating Password](#321-creating-password)
+ * [Viewing help](#33-viewing-help--help)
+ * [Adding a person](#34-adding-a-person-add)
+ * [Listing all persons](#341-listing-all-persons--list)
+ * [Viewing a person](#342-viewing-a-persons-contact-details--view)
+ * [Editing a person](#343-editing-a-person--edit)
+ * [Deleting a person](#344-deleting-a-person--delete)
+ * [Filtering persons](#345-filtering-contacts--filter)
+ * [Undo commands](#346-undo-past-commands-undo)
+ * [Exporting](#35-exporting-selected-persons-contact-details--export)
+ * [Importing](#351-load-another-data-file-load)
+ * [Dark/Light mode](#36-darklight-mode-dark--light)
+ * [Clear](#37-clearing-all-entries--clear)
+ * [Saving the data](#371-saving-the-data)
+ * [Editing the data](#372-editing-the-data-file)
+ * [Exit](#38-exiting-the-program--exit)
+4. [FAQ](#4-faq)
+5. [Command Summary](#5-command-summary)
-* Table of Contents
-{:toc}
+--------------------------------------------------------------------------------------------------------------------
+## 1. ModCheck
+MODCheck 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, MODCheck can get your contact management tasks done faster than traditional GUI apps.
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## 2. Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
-
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
-
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+2. The first GUI you will encounter is the `ModCheck Login`.
+3. If it is the first time that you are using MODCheck, you will be greeted with the GUI below.
+ 
+
+ First Time User Welcome GUI
+
+
+
+4. Enter `yes` if you want to create a password or `no` if you want to continue to MODCheck main application. Further explanation on the `Login` feature will be described further down under `Features`.
+5. You will be greeted with one of GUI shown below that represents the `loading` screen before entering MODCheck's main application depending on the choice you have made earlier.
+ 
+
+
+
+6. A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.

+
+ MODCheck Main GUI
+
+
-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.
+7. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try:
- * `list` : Lists all contacts.
+ * `list` : Lists all contacts.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * `add n/John Doe p/98765432 e/johnd@example.com d/Friendly` : Adds a contact named `John Doe` to ModCheck.
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+ * `delete 3` : Deletes the 3rd contact shown in the current list.
- * `clear` : Deletes all contacts.
+ * `clear` : Deletes all contacts.
- * `exit` : Exits the app.
+ * `exit` : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+8. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
-## Features
+## 3. Features
-**:information_source: Notes about the command format:**
+### 3.1 :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`.
@@ -63,9 +107,49 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
* 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`.
+* :warning: Unrecognised fields such as `b/` or `c/` will not be picked up as fields, and will be treated as input.
+
-### Viewing help : `help`
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.2 Logging in
+
+Login to MODCheck with given passsword that may be empty.
+
+Format: `[Password]`
+
+`[Password]`: Password of user.
+
+> :bulb: **Tip:** User can leave the textbox empty and press `Enter` to enter inside MODCheck's main application if user did not create a password to secure MODCheck.
+
+
+
+
+Default Login GUI
+
+
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.2.1 Creating Password
+
+Creates a password to secure MODCheck from other unwanted users
+
+
+
+
+Create Password GUI
+
+
+
+Format: `[Password]`
+
+`[Password]`: Password of user.
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.3 Viewing help : `help`
Shows a message explaning how to access the help page.
@@ -73,121 +157,285 @@ Shows a message explaning how to access the help page.
Format: `help`
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.4 Adding a person: `add`
-### Adding a person: `add`
+Adds a person contact details to ModCheck.
-Adds a person to the address book.
+Compulsory Field:
+* `n/` : name of the person
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+Additional Fields:
+* `d/` : description of the person
+* `e/` : email
+* `p/` : phone number
+* `t/` : tags
+* `m/` : module tags
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+> :bulb: **Tip:** A person can have any number of tags or modules (including 0). The order of the fields is not important.
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `add n/John d/Important friend e/leomessi@psg.com p/98101010`
+* `add n/Gray d/Coolest Prof ever e/SIUUUUUU@gmail.com p/98070707 t/Prof m/CS2103 m/CS3230`
+
+--------------------------------------------------------------------------------------------------------------------
-### Listing all persons : `list`
+### 3.4.1 Listing all persons : `list`
-Shows a list of all persons in the address book.
+Shows a list of all persons in ModCheck.
Format: `list`
-### Editing a person : `edit`
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.4.2 Viewing a person's contact details : `view`
+
+View a person's contact details.
+
+Format: `view `
+
+Examples:
+* `view 1` returns the contact details of the first person in the list
+* `view 1 3` returns the contact details of the first and third person in the list
+
+
+
+--------------------------------------------------------------------------------------------------------------------
-Edits an existing person in the address book.
+### 3.4.3 Editing a person : `edit`
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Edits an existing person in ModCheck.
-* 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, …
+Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [d/DESCRIPTION] [t/TAG]… [m/MODULE_TAG]…` or `edit NAME [n/NAME] [p/PHONE] [e/EMAIL] [a/DESCRIPTION] [t/TAG]… [m/MODULE_TAG]…`
+
+* Edits the person at the specified `INDEX` or `NAME`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
+* `edit` followed by a positive integer will be interpreted as an index, and not a name.
* 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.
+ specifying any tags after it.
+* When editing by `NAME`, if there are multiple people with the same specified name, no edit will be done, and a list of people with those specified name will be returned.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `edit Bernice p/91164512` Edits the phone number of the Bernice to be `91164512`.
+* `edit 3 p/90011009 e/bernice512@example.com` Edits the phone number and email address of the 3rd person to be
+ 90011009 and bernice512@example.com respectively
+* `edit 1 p/91164512` Edits the phone number of the 1st person to be `91164512`.
+ 
-### Locating persons by name: `find`
+--------------------------------------------------------------------------------------------------------------------
-Finds persons whose names contain any of the given keywords.
+### 3.4.4 Deleting a person : `delete`
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Deletes the specified person from ModCheck.
+
+Format: `delete INDEX` or `delete INDEXES` or `delete NAME`
+
+* Deletes the person at the specified `INDEX`.
+* The index refers to the index number shown in the displayed person list.
+* Any index **must be a positive integer** …
+* When deleting multiple persons with indexes, each index must be separated by a comma, without any whitespace between any index.
+* `delete` followed by a positive integer will be interpreted as an index, and not a name.
+* When deleting by `NAME`, if there are multiple people with the same specified name, no deletion will be done, and a list of people with those specified name will be returned.
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
+* `list` followed by `delete Betsy` deletes Betsy if there is only one Betsy in the list.
+* `list` followed by `delete 1,2,3` deletes the 1st, 2nd and 3rd person in the list. However, `list` followed by `delete 1,2, 3` is invalid due to the whitespace between index 2 and 3.
+* `list` followed by `delete 3` deletes the 3rd person in the list.
-### Deleting a person : `delete`
+
-Deletes the specified person from the address book.
+--------------------------------------------------------------------------------------------------------------------
-Format: `delete INDEX`
+### 3.4.5 Filtering contacts : `filter`
-* 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, …
+Filters the contacts based on the arguments provided.
+
+Formats:
+1. `filter n/NAME`
+2. `filter p/PHONE_NUMBER`
+3. `filter e/EMAIL_ADDRESS`
+4. `filter d/DESCRIPTION`
+5. `filter t/TAG`
+6. `filter m/MODULE_TAG`
+
+Examples:
+
+* `filter n/Alex` returns `1 contacts listed!`
+* `filter n/Alex Bernice` returns `2 contacts listed!`
+* 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
+ 
+* `filter p/91031282` returns `1 contacts listed!`
+ 
+* `filter e/royb@example.com` returns `1 contacts listed!`
+ 
+* `filter d/helpful` returns `1 contacts listed!`
+* `filter d/helpful Newgate` returns `2 contacts listed!`
+* The search is case-sensitive. e.g Helpful will match Helpful and not helpful
+* The order of the keywords does not matter. e.g. Helpful Newgate will match Newgate Helpful
+* Only the description is searched.
+* Only full words will be matched.
+* Contacts matching at least one keyword will be returned (i.e. OR search). e.g. Helpful roommate will return Helpful, Helpful friend, lazy roomate
+ 
+* `filter t/family` returns `1 contacts listed`
+* `filter t/family t/friends t/classmates` returns `3 contacts listed!`
+ 
+* `filter m/CS2103` returns `1 contacts listed`
+ 
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.4.6 Undo past commands `undo`
+
+
+Undoes previous commands that modified ModCheck.
+
+Undo will only undo commands that have successfully modified the data in ModCheck. For example, a successful `add`,
+`edit`, or `delete` command can be undone by the undo command.
+
+Any commands that does not modify the data in ModCheck will NOT be undone. This includes `view`, `find`, and other
+similar commands. Any command that would have modified the data in ModCheck, but was unsuccessful in doing so (eg:
+`add` duplicate person), will NOT be undone.
+
+Chaining of a few undo commands is supported. Once the undo limit has been reached, the error message `No command to
+undo!` will be shown. The undo limit is set to 4 by default.
+
+Format: `undo`
+
+Use `redo` to reapply the changes undone by undo.
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.4.7 Redo previously undone commands `redo`
+
+Redoes previously undone commands.
+
+Redo will reapply any changes previously undone by `undo`. The behaviour of `redo` when interacting with other
+commands is as follows:
+- If no `undo` command was previously used, then `redo` will show an error message, and will not change the state of
+ ModCheck.
+- If a command that changes the data of ModCheck is executed while ModCheck is in an undone state _(eg: `undo` ->
+ `edit 1 t/`)_, the `redo` command will be reset. Using the `redo` command will not redo commands that was
+ undone before the modification commands were made. This behaviour of `redo` is consistent with other `undo` and
+ `redo` features found in most commercial software today.
+- Chaining of several `redo` commands, to redo chained `undo` commands, is supported.
+
+Format: `redo`
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.5 Exporting selected person's contact details : `export`
+
+Export a person's contact details.
+
+Format: `export `
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.
+* `export 1` exports the contact details of the first person in the list
+* `export 1 4` exports the contact details of the first and fourth person in the list
+
+Exported contacts will be stored inside a Json file under the `exports` folder found in the same directory as ModCheck
+
+
+
+--------------------------------------------------------------------------------------------------------------------
-### Clearing all entries : `clear`
+### 3.5.1 Load another data file `load`
-Clears all entries from the address book.
+`Load` the contacts in another ModCheck data file into the user's ModCheck.
+`Load` will open up a file chooser window, where the user can select the desired json file to be loaded.
+Only json files that are generated by ModCheck can be successfully loaded. If a file that ModCheck is unable to read
+is loaded, an error will be shown.
+
+The user also has the option to directly specify the absolute file path to be loaded in the text box. The use of the
+command in this way is not recommended.
+
+The data files that are ModCheck-readable can be obtained through:
+1. use of `export` command
+2. copying the data file in data/addressbook.json
+
+Note: If there are duplicate persons (ie: persons with the same name but possibly different fields) in ModCheck and the
+data file to be loaded, the person will be ignored instead of overwritten.
+
+Format: `load` OR `load `
+
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.6 Dark/Light Mode `dark` / `light`
+
+Choose your favourite theme !
+
+`light` To change to light mode.
+
+`dark` To change to dark mode. (default)
+
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.7 Clearing all entries : `clear`
+
+Clears all entries from ModCheck.
Format: `clear`
-### Exiting the program : `exit`
+Examples:
+* `list` followed by `clear` deletes all the contacts in the list.
-Exits the program.
+--------------------------------------------------------------------------------------------------------------------
-Format: `exit`
+### 3.7.1 Saving the data
-### Saving the data
+MODCheck data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+--------------------------------------------------------------------------------------------------------------------
-### Editing the data file
+### 3.7.2 Editing the data file
-AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+MODCheck data are saved as a JSON file. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run.
+If your changes to the data file makes its format invalid, MODCheck will discard all data and start with an empty data file at the next run.
-### Archiving data files `[coming in v2.0]`
+--------------------------------------------------------------------------------------------------------------------
+
+### 3.8 Exiting the program : `exit`
+
+Exits the program.
-_Details coming soon ..._
+Format: `exit`
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## 4. 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. `load` the contents of the previous data file into your new ModCheck.
--------------------------------------------------------------------------------------------------------------------
-## 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`
+## 5. Command summary
+
+| Action | Format, Examples |
+|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL d/DESCRIPTION [t/TAG]… [m/MODULE_TAG]… ` e.g., `add n/Benedict Tan d/Great Friend e/BenedictTan@gmail.com p/98070707 t/Friend m/CS2103 m/CS3230 ` |
+| **View** | `view INDEX` e.g., `view 2` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` or `delete INDEXES` or `delete NAME` e.g., `delete 3` or `delete 1,2,3` or `delete James` |
+| **Edit** | `edit {INDEX or NAME} [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com` or `edit James e/jameslee@example.com` | |
+| **List** | `list` |
+| **Help** | `help` |
+| **Filter** | `filter n/NAME` `filter p/PHONE_NUMBER` `filter e/EMAIL_ADDRESS` `filter d/DESCRIPTION` `filter t/TAG` `filter m/MODULE_TAG` e.g. `filter n/Alex` e.g. `filter p/91031282` e.g. `filter e/royb@example.com` e.g. `filter d/helpful` e.g. `filter t/family` e.g. `filter m/CS2103` |
+| **Undo** | `undo` |
+| **Redo** | `redo` |
+| **Load** | `load` OR `load ` |
+| **Export** | `export INDEX` e.g., `export 2` |
+| **Light** | `light` |
+| **Dark** | `dark` |
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..19b72e7b92d 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "MODCheck"
theme: minima
header_pages:
diff --git a/docs/diagrams/CreatePassword.puml b/docs/diagrams/CreatePassword.puml
new file mode 100644
index 00000000000..1c8cb810589
--- /dev/null
+++ b/docs/diagrams/CreatePassword.puml
@@ -0,0 +1,16 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:ModCheck requests Student to enter a new password;
+:Student enters a new password;
+:ModCheck requests Student to re-enter password;
+:Student re-enters password;
+if () then ([passwords do not match])
+ :ModCheck displays error message;
+ :Student enters both passwords correctly;
+else ([else])
+ endif
+stop
+
+@enduml
diff --git a/docs/diagrams/LoadCommandActivityDiagram.puml b/docs/diagrams/LoadCommandActivityDiagram.puml
new file mode 100644
index 00000000000..c86bae9a667
--- /dev/null
+++ b/docs/diagrams/LoadCommandActivityDiagram.puml
@@ -0,0 +1,35 @@
+@startuml
+start
+:Load command executed;
+:Arguments given?;
+if () then ([no arguments])
+ :throw UiInputRequired exception;
+ :Ui component catches the exception
+ and shows a file chooser;
+ if () then ([file chosen])
+ :Append file path
+ onto load command;
+ else ([else])
+ :Append "!"
+ onto load command;
+ endif
+ :Reexecute load command with
+ arguments given;
+else ([arguments given])
+endif
+
+if () then ([argument =="!"])
+ :No file chosen;
+ :throw CommandException;
+
+else ([argument == path])
+ :Path given;
+ if () then ([path is valid])
+ :Add data to ModCheck;
+ else ([else])
+ :throw CommandException;
+ endif
+endif
+end
+@enduml
+
diff --git a/docs/diagrams/Login.puml b/docs/diagrams/Login.puml
new file mode 100644
index 00000000000..a4e9298e938
--- /dev/null
+++ b/docs/diagrams/Login.puml
@@ -0,0 +1,28 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+'idea of rake provided @WillySeahh in https://github.com/nus-cs2103-AY1920S2/forum/issues/105
+
+start
+:ModCheck checks number of times Student visited ModCheck;
+if () then ([Student visited more than once])
+ :ModCheck displays default login screen;
+ :ModCheck requests Student to login;
+ :Student enters password;
+ if () then ([password is correct])
+ else ([else])
+ :ModCheck displays error message;
+ :Student re-enters correct password;
+ endif
+else ([else])
+ :ModCheck displays welcome screen;
+ :ModCheck requests Student to create a password;
+ if () then ([Student wants to create password])
+ :Student creates password (@rake CreatePasswordActivityDiagram);
+ else ([else])
+ endif
+endif
+ :ModCheck displays loading screen;
+stop
+
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 4439108973a..4e8ef51f886 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -4,13 +4,20 @@ skinparam arrowThickness 1.1
skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
-Package Model <>{
-Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
-Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
-Class "<>\nModel" as Model
-Class AddressBook
+Package Model\nClasses <>{
+together {
+ Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
+ Class "<>\nModel" as Model
+ Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
+ Class "<>\nReadOnlyUserData" as ReadOnlyUserData
+}
Class ModelManager
-Class UserPrefs
+together {
+ Class AddressBook
+ Class UserPrefs
+ Class UserData
+ Class UndoManager
+}
Class UniquePersonList
Class Person
@@ -26,12 +33,16 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
AddressBook .up.|> ReadOnlyAddressBook
+UserData .up.|> ReadOnlyUserData
ModelManager .up.|> Model
-Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
-ModelManager -left-> "1" AddressBook
-ModelManager -right-> "1" UserPrefs
+Model .down.> ReadOnlyUserPrefs
+Model .down.> ReadOnlyAddressBook
+Model .down.> ReadOnlyUserData
+ModelManager -down-> "1" AddressBook
+ModelManager -down-> "1" UserPrefs
+ModelManager -down-> "1" UserData
+ModelManager-down-> "1" UndoManager
UserPrefs .up.|> ReadOnlyUserPrefs
AddressBook *--> "1" UniquePersonList
@@ -46,5 +57,5 @@ Name -[hidden]right-> Phone
Phone -[hidden]right-> Address
Address -[hidden]right-> Email
-ModelManager -->"~* filtered" Person
+ModelManager --> "~* filtered" Person
@enduml
diff --git a/docs/diagrams/RedoSequenceDiagram.puml b/docs/diagrams/RedoSequenceDiagram.puml
new file mode 100644
index 00000000000..5d636aa018d
--- /dev/null
+++ b/docs/diagrams/RedoSequenceDiagram.puml
@@ -0,0 +1,42 @@
+@startuml
+!include style.puml
+
+
+participant ":AddCommand" as AddCommand LOGIC_COLOR
+
+box Model MODEL_COLOR_T1
+participant ":ModelManager" as Model MODEL_COLOR
+participant ":AddressBook" as AddressBook MODEL_COLOR
+participant ":UndoManager" as UndoManager MODEL_COLOR
+participant "addressBookHistory:LinkedList" as History MODEL_COLOR
+end box
+
+AddCommand -> Model : addPerson()
+activate Model
+
+Model -> AddressBook : addPerson()
+activate AddressBook
+
+AddressBook --> Model
+deactivate AddressBook
+
+Model -> UndoManager : addToHistory()
+activate UndoManager
+
+opt isInUndoneState
+UndoManager -> UndoManager : deleteUntrackedHead()
+end
+
+UndoManager --> History : offerFirst(AddressBook)
+activate History
+
+History --> UndoManager
+deactivate History
+
+UndoManager --> Model
+deactivate UndoManager
+
+Model --> AddCommand : result
+deactivate Model
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 760305e0e58..5b42cd1f561 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -4,22 +4,29 @@ skinparam arrowThickness 1.1
skinparam arrowColor STORAGE_COLOR
skinparam classBackgroundColor STORAGE_COLOR
-package Storage{
-
-package "UserPrefs Storage" #F4F6F6{
-Class "<>\nUserPrefsStorage" as UserPrefsStorage
-Class JsonUserPrefsStorage
-}
-
+package Storage\nClasses {
Class "<>\nStorage" as Storage
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Class "<>\nAddressBookStorage" as AddressBookStorage
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
-Class JsonAdaptedTag
+together {
+
+ package "UserPrefs Storage" #F4F6F6{
+ Class "<>\nUserPrefsStorage" as UserPrefsStorage
+ Class JsonUserPrefsStorage
+ }
+
+ package "AddressBook Storage" #F4F6F6{
+ Class "<>\nAddressBookStorage" as AddressBookStorage
+ Class JsonAddressBookStorage
+ Class JsonSerializableAddressBook
+ Class JsonAdaptedPerson
+ Class JsonAdaptedTag
+ }
+
+ package "UserData Storage" #F4F6F6 {
+ Class "<>\nUserDataStorage" as UserDataStorage
+ Class JsonUserDataStorage
+ }
}
}
@@ -27,15 +34,18 @@ Class JsonAdaptedTag
Class HiddenOutside #FFFFFF
HiddenOutside ..> Storage
-StorageManager .up.|> Storage
-StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager .up...|> Storage
+StorageManager -down-> "1" UserPrefsStorage
+StorageManager -down-> "1" AddressBookStorage
+StorageManager -down-> "1" UserDataStorage
-Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -down-|> UserPrefsStorage
+Storage -down-|> AddressBookStorage
+Storage -down-|> UserDataStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
+JsonUserDataStorage .up.|> UserDataStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..a4cfd33a739 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -8,13 +8,22 @@ package UI <>{
Class "<>\nUi" as Ui
Class "{abstract}\nUiPart" as UiPart
Class UiManager
-Class MainWindow
-Class HelpWindow
-Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
-Class StatusBarFooter
-Class CommandBox
+together {
+ Class MainWindow
+ Class LoginWindow
+}
+together {
+ Class HelpWindow
+ Class ResultDisplay
+ Class PersonListPanel
+ Class PersonCard
+ Class StatusBarFooter
+ Class CommandBox
+}
+Class WelcomeSection
+Class LoadingSection
+Class CreatePasswordSection
+Class DefaultLoginSection
}
package Model <> {
@@ -28,17 +37,30 @@ Class HiddenLogic #FFFFFF
Class HiddenOutside #FFFFFF
HiddenOutside ..> Ui
-UiManager .left.|> Ui
-UiManager -down-> "1" MainWindow
+UiManager .up.|> Ui
+UiManager -right-> "1" MainWindow
+UiManager -down-> "1" LoginWindow
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
+LoginWindow *-down-> "1" WelcomeSection
+LoginWindow *-down-> "1" LoadingSection
+LoginWindow *-down-> "1" CreatePasswordSection
+LoginWindow *-down-> "1" DefaultLoginSection
+
+WelcomeSection --|> UiPart
+LoadingSection --|> UiPart
+CreatePasswordSection --|> UiPart
+DefaultLoginSection --|> UiPart
+
+
PersonListPanel -down-> "*" PersonCard
MainWindow -left-|> UiPart
+LoginWindow -down-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
@@ -49,7 +71,8 @@ HelpWindow --|> UiPart
PersonCard ..> Model
UiManager -right-> Logic
-MainWindow -left-> Logic
+MainWindow -right-> Logic
+LoginWindow -right-> Logic
PersonListPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
index 410aab4e412..bede81138fe 100644
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ b/docs/diagrams/UndoSequenceDiagram.puml
@@ -2,52 +2,39 @@
!include style.puml
box Logic LOGIC_COLOR_T1
-participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
-participant "u:UndoCommand" as UndoCommand LOGIC_COLOR
+participant ":UndoCommand" as UndoCommand LOGIC_COLOR
end box
box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
+participant ":ModelManager" as Model MODEL_COLOR
+participant ":UndoManager" as UndoManager MODEL_COLOR
+participant ":AddressBook" as AddressBook MODEL_COLOR
end box
-[-> LogicManager : execute(undo)
-activate LogicManager
-LogicManager -> AddressBookParser : parseCommand(undo)
-activate AddressBookParser
-
-create UndoCommand
-AddressBookParser -> UndoCommand
+[-> UndoCommand : execute()
activate UndoCommand
-UndoCommand --> AddressBookParser
-deactivate UndoCommand
+UndoCommand -> Model : executeUndo()
+activate Model
-AddressBookParser --> LogicManager : u
-deactivate AddressBookParser
+Model -> UndoManager : getPreviousHistory()
+activate UndoManager
-LogicManager -> UndoCommand : execute()
-activate UndoCommand
+UndoManager --> Model : history
+deactivate UndoManager
-UndoCommand -> Model : undoAddressBook()
-activate Model
-
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
+Model -> AddressBook : resetData(history)
+activate AddressBook
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
+AddressBook --> Model
+deactivate AddressBook
-Model --> UndoCommand
+Model --> UndoCommand : undoneCommand:String
deactivate Model
-UndoCommand --> LogicManager : result
+[<--UndoCommand
deactivate UndoCommand
-UndoCommand -[hidden]-> LogicManager : result
+
destroy UndoCommand
-[<--LogicManager
-deactivate LogicManager
@enduml
diff --git a/docs/diagrams/commands/FilterCommand/FilterActivityDiagram.puml b/docs/diagrams/commands/FilterCommand/FilterActivityDiagram.puml
new file mode 100644
index 00000000000..fd879e09921
--- /dev/null
+++ b/docs/diagrams/commands/FilterCommand/FilterActivityDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+
+start
+:User wants to filter contacts by detail given;
+:User enters "filter" command;
+if () then ([contacts matches detail])
+ :MODCheck displays contacts;
+else ([else])
+ :MODCheck displays error;
+endif
+stop
+@enduml
diff --git a/docs/images/CreatePassword.png b/docs/images/CreatePassword.png
new file mode 100644
index 00000000000..e19e85c3d5c
Binary files /dev/null and b/docs/images/CreatePassword.png differ
diff --git a/docs/images/EditActivityDiagram.png b/docs/images/EditActivityDiagram.png
new file mode 100644
index 00000000000..53d206d4af0
Binary files /dev/null and b/docs/images/EditActivityDiagram.png differ
diff --git a/docs/images/LoadCommand/LoadCommandActivityDiagram.png b/docs/images/LoadCommand/LoadCommandActivityDiagram.png
new file mode 100644
index 00000000000..759c3e922eb
Binary files /dev/null and b/docs/images/LoadCommand/LoadCommandActivityDiagram.png differ
diff --git a/docs/images/LoadCommand/loadCommandExample.png b/docs/images/LoadCommand/loadCommandExample.png
new file mode 100644
index 00000000000..027d81b791c
Binary files /dev/null and b/docs/images/LoadCommand/loadCommandExample.png differ
diff --git a/docs/images/Login.png b/docs/images/Login.png
new file mode 100644
index 00000000000..61d00227665
Binary files /dev/null and b/docs/images/Login.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 04070af60d8..37519735c42 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 2533a5c1af0..ad0eb0ceca1 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..3c09f0d771b 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..d6b9f3d4f6c 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedo/AddToHistorySequenceDiagram.png b/docs/images/UndoRedo/AddToHistorySequenceDiagram.png
new file mode 100644
index 00000000000..5f2bfea7a26
Binary files /dev/null and b/docs/images/UndoRedo/AddToHistorySequenceDiagram.png differ
diff --git a/docs/images/UndoRedo/RedoSequenceDiagram.png b/docs/images/UndoRedo/RedoSequenceDiagram.png
new file mode 100644
index 00000000000..4b9dce476c2
Binary files /dev/null and b/docs/images/UndoRedo/RedoSequenceDiagram.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState0new.png b/docs/images/UndoRedo/UndoRedoState0new.png
new file mode 100644
index 00000000000..208b0d7fdb2
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState0new.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState1new.png b/docs/images/UndoRedo/UndoRedoState1new.png
new file mode 100644
index 00000000000..41f73a127f0
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState1new.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState2new.png b/docs/images/UndoRedo/UndoRedoState2new.png
new file mode 100644
index 00000000000..7440e4d22bd
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState2new.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState3new.png b/docs/images/UndoRedo/UndoRedoState3new.png
new file mode 100644
index 00000000000..90031fdef51
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState3new.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState4new.png b/docs/images/UndoRedo/UndoRedoState4new.png
new file mode 100644
index 00000000000..366bc118f95
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState4new.png differ
diff --git a/docs/images/UndoRedo/UndoRedoState5new.png b/docs/images/UndoRedo/UndoRedoState5new.png
new file mode 100644
index 00000000000..7ba84945b47
Binary files /dev/null and b/docs/images/UndoRedo/UndoRedoState5new.png differ
diff --git a/docs/images/UndoRedo/UndoSequenceDiagram.png b/docs/images/UndoRedo/UndoSequenceDiagram.png
new file mode 100644
index 00000000000..70930a02079
Binary files /dev/null and b/docs/images/UndoRedo/UndoSequenceDiagram.png differ
diff --git a/docs/images/UndoRedo/undoExample1.png b/docs/images/UndoRedo/undoExample1.png
new file mode 100644
index 00000000000..1da90744517
Binary files /dev/null and b/docs/images/UndoRedo/undoExample1.png differ
diff --git a/docs/images/UndoRedo/undoExample2.png b/docs/images/UndoRedo/undoExample2.png
new file mode 100644
index 00000000000..fffcced0aa5
Binary files /dev/null and b/docs/images/UndoRedo/undoExample2.png differ
diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png
deleted file mode 100644
index 8f7538cd884..00000000000
Binary files a/docs/images/UndoRedoState0.png and /dev/null differ
diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png
deleted file mode 100644
index df9908d0948..00000000000
Binary files a/docs/images/UndoRedoState1.png and /dev/null differ
diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png
deleted file mode 100644
index 36519c1015b..00000000000
Binary files a/docs/images/UndoRedoState2.png and /dev/null differ
diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png
deleted file mode 100644
index 19959d01712..00000000000
Binary files a/docs/images/UndoRedoState3.png and /dev/null differ
diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png
deleted file mode 100644
index 4c623e4f2c5..00000000000
Binary files a/docs/images/UndoRedoState4.png and /dev/null differ
diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png
deleted file mode 100644
index 84ad2afa6bd..00000000000
Binary files a/docs/images/UndoRedoState5.png and /dev/null differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
deleted file mode 100644
index 6addcd3a8d9..00000000000
Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/acerizm.png b/docs/images/acerizm.png
new file mode 100644
index 00000000000..a7da3bdc941
Binary files /dev/null and b/docs/images/acerizm.png differ
diff --git a/docs/images/addcommandActivityDiagram.png b/docs/images/addcommandActivityDiagram.png
new file mode 100644
index 00000000000..c2d21b9a14a
Binary files /dev/null and b/docs/images/addcommandActivityDiagram.png differ
diff --git a/docs/images/clear/clearAllContacts.png b/docs/images/clear/clearAllContacts.png
new file mode 100644
index 00000000000..9aa2f529911
Binary files /dev/null and b/docs/images/clear/clearAllContacts.png differ
diff --git a/docs/images/delete/DeleteActivityDiagram.png b/docs/images/delete/DeleteActivityDiagram.png
new file mode 100644
index 00000000000..835d775fb12
Binary files /dev/null and b/docs/images/delete/DeleteActivityDiagram.png differ
diff --git a/docs/images/delete/deleteContact.png b/docs/images/delete/deleteContact.png
new file mode 100644
index 00000000000..b6be3602b60
Binary files /dev/null and b/docs/images/delete/deleteContact.png differ
diff --git a/docs/images/editCommandExample.png b/docs/images/editCommandExample.png
new file mode 100644
index 00000000000..df8977d0a9b
Binary files /dev/null and b/docs/images/editCommandExample.png differ
diff --git a/docs/images/elvern18.png b/docs/images/elvern18.png
new file mode 100644
index 00000000000..46c670eb4f9
Binary files /dev/null and b/docs/images/elvern18.png differ
diff --git a/docs/images/export/exportActivityDiagram.png b/docs/images/export/exportActivityDiagram.png
new file mode 100644
index 00000000000..e441a07c00b
Binary files /dev/null and b/docs/images/export/exportActivityDiagram.png differ
diff --git a/docs/images/export/exportContacts.png b/docs/images/export/exportContacts.png
new file mode 100644
index 00000000000..bf45b1a74d8
Binary files /dev/null and b/docs/images/export/exportContacts.png differ
diff --git a/docs/images/filter/filterActivityDiagram.png b/docs/images/filter/filterActivityDiagram.png
new file mode 100644
index 00000000000..19c45b0edb4
Binary files /dev/null and b/docs/images/filter/filterActivityDiagram.png differ
diff --git a/docs/images/filter/filterByDescriptionResult.png b/docs/images/filter/filterByDescriptionResult.png
new file mode 100644
index 00000000000..16fc4afddce
Binary files /dev/null and b/docs/images/filter/filterByDescriptionResult.png differ
diff --git a/docs/images/filter/filterByEmailResult.png b/docs/images/filter/filterByEmailResult.png
new file mode 100644
index 00000000000..ddef828fcbe
Binary files /dev/null and b/docs/images/filter/filterByEmailResult.png differ
diff --git a/docs/images/filter/filterByModule.png b/docs/images/filter/filterByModule.png
new file mode 100644
index 00000000000..e1759b0cfbd
Binary files /dev/null and b/docs/images/filter/filterByModule.png differ
diff --git a/docs/images/filter/filterByNameResult.png b/docs/images/filter/filterByNameResult.png
new file mode 100644
index 00000000000..bd3890bb9f3
Binary files /dev/null and b/docs/images/filter/filterByNameResult.png differ
diff --git a/docs/images/filter/filterByPhoneNumberResult.png b/docs/images/filter/filterByPhoneNumberResult.png
new file mode 100644
index 00000000000..e44a97cc3c5
Binary files /dev/null and b/docs/images/filter/filterByPhoneNumberResult.png differ
diff --git a/docs/images/filter/filterByTagsResult.png b/docs/images/filter/filterByTagsResult.png
new file mode 100644
index 00000000000..76f3476ffd2
Binary files /dev/null and b/docs/images/filter/filterByTagsResult.png differ
diff --git a/docs/images/filter/filterFamilyResult.png b/docs/images/filter/filterFamilyResult.png
new file mode 100644
index 00000000000..be0c57f86c9
Binary files /dev/null and b/docs/images/filter/filterFamilyResult.png differ
diff --git a/docs/images/filter/filterGirlfriendResult.png b/docs/images/filter/filterGirlfriendResult.png
new file mode 100644
index 00000000000..bf32250602b
Binary files /dev/null and b/docs/images/filter/filterGirlfriendResult.png differ
diff --git a/docs/images/gibson0918.png b/docs/images/gibson0918.png
new file mode 100644
index 00000000000..367fdf8a8e1
Binary files /dev/null and b/docs/images/gibson0918.png differ
diff --git a/docs/images/joellow88.png b/docs/images/joellow88.png
new file mode 100644
index 00000000000..9d8387fd3cd
Binary files /dev/null and b/docs/images/joellow88.png differ
diff --git a/docs/images/lightdarkactivitydiagram.png b/docs/images/lightdarkactivitydiagram.png
new file mode 100644
index 00000000000..1ed6356cf07
Binary files /dev/null and b/docs/images/lightdarkactivitydiagram.png differ
diff --git a/docs/images/login/CreatePasswordSection.png b/docs/images/login/CreatePasswordSection.png
new file mode 100644
index 00000000000..00745d51c09
Binary files /dev/null and b/docs/images/login/CreatePasswordSection.png differ
diff --git a/docs/images/login/DefaultLoadingSection.png b/docs/images/login/DefaultLoadingSection.png
new file mode 100644
index 00000000000..98123afdc43
Binary files /dev/null and b/docs/images/login/DefaultLoadingSection.png differ
diff --git a/docs/images/login/DefaultLoginSection.png b/docs/images/login/DefaultLoginSection.png
new file mode 100644
index 00000000000..de19b2d5d38
Binary files /dev/null and b/docs/images/login/DefaultLoginSection.png differ
diff --git a/docs/images/login/FirstTimeLoginUser.png b/docs/images/login/FirstTimeLoginUser.png
new file mode 100644
index 00000000000..97da4468d53
Binary files /dev/null and b/docs/images/login/FirstTimeLoginUser.png differ
diff --git a/docs/images/login/PasswordCreatedLoadingSection.png b/docs/images/login/PasswordCreatedLoadingSection.png
new file mode 100644
index 00000000000..b62431bc9be
Binary files /dev/null and b/docs/images/login/PasswordCreatedLoadingSection.png differ
diff --git a/docs/images/redoExample1.png b/docs/images/redoExample1.png
new file mode 100644
index 00000000000..826081f0d8a
Binary files /dev/null and b/docs/images/redoExample1.png differ
diff --git a/docs/images/statspadders.png b/docs/images/statspadders.png
new file mode 100644
index 00000000000..5fca6a75337
Binary files /dev/null and b/docs/images/statspadders.png differ
diff --git a/docs/images/view/viewActivityDiagram.png b/docs/images/view/viewActivityDiagram.png
new file mode 100644
index 00000000000..f6a809d9347
Binary files /dev/null and b/docs/images/view/viewActivityDiagram.png differ
diff --git a/docs/images/view/viewContactDetails.png b/docs/images/view/viewContactDetails.png
new file mode 100644
index 00000000000..c32a1eace6a
Binary files /dev/null and b/docs/images/view/viewContactDetails.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..ad6f3abdf68 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,18 +1,136 @@
---
layout: page
-title: AddressBook Level-3
+title: ModCheck
---
[](https://github.com/se-edu/addressbook-level3/actions)
[](https://codecov.io/gh/se-edu/addressbook-level3)
-
+# ModCheck
+ModCheck is a contact management app that enables you to manage all your contacts easily that works on Windows, MacOS and Linux!
+Our app is catered towards fast typist and many features are catered for students that need better management of their contacts.
-**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).
+## Main GUI
+
+
+
+
+Main UI
+
-* 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.
+## Key Features
+### 👁️ View hidden details of contacts
+
+ModCheck allows you to view all the details that the contacts have
+
+### ➕ Add new contacts easily
+
+ModCheck can add new contacts quickly and supports many contacts
+
+### 🧹 Clear all existing contacts
+
+ModCheck can clear all your contacts from the app with just one command!
+
+### ❌ Delete a specific contact
+
+ModCheck allows you to choose which contact to be deleted from the app
+
+### ✍️ Edit a specific contact
+
+ModCheck allows you to edit details of the specified contact
+
+### 🔙 Undo previous changes made in ModCheck
+
+ModCheck can undo all changes that you have made accidentally
+
+### ↩️ Redo what has been undone
+
+ModCheck also allows you to redo changes made if you have undo accidentally
+
+### ⬇️ Load a new data file
+
+ModCheck also allows you to quickly load data from another file to ModCheck
+
+### 📦 Export specified contacts for archiving
+
+ModCheck can export contacts that you want to archive in another place
+
+### 🔦 Toggle between light and dark mode
+
+ModCheck allows you to quickly customize how the overall UI looks!
+
+### 🔍 Filter contacts by name, phone number, description, email address, tags and module tags quickly
+
+ModCheck can quickly filter all your contacts based on the criteria provided
+
+### 💾 Instant saving when changes are made
+
+ModCheck can save all your work immediately on the fly
+
+### ✅ List all contacts quickly without lag
+
+ModCheck can display all your contacts in one go!
+
+## Usage
+
+
+
+**: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`.
+
+* 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`.
+
+* Items with `…` after them can be used multiple times including zero times.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+
+* Parameters can be in any order.
+ e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+
+* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
+ 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`.
+
+* :warning: Unrecognised fields such as `b/` or `c/` will not be picked up as fields, and will be treated as input.
+
+
+
+## Guides
+
+[User Guide](https://ay2223s2-cs2103-f10-3.github.io/tp/UserGuide.html)
+
+[Developer Guide](https://ay2223s2-cs2103-f10-3.github.io/tp/DeveloperGuide.html)
+
+## FAQ
+
+**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer. `load` the contents of the previous data file into your new ModCheck.
+
+## Command summary
+
+| Action | Format, Examples |
+|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL d/DESCRIPTION [t/TAG]… [m/MODULE_TAG]… ` e.g., `add n/Benedict Tan d/Great Friend e/BenedictTan@gmail.com p/98070707 t/Friend m/CS2103 m/CS3230 ` |
+| **View** | `view INDEX` e.g., `view 2` |
+| **Clear** | `clear` |
+| **Delete** | `delete INDEX` or `delete INDEXES` or `delete NAME` e.g., `delete 3` or `delete 1,2,3` or `delete James` |
+| **Edit** | `edit {INDEX or NAME} [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com` or `edit James e/jameslee@example.com` | |
+| **List** | `list` |
+| **Help** | `help` |
+| **Filter** | `filter n/NAME` `filter p/PHONE_NUMBER` `filter e/EMAIL_ADDRESS` `filter d/DESCRIPTION` `filter t/TAG` `filter m/MODULE_TAG` e.g. `filter n/Alex` e.g. `filter p/91031282` e.g. `filter e/royb@example.com` e.g. `filter d/helpful` e.g. `filter t/family` e.g. `filter m/CS2103` |
+| **Undo** | `undo` |
+| **Redo** | `redo` |
+| **Load** | `load` OR `load ` |
+| **Export** | `export INDEX` e.g., `export 2` |
+| **Light** | `light` |
+| **Dark** | `dark` |
+
+---
**Acknowledgements**
diff --git a/docs/team/acerizm.md b/docs/team/acerizm.md
new file mode 100644
index 00000000000..4b87d813aea
--- /dev/null
+++ b/docs/team/acerizm.md
@@ -0,0 +1,66 @@
+---
+layout: page
+title: Haiqel's Project Portfolio Page
+---
+
+### Project: MODcheck
+
+MODcheck is a desktop app to allow students to easily check the module coordinators, professors and teaching
+assistants in the modules they are taking.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added Login Feature to allow the user to secure MODCheck with their own password.
+ * What it does: allows the user to create a password to prevent other unwanted users from using MODCheck. The user is able to skip creating a password and use MODCheck without any password.
+ * Justification: This feature improves the security of MODCheck as the user might not want anyone else to access the contacts stored in MODCheck and the contacts stored will be private to the user.
+ * Highlights: This enhancement affects how the user will potentially interact with MODCheck as a first time user and after subsequent usage.
+ It required extensive analysis and code tracing of existing implementation of AB3 so that the Login feature adheres to coding standards set by previous students in AB3.
+ The implementation was extremely challenging as it was the first time that I had learnt very deeply on how to use JavaFX and FXML effectively to create UI.
+ Not only that, implementing the UI requires knowledge on the Observer Design Pattern as I had to handle multiple scenarios when the user clicks or enter text in the UI which was not easy.
+ The most difficult part was learning how the original AB3 codebase worked so that my login feature can follow the same design patterns and architecture design.
+ * Credits: [Jakob Jenkov's tutorial website](https://jenkov.com/tutorials/javafx/index.html) for learning JavaFX and FXML
+
+* **New Feature**: Added a filter command with name, phone number, email addresses, description and tag parameters.
+ * What it does: allows the user to filter contacts based on different parameters given.
+ * Justification: This feature will make the user's experience easier when navigating MODCheck as MODCheck may have a lot of contacts and the user
+ is able to filter the desired contacts easily in a single command.
+ * Highlights: This feature was somewhat challenging as I had to learn how other existing features were implemented and follow the developer guide extensively.
+ It was also the first time for me experiencing writing quite a few test cases to ensure that my feature works as expected.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2023-02-17&tabOpen=true&tabType=authorship&tabAuthor=Acerizm&tabRepo=AY2223S2-CS2103-F10-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for filter command
+ * Added documentation for Login feature
+ * Developer Guide:
+ * Added further documentation for Ui Class Diagram, Model Class Diagram and Storage Class Diagram
+ * Added documentation for filter command
+
+* **Contributions to team-based tasks**:
+ * Created the [MODCheck repo](https://github.com/AY2223S2-CS2103-F10-3/tp)
+ * Added CodeCov
+ * Updated the [ReadMe](https://github.com/AY2223S2-CS2103-F10-3/tp/blob/923f49aa6c5552f5b45aa6bbe279f54ea1fd84f2/README.md) documentation
+ * Added essential labels to be assigned for issues
+ * Protected the master branch of [MODCheck's repo](https://github.com/AY2223S2-CS2103-F10-3/tp) with custom settings where each pull request has
+ to be approved by at least 1 team member (including myself to prevent abuse of repo ownership)
+ * Assigned team members to multiple issues/pull requests with the correct labels/milestones
+* **Review/mentoring contributions**:
+ * PRs reviewed extensively on other teammates forks and branches:
+ [#11](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/11)
+ [#15](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/15)
+ [#17](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/17)
+ [#18](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/18)
+ [#19](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/19)
+ [#21](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/21)
+ [#36](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/36)
+ [#37](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/37)
+ [#41](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/41)
+ [#42](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/42)
+ [#43](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/43)
+ [#50](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/50)
+ [#53](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/53)
+ [#58](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/58)
+ [#104](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/104)
+ * Helped teammates when they encounter difficulties in general through Telegram
+
diff --git a/docs/team/elvern18.md b/docs/team/elvern18.md
new file mode 100644
index 00000000000..3fd4f19db1e
--- /dev/null
+++ b/docs/team/elvern18.md
@@ -0,0 +1,66 @@
+---
+layout: page
+title: Elvern Tan's Project Portfolio Page
+---
+
+### Project: MODcheck
+
+MODcheck is a desktop app to allow students to easily check the module coordinators, professors and teaching
+assistants in the modules they are taking.
+
+Given below are my contributions to the project.
+
+* **Code contributed**: [RepoSenseLink](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2023-02-17&tabOpen=true&tabType=authorship&tabAuthor=elvern18&tabRepo=AY2223S2-CS2103-F10-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+* **Enhancements to existing features**: DeleteMultipleIndexCommand
+ * What it does: Allow users to delete contacts by listing multiple indexes.
+ * Justification: This new command would help users delete contacts more quickly and conveniently, helping them save time, since users no longer have to delete using a single index multiple times.
+ * Highlights: This was challenging as I had to refactor the original DeleteCommand into a parent class, and create another class DeleteSingleIndexCommand. It was tricky on how to accept valid indexes, and many scenarios have to be carefully thought.
+
+* **Enhancements to existing features**: DeleteByNameCommand
+ * What it does: Allow users to delete contacts by their name.
+ * Justification: This new command would help users delete contacts more quickly and conveniently, helping them save time, since users no longer have to slowly search for the index that the person is in. This is especially time-consuming when there are a large number of contacts in the list.
+ * Highlights: Apart from having to refactor DeleteCommand, I had to think about how to handle the validity of the parsing of names, as it is no longer integers or indexes that I had to handle. I had to carefully think of new different scenarios as by allowing deletion by name, as this meant that almost any string entered would be a valid name.
+
+* **Enhancements to existing features**: EditByNameCommand
+ * What it does: Allow users to edit contacts by their name.
+ * Justification: This new command would help users edit contacts more quickly and conveniently, helping them save time, since users no longer have to delete using a single index multiple times.
+ * Highlights: This was challenging as I had to refactor the original EditCommand into a parent class, and create another class EditByIndexCommand. It was tricky on how to handle the validity of the parsing of names, as it is no longer integers or indexes that I had to handle. Thus, I had to carefully think of new different scenarios as by allowing editing by name, as this meant that almost any string entered would be a valid name.
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for DeleteSingleIndexCommand
+ * Added documentation for DeleteMultipleIndexCommand
+ * Added documentation for DeleteByNameCommand
+ * Added documentation for EditByIndexCommand
+ * Added documentation for EditByNameCommand
+
+ * Developer Guide:
+ * Added documentation for DeleteSingleIndexCommand
+ * Added documentation for DeleteMultipleIndexCommand
+ * Added documentation for DeleteByNameCommand
+ * Added documentation for EditByIndexCommand
+ * Added documentation for EditByNameCommand
+ * Added documentation to user stories
+ * Added non-functionality requirements
+ * Added terms to glossary
+
+* **Contributions to team-based tasks**:
+ * Submit PE-D bugs
+
+* **Review/mentoring contributions**:
+ * [#29](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/29), [#34](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/34), [#43](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/43), [#47](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/47)
+, [#83](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/83)
+, [#97](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/97)
+, [#156](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/156)
+, [#157](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/157)
+, [#160](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/160)
+, [#161](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/161)
+, [#170](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/170)
+
+* **Contributions beyond the project task**:
+ * Bugs reported in other team's products
+ * [#1](https://github.com/elvern18/ped/issues/1),
+[#2](https://github.com/elvern18/ped/issues/2), [#3](https://github.com/elvern18/ped/issues/3), [#4](https://github.com/elvern18/ped/issues/4), [#5](https://github.com/elvern18/ped/issues/5), [#6](https://github.com/elvern18/ped/issues/6)
+
diff --git a/docs/team/gibson0918.md b/docs/team/gibson0918.md
new file mode 100644
index 00000000000..00d7f0840e0
--- /dev/null
+++ b/docs/team/gibson0918.md
@@ -0,0 +1,36 @@
+---
+layout: page
+title: Gibson's Project Portfolio Page
+---
+
+### Project: MODCheck
+
+MODcheck is a desktop app to allow students to easily check the module coordinators, professors and teaching
+assistants in the modules they are taking
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added a view command.
+ * What it does: Allows the user to view the full contact details of all users stored within ModCheck.
+ * Justification: This feature ensures the privacy of MODCheck's users as their contact details are not displayed openly.
+ * Highlights: This enhancement affects existing commands and commands to be added in the future.
+ It required an in-depth analysis of the existing code base and design alternatives to incorporate this feature.
+ The implementation was challenging as it require changes to existing commands and code analysis of the existing implementation to support existing and future commands.
+
+
+* **New Feature**: Added an export command.
+ * What it does: Allows the user to export selected contacts from ModCheck
+ * Justification: This feature enhances the experience of using ModCheck as it allows for the easy exporting of selected contact details.
+ * Highlights: This feature was somewhat challenging as I had to learn how other existing features were implemented and follow the developer guide extensively.
+ It was also the first time for me experiencing writing quite a few test cases to ensure that my feature works as expected.
+
+
+* **Code contributed**:
+ * https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2023-02-17&tabOpen=true&tabType=authorship&tabAuthor=Gibson0918&tabRepo=AY2223S2-CS2103-F10-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=fals
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `view` and `export` [#32](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/32) [#151](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/151)
+ * Developer Guide:
+ * Added implementation for the features `view` and `export` [#50](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/50) [#151](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/151)
+
diff --git a/docs/team/joellow88.md b/docs/team/joellow88.md
new file mode 100644
index 00000000000..38124f16bdb
--- /dev/null
+++ b/docs/team/joellow88.md
@@ -0,0 +1,93 @@
+---
+layout: page
+title: Joel Low's Project Portfolio Page
+---
+
+### Project: ModCheck
+
+ModCheck is a desktop app to allow students to easily check the module coordinators, professors and teaching
+assistants in the modules they are taking.
+
+Given below are my contributions to the project.
+
+________________________________________________________________________________________________________________________
+
+
+#### Features added
+
+##### Undo/Redo feature
+
+* **What it does**: Allows user to use `undo` to reverse a previous command.
+
+* **Justification**: This allows users to much more easily reverse a change made to ModCheck. As ModCheck commands
+ mostly use textual input, it is easy to make a typo in a command. It would be a hassle for users to have to
+ manually reverse an erroneous command (especially in case of `clear`, `load`, etc). Rather, the `undo` command
+ allows users to easily revert ot a previous state.
+
+* **Highlights**: The challenge of the `undo` feature lay in the complexity of its interactions with other commands.
+ Many possible scenarios have to be considered, for example, what happens when a modification is made while the app
+ is in an undone state. Support for chained `undo` and `redo` commands is also difficult to implement as the
+ version tracking had to work with only 'modification' commands, and had to automatically clear the history past
+ the undo limit.
+
+##### Load feature
+* **What it does**: Allows users to `load` a selected data file into ModCheck.
+
+* **Justification**: As the target users of ModCheck are students, this feature allows module coordinators to send
+ students a data file of important module contacts, which the student will be able to load easily, without
+ disturbing their current contacts.
+
+* **Highlights**: The challenge of the `load` feature lay in figuring out how to use JavaFX's FileChooser, while
+ maintaining the integrity of the architecture. The FileChooser object requires a
+ Stage object as input, which exists in the Ui component, but the command is only parsed in the Logic
+ component, which does not have access to the Stage object.
+
+ To implement the load command, I had to use an exception to communicate between the Ui and Logic components. This
+ allowed the FileChooser to be shown during execution of the command, yet still minimising coupling between Ui and
+ Logic components (compared to the alternative of passing the Stage object as a parameter into Logic).
+
+#### Enhancement to existing features
+* **Filter by module**: The filter command implemented in V1.2 did not have support for filtering by module tags. In
+ V1.3, the filter command was updated to include filtering by module using the prefix `m/`.
+
+
+________________________________________________________________________________________________________________________
+
+
+
+* **Documentation**:
+ * User Guide:
+ * Added descriptions of `undo`, `redo` and `load` to the user guide.
+ * Developer Guide:
+ * Updated implementation of `undo` and `redo` found in the original AB3 developer guide.
+ * Added implementation details of `load` to the developer guide.
+ * Added sequence and activity diagrams generated using PlantUML.
+ * Contributed to non-functional requirements, glossary, and use cases.
+
+* **Contribution to team tasks**:
+ * Set up zoom sessions for team meetings
+ * Created team PR to upstream repo
+ * Added screenshots of V1.2 and V1.3 to project notes document
+ * Reviewed and commented on pull requests to the team repo
+ * Authored and commented on issues in the team repo
+
+* Notable links to PRs reviewed
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/pull/63
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/pull/68
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/pull/88
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/pull/89
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/pull/92
+* Notable links to issues created/commented on
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/9
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/59
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/73
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/74
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/90
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/61
+ * https://github.com/AY2223S2-CS2103-F10-3/tp/issues/60
+
+
+________________________________________________________________________________________________________________________
+
+
+* **Link to RepoSense Report**: [Reposense report](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=joellow88&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
diff --git a/docs/team/statspadders.md b/docs/team/statspadders.md
new file mode 100644
index 00000000000..bc2c0316838
--- /dev/null
+++ b/docs/team/statspadders.md
@@ -0,0 +1,67 @@
+---
+layout: page
+title: Statspadders's Project Portfolio Page
+---
+
+### Project: MODcheck
+
+MODcheck is a desktop app to allow students to easily check the module coordinators, professors and teaching
+assistants in the modules they are taking.
+
+Given below are my contributions to the project.
+
+* **Enhancements to existing features**: Add Command
+ * What it does: Allow users to add a new field MODULE_TAGS
+ * Justification: This new field would help users keep track of module-specific tags or could be used to further differentiate between individuals or groups involved in the same module. The added benefit of having module tags in a vibrant orange color can also make the user interface more vibrant and visually appealing which further helps to organise ModCheck.
+ * Highlights: It is a bit tedious since you must perform a thorough analysis of the add command and know where to make further improvements to the existing code.
+
+* **Enhancements to existing features**: Add Command
+ * What it does: Keeping the field NAME as the primary requirement, and remove the mandatory requirement of all other fields.
+ * Justification: In our daily lives, it is difficult to have complete contact information for individuals. It is a poor idea if ModCheck prevents someone from adding a person's contact information because he is lacking a required field.
+ * Highlights: Understanding how AB3 enforces mandatory fields, and uses regex to validate parameters. It was difficult to learn and apply the new regex.
+ * Credits: @joellow88 for suggesting this issue.
+
+* **New Feature**: Light/Dark Mode
+ * What it does: Allow the users to change between light or dark mode
+ * Justification: ModCheck adopts the AB3 (Dark theme) basic user interface, which may be a little dull. As a result, a light theme is available for users who like to use the program in a light tone.
+ * Highlights: Updating the CSS was difficult because there were extra fields in the css theme file from AB3 that were not utilized in AB3, making it difficult to identify the fields that you wanted to alter.
+
+* **Code contributed**:
+ * [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=statspadders&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Documentation**:
+ * User Guide:
+ * Table of contents
+ * Add Command
+ * Light/Dark Mode
+ * Developer Guide:
+ * Add Command
+ * Light/Dark Mode
+ * User stories
+ * Glossary
+ * Non-functional requirements
+
+* **Contributions to team-based tasks:**
+ * Upload Trial Jar file
+ * Upload Dry PE Jar file
+ * Feedback on PE-D Testers
+
+* **Review/mentoring contributions**:
+[#16](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/16)
+[#38](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/38)
+[#48](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/48)
+[#49](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/49)
+[#76](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/76)
+[#91](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/91)
+[#92](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/92)
+[#93](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/93)
+[#99](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/99)
+[#100](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/100)
+[#102](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/102)
+[#103](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/103)
+[#144](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/144)
+[#146](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/146)
+[#147](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/147)
+[#151](https://github.com/AY2223S2-CS2103-F10-3/tp/pull/151)
+
+
diff --git a/export/ModCheck_contact_list.json b/export/ModCheck_contact_list.json
new file mode 100644
index 00000000000..ad01a84b650
--- /dev/null
+++ b/export/ModCheck_contact_list.json
@@ -0,0 +1,52 @@
+{
+ "persons" : [ {
+ "name" : "Alice Pauline",
+ "phone" : "94351253",
+ "email" : "alice@example.com",
+ "address" : "123, Jurong West Ave 6, #08-111",
+ "tagged" : [ "friends" ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Benson Meier",
+ "phone" : "98765432",
+ "email" : "johnd@example.com",
+ "address" : "311, Clementi Ave 2, #02-25",
+ "tagged" : [ "owesMoney", "friends" ],
+ "moduleTagged" : [ "CS2103" ]
+ }, {
+ "name" : "Carl Kurz",
+ "phone" : "95352563",
+ "email" : "heinz@example.com",
+ "address" : "wall street",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Daniel Meier",
+ "phone" : "87652533",
+ "email" : "cornelia@example.com",
+ "address" : "10th street",
+ "tagged" : [ "friends" ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Elle Meyer",
+ "phone" : "9482224",
+ "email" : "werner@example.com",
+ "address" : "michegan ave",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Fiona Kunz",
+ "phone" : "9482427",
+ "email" : "lydia@example.com",
+ "address" : "little tokyo",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "George Best",
+ "phone" : "9482442",
+ "email" : "anna@example.com",
+ "address" : "4th street",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ } ]
+}
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 4133aaa0151..6c7936a4ae0 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -2,6 +2,7 @@
import java.io.IOException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Optional;
import java.util.logging.Logger;
@@ -19,14 +20,18 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserData;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
import seedu.address.model.util.SampleDataUtil;
import seedu.address.storage.AddressBookStorage;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserDataStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
+import seedu.address.storage.UserDataStorage;
import seedu.address.storage.UserPrefsStorage;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -57,11 +62,13 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ UserDataStorage userDataStorage = new JsonUserDataStorage(Paths.get("userData.json"));
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
+ UserData userData = initUserData(userDataStorage);
initLogging(config);
- model = initModelManager(storage, userPrefs);
+ model = initModelManager(storage, userPrefs, userData);
logic = new LogicManager(model, storage);
@@ -73,7 +80,7 @@ public void init() throws Exception {
* The data from the sample address book will be used instead if {@code storage}'s address book is not found,
* or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
*/
- private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
+ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs, ReadOnlyUserData userData) {
Optional addressBookOptional;
ReadOnlyAddressBook initialData;
try {
@@ -90,7 +97,7 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
initialData = new AddressBook();
}
- return new ModelManager(initialData, userPrefs);
+ return new ModelManager(initialData, userPrefs, userData);
}
private void initLogging(Config config) {
@@ -165,6 +172,33 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
return initializedPrefs;
}
+ protected UserData initUserData(UserDataStorage userDataStorage) {
+ Path userDataFilePath = storage.getUserDataFilePath();
+ logger.info("Using prefs file : " + userDataFilePath);
+
+ UserData initializedUserData;
+ try {
+ Optional userDataOptional = storage.readUserData();
+ initializedUserData = userDataOptional.orElse(new UserData());
+ } catch (DataConversionException e) {
+ logger.warning("UserData file at " + userDataFilePath + " is not in the correct format. "
+ + "Using default user prefs");
+ initializedUserData = new UserData();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the file. Will be starting with an empty user data");
+ initializedUserData = new UserData();
+ }
+
+ // Update userData file in case it was missing to begin with or there are new/unused fields
+ try {
+ storage.saveUserData(initializedUserData);
+ } catch (IOException e) {
+ logger.warning("Failed to save userData file : " + StringUtil.getDetails(e));
+ }
+
+ return initializedUserData;
+ }
+
@Override
public void start(Stage primaryStage) {
logger.info("Starting AddressBook " + MainApp.VERSION);
@@ -175,7 +209,9 @@ public void start(Stage primaryStage) {
public void stop() {
logger.info("============================ [ Stopping Address Book ] =============================");
try {
+ logic.setNumberOfTimesUsed(logic.getNumberOfTimesUsed() + 1);
storage.saveUserPrefs(model.getUserPrefs());
+ storage.saveUserData(model.getUserData());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
}
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java
index ba33653be67..a03f7036202 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/seedu/address/commons/core/GuiSettings.java
@@ -11,7 +11,7 @@
public class GuiSettings implements Serializable {
private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_WIDTH = 1000;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..c5f00d7ef46 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -8,6 +8,18 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
+ public static final String MESSAGE_INVALID_PERSON_DISPLAYED_MULTIPLE_INDEX =
+ "At least one of the indexes provided is invalid";
+ public static final String MESSAGE_INVALID_TAG = "There is an invalid tag provided";
public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_VIEW_PERSON_CONTACT_DETAILS = "%s's contact details displayed";
+ public static final String MESSAGE_INVALID_INDEX =
+ "The person index provided is invalid. Please enter \"list\" again to retrieve the list and try again.";
+ public static final String MESSAGE_CONTACTS_LISTED_OVERVIEW = "%1$d contacts listed!";
+ public static final String MESSAGE_PERSON_NOT_FOUND = "There is no such person in your contact list.";
+ public static final String MESSAGE_MULTIPLE_PERSONS_FOUND = "There are multiple people who have the same name. "
+ + "You have to be more specific";
+ public static final String MESSAGE_IOEXCEPTION = "Unable to create \"ModCheck\" file to write";
+ public static final String MESSAGE_EXPORT_PERSON_CONTACT_DETAILS = "%s's contact details exported";
}
+
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..99e4f0fdf8f 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/seedu/address/commons/core/index/Index.java
@@ -8,7 +8,7 @@
* base the other component is using for its index. However, after receiving the {@code Index}, that component can
* convert it back to an int if the index will not be passed to a different component again.
*/
-public class Index {
+public class Index implements Comparable {
private int zeroBasedIndex;
/**
@@ -51,4 +51,9 @@ public boolean equals(Object other) {
|| (other instanceof Index // instanceof handles nulls
&& zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check
}
+
+ @Override
+ public int compareTo(Index o) {
+ return this.getZeroBased() - o.getZeroBased();
+ }
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..d1cbd6ed307 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -47,4 +47,35 @@ public interface Logic {
* Set the user prefs' GUI settings.
*/
void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' theme
+ */
+ String getCssFilePath();
+
+ /**
+ * Sets the user prefs' theme
+ */
+ void setCssFilePath(String cssFilePath);
+
+ /**
+ * Set the user's password
+ */
+ void setUserHashedPassword(String hashedPassword);
+
+ /**
+ * Retrieve the user's password
+ */
+ String getUserHashedPassword();
+
+ /**
+ * Retrieves the number of times the user has used MODCheck
+ * @return number of times used
+ */
+ int getNumberOfTimesUsed();
+
+ /**
+ * Set the number of times the user has used MODCheck
+ */
+ void setNumberOfTimesUsed(int numberOfTimesUsed);
}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 9d9c6d15bdc..9678c550c2c 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -78,4 +78,34 @@ public GuiSettings getGuiSettings() {
public void setGuiSettings(GuiSettings guiSettings) {
model.setGuiSettings(guiSettings);
}
+
+ @Override
+ public String getCssFilePath() {
+ return model.getCssFilePath();
+ }
+
+ @Override
+ public void setCssFilePath(String cssFilePath) {
+ model.setCssFilePath(cssFilePath);
+ }
+
+ @Override
+ public void setUserHashedPassword(String hashedPassword) {
+ model.setHashedPassword(hashedPassword);
+ }
+
+ @Override
+ public String getUserHashedPassword() {
+ return model.getHashedPassword();
+ }
+
+ @Override
+ public int getNumberOfTimesUsed() {
+ return model.getNumberOfTimesUsed();
+ }
+
+ @Override
+ public void setNumberOfTimesUsed(int numberOfTimesUsed) {
+ model.setNumberOfTimesUsed(numberOfTimesUsed);
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 71656d7c5c8..23f57516ccf 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -3,6 +3,7 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
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;
@@ -18,20 +19,22 @@ public class AddCommand extends Command {
public static final String COMMAND_WORD = "add";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person contact details to ModCheck. "
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + PREFIX_ADDRESS + "DESCRIPTION "
+ + "[" + PREFIX_TAG + "TAG]... "
+ + "[" + PREFIX_MODULE_TAG + "MODULE 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_ADDRESS + "Friendly "
+ PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
+ + PREFIX_TAG + "TA "
+ + PREFIX_MODULE_TAG + "CS2103";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
@@ -49,12 +52,12 @@ public AddCommand(Person person) {
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
-
if (model.hasPerson(toAdd)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
-
+ model.resetPersonHiddenStatus();
model.addPerson(toAdd);
+ model.resetPersonHiddenStatus();
return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..024ad84a1d1 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -11,7 +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 = "ModCheck has been cleared!";
@Override
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..4ef266564c3 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -16,6 +16,9 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ /** The application is in dark theme mode*/
+ private Boolean darkTheme;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
@@ -26,6 +29,17 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
this.exit = exit;
}
+ /**
+ * For Commands : Light and Dark only
+ * Constructs a {@code CommandResult} with the specified fields.
+ */
+ public CommandResult(String feedbackToUser, Boolean darkTheme) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.showHelp = false;
+ this.exit = false;
+ this.darkTheme = darkTheme;
+ }
+
/**
* Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
* and other fields set to their default value.
@@ -46,6 +60,10 @@ public boolean isExit() {
return exit;
}
+ public Boolean isDarkTheme() {
+ return darkTheme;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -68,4 +86,10 @@ public int hashCode() {
return Objects.hash(feedbackToUser, showHelp, exit);
}
+ @Override
+ public String toString() {
+ return "CommandResult{"
+ + "feedbackToUser='" + feedbackToUser + '\''
+ + '}';
+ }
}
diff --git a/src/main/java/seedu/address/logic/commands/DarkCommand.java b/src/main/java/seedu/address/logic/commands/DarkCommand.java
new file mode 100644
index 00000000000..0708d7e57da
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DarkCommand.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+
+/**
+ * Command to change to dark theme
+ */
+public class DarkCommand extends Command {
+ public static final String COMMAND_WORD = "dark";
+ public static final String MESSAGE_SUCCESS = "Successfully changed to the Dark theme !";
+ public static final String MESSAGE_ERROR = "Already in the dark theme";
+ private static final String cssFilePath = "view/DarkTheme.css";
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (cssFilePath.equals(model.getCssFilePath())) {
+ throw new CommandException(MESSAGE_ERROR);
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, true);
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/DeleteByNameCommand.java b/src/main/java/seedu/address/logic/commands/DeleteByNameCommand.java
new file mode 100644
index 00000000000..eb039d3788b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteByNameCommand.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.model.Model;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
+import seedu.address.model.person.Person;
+
+
+
+/**
+ * Finds and deletes person in address book whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class DeleteByNameCommand extends DeleteCommand {
+
+ private final NameContainsAllKeywordsPredicate predicate;
+
+ public DeleteByNameCommand(NameContainsAllKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ List updatedList = model.getFilteredPersonList();
+
+ if (updatedList.isEmpty()) {
+ model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(Messages.MESSAGE_PERSON_NOT_FOUND));
+ }
+ if (updatedList.size() == 1) {
+ Person personToDelete = updatedList.get(0);
+ model.deletePerson(personToDelete);
+ model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
+ } else {
+ model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_MULTIPLE_PERSONS_FOUND));
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteByNameCommand // instanceof handles nulls
+ && predicate.equals(((DeleteByNameCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 02fd256acba..0dc337fac80 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -1,53 +1,35 @@
package seedu.address.logic.commands;
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
/**
* Deletes a person identified using it's displayed index from the address book.
*/
-public class DeleteCommand extends Command {
+public abstract class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
- public static final String MESSAGE_USAGE = COMMAND_WORD
+ public static final String MESSAGE_USAGE_SINGLE_DELETE = "1. Delete Single Index"
+ ": Deletes the person identified by the index number used in the displayed person list.\n"
+ "Parameters: INDEX (must be a positive integer)\n"
+ "Example: " + COMMAND_WORD + " 1";
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
+ public static final String MESSAGE_USAGE_MULTIPLE_DELETE = "2. Delete Multiple Indexes"
+ + ": Deletes multiple people identified by the index numbers used in the displayed person list.\n"
+ + "Parameters: INDEXES (must be a positive integer), with each index being separated by a comma \n"
+ + "Example: " + COMMAND_WORD + " 1,2,3";
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
+ public static final String MESSAGE_USAGE_DELETE_BY_NAME = "3. Delete Single Person By Name"
+ + ": Deletes the person identified by the specified keyword (case-insensitive) used.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " alice bob charlie";
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
- }
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "\n"
+ + MESSAGE_USAGE_SINGLE_DELETE
+ + "\nOR\n"
+ + MESSAGE_USAGE_MULTIPLE_DELETE
+ + "\nOR\n"
+ + MESSAGE_USAGE_DELETE_BY_NAME;
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
- }
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteMultipleIndexCommand.java b/src/main/java/seedu/address/logic/commands/DeleteMultipleIndexCommand.java
new file mode 100644
index 00000000000..f7a0b1eda3e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteMultipleIndexCommand.java
@@ -0,0 +1,67 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Deletes a person identified using it's displayed index from the address book.
+ */
+public class DeleteMultipleIndexCommand extends DeleteCommand {
+
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted People Successfully ";
+
+ private final List indexes;
+
+ public DeleteMultipleIndexCommand(List indexes) {
+ this.indexes = indexes;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.resetPersonHiddenStatus();
+ List lastShownList = model.getFilteredPersonList();
+ isIndexesValid(lastShownList);
+
+ List listOfPeople = new ArrayList();
+ for (Index targetIndex : this.indexes) {
+
+ Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+ listOfPeople.add(personToDelete);
+ }
+ model.resetPersonHiddenStatus();
+ model.deleteMultiplePersons(listOfPeople);
+ model.resetPersonHiddenStatus();
+ return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteMultipleIndexCommand // instanceof handles nulls
+ && indexes.equals(((DeleteMultipleIndexCommand) other).indexes)); // state check
+ }
+
+ /**
+ * Checks if all the indexes in the list of indexes are valid.
+ * @param lastShownList
+ * @return validity of the list of indexes.
+ * @throws CommandException
+ */
+ public boolean isIndexesValid(List lastShownList) throws CommandException {
+ for (Index targetIndex : indexes) {
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_MULTIPLE_INDEX);
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteSingleIndexCommand.java b/src/main/java/seedu/address/logic/commands/DeleteSingleIndexCommand.java
new file mode 100644
index 00000000000..6b630ddfc86
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteSingleIndexCommand.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+
+/**
+ * Deletes a single person in the address book.
+ */
+public class DeleteSingleIndexCommand extends DeleteCommand {
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteSingleIndexCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deletePerson(personToDelete);
+
+ return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteSingleIndexCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteSingleIndexCommand) other).targetIndex)); // state check
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditByIndexCommand.java b/src/main/java/seedu/address/logic/commands/EditByIndexCommand.java
new file mode 100644
index 00000000000..dcab88de243
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditByIndexCommand.java
@@ -0,0 +1,71 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Abstract parent class of EditCommand and EditByNameCommand
+ */
+public class EditByIndexCommand extends EditCommand {
+ private final Index index;
+ private final EditPersonDescriptor editPersonDescriptor;
+
+
+ /**
+ * @param index of the person in the filtered person list to edit
+ * @param editPersonDescriptor details to edit the person with
+ */
+ public EditByIndexCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editPersonDescriptor);
+
+ this.index = index;
+ this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
+ }
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Person personToEdit = lastShownList.get(index.getZeroBased());
+ Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+
+ if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditByIndexCommand)) {
+ return false;
+ }
+
+ // state check
+ EditByIndexCommand e = (EditByIndexCommand) other;
+ return index.equals(e.index)
+ && editPersonDescriptor.equals(e.editPersonDescriptor);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditByNameCommand.java b/src/main/java/seedu/address/logic/commands/EditByNameCommand.java
new file mode 100644
index 00000000000..0cecf0bf22f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditByNameCommand.java
@@ -0,0 +1,79 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
+import seedu.address.model.person.Person;
+
+
+
+/**
+ * Edits the details of an existing person in the address book.
+ */
+public class EditByNameCommand extends EditCommand {
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+
+ private final EditPersonDescriptor editPersonDescriptor;
+
+ private final NameContainsAllKeywordsPredicate predicate;
+
+ /**
+ * @param predicate predicate of name containing keywords
+ * @param editPersonDescriptor details to edit the person with
+ */
+ public EditByNameCommand(NameContainsAllKeywordsPredicate predicate, EditPersonDescriptor editPersonDescriptor) {
+ requireNonNull(editPersonDescriptor);
+
+ this.predicate = predicate;
+ this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ List updatedList = model.getFilteredPersonList();
+ if (updatedList.isEmpty()) {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND);
+ }
+ if (updatedList.size() == 1) {
+ Person personToEdit = updatedList.get(0);
+ Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+ if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ }
+ model.setPerson(personToEdit, editedPerson);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
+ } else {
+ throw new CommandException(
+ String.format(Messages.MESSAGE_MULTIPLE_PERSONS_FOUND));
+ }
+ }
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditByNameCommand)) {
+
+ // state check
+ return false;
+ }
+
+ // state check
+ EditByNameCommand e = (EditByNameCommand) other;
+ return predicate.equals(e.predicate)
+ && editPersonDescriptor.equals(e.editPersonDescriptor);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 7e36114902f..88cb2f1151c 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,35 +1,31 @@
package seedu.address.logic.commands;
-import static java.util.Objects.requireNonNull;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
+
/**
* Edits the details of an existing person in the address book.
*/
-public class EditCommand extends Command {
+public abstract class EditCommand extends Command {
public static final String COMMAND_WORD = "edit";
@@ -41,7 +37,8 @@ public class EditCommand extends Command {
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_TAG + "TAG]..."
+ + "[" + PREFIX_MODULE_TAG + "MODULE TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
@@ -50,47 +47,11 @@ public class EditCommand extends Command {
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
- private final Index index;
- private final EditPersonDescriptor editPersonDescriptor;
-
- /**
- * @param index of the person in the filtered person list to edit
- * @param editPersonDescriptor details to edit the person with
- */
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
-
- this.index = index;
- this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
-
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
-
/**
* Creates and returns a {@code Person} with the details of {@code personToEdit}
* edited with {@code editPersonDescriptor}.
*/
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
+ static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
assert personToEdit != null;
Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName());
@@ -98,26 +59,9 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ Set updatedModuleTags = editPersonDescriptor.getModuleTags().orElse(personToEdit.getModuleTags());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, updatedModuleTags);
}
/**
@@ -131,6 +75,8 @@ public static class EditPersonDescriptor {
private Address address;
private Set tags;
+ private Set moduleTags;
+
public EditPersonDescriptor() {}
/**
@@ -143,13 +89,14 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setEmail(toCopy.email);
setAddress(toCopy.address);
setTags(toCopy.tags);
+ setModuleTags(toCopy.moduleTags);
}
/**
* 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, tags, moduleTags);
}
public void setName(Name name) {
@@ -201,6 +148,14 @@ public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ public void setModuleTags(Set moduleTags) {
+ this.moduleTags = (moduleTags != null) ? new HashSet<>(moduleTags) : null;
+ }
+
+ public Optional> getModuleTags() {
+ return (moduleTags != null) ? Optional.of(Collections.unmodifiableSet(moduleTags)) : Optional.empty();
+ }
+
@Override
public boolean equals(Object other) {
// short circuit if same object
@@ -220,7 +175,8 @@ public boolean equals(Object other) {
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
&& getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
+ && getTags().equals(e.getTags())
+ && getModuleTags().equals(e.getModuleTags());
}
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ExportCommand.java b/src/main/java/seedu/address/logic/commands/ExportCommand.java
new file mode 100644
index 00000000000..0e4867310e9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ExportCommand.java
@@ -0,0 +1,124 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.person.MatchNamePredicate;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.storage.JsonSerializableAddressBook;
+
+/**
+ * Views a person's contact details
+ */
+public class ExportCommand extends Command {
+ public static final String COMMAND_WORD = "export";
+ public static final String MESSAGE_ARGUMENTS = "Index: %1$d";
+ public static final String FILEPATH = "export/ModCheck_contact_list.json";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": exports the contact details of the person identified "
+ + "by the index number used in the last person listing. "
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1 ";
+
+ private final List index;
+
+ /**
+ * Constructor for ExportCommand
+ * @param index index of the person in the list to display their contacts
+ */
+ public ExportCommand(List index) {
+ requireNonNull(index);
+ this.index = index;
+ }
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.resetPersonHiddenStatus();
+ List lastShownList = model.getFilteredPersonList();
+
+ Optional maxIndex = this.index.stream().max(Comparator.naturalOrder());
+ int max = maxIndex.get().getOneBased();
+
+ if (max > lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX);
+ }
+
+ List nameList = index.stream().filter(x -> validateIndex(x, lastShownList))
+ .map(x -> lastShownList.get(x.getZeroBased()).getName()).collect(Collectors.toList());
+ model.resetPersonHiddenStatus();
+ MatchNamePredicate predicate = new MatchNamePredicate(nameList);
+ model.updateFilteredPersonList(predicate);
+ StringBuilder sb = new StringBuilder();
+ nameList.stream().forEach(x -> sb.append(
+ String.format(Messages.MESSAGE_EXPORT_PERSON_CONTACT_DETAILS, x) + "\n"));
+
+ List updatedPersonList = model.getFilteredPersonList();
+ try {
+ writeToJsonFile(updatedPersonList);
+ } catch (IOException e) {
+ throw new CommandException(Messages.MESSAGE_IOEXCEPTION);
+ }
+ return new CommandResult(sb.toString());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ExportCommand)) {
+ return false;
+ }
+
+ // state check
+ ExportCommand v = (ExportCommand) other;
+ return index.equals(v.index);
+ }
+
+ /**
+ * Method to write person contact details to text file
+ * @param personList list of person's contact details
+ * @throws IOException read/write exception
+ */
+ @SuppressWarnings("checkstyle:CommentsIndentation")
+ public void writeToJsonFile(List personList) throws IOException {
+ File file = new File(FILEPATH);
+ Path path = Path.of(FILEPATH);
+ FileUtil.createParentDirsOfFile(path);
+ FileUtil.createIfMissing(path);
+ AddressBook addressBook = new AddressBook();
+ for (Person p : personList) {
+ addressBook.addPerson(p);
+ }
+ if (file.exists() && !file.isDirectory()) {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(file));
+ writer.write(JsonUtil.toJsonString(new JsonSerializableAddressBook(addressBook)));
+ writer.newLine();
+ writer.close();
+ }
+ }
+
+ private boolean validateIndex(Index index, List lastShownList) {
+ return index.getZeroBased() >= lastShownList.size() ? false : true;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java
new file mode 100644
index 00000000000..2c96a4586f8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java
@@ -0,0 +1,70 @@
+package seedu.address.logic.commands;
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
+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.function.Predicate;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+
+/**
+ * Command class that filters the models based on keywords given
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class FilterCommand extends Command {
+
+ public static final String COMMAND_WORD = "filter";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters a contact.\n"
+ + "Parameters: "
+ + PREFIX_NAME + "NAME "
+ + PREFIX_PHONE + "PHONE "
+ + PREFIX_EMAIL + "EMAIL "
+ + PREFIX_ADDRESS + "DESCRIPTION "
+ + PREFIX_TAG + "TAG "
+ + PREFIX_MODULE_TAG + "MODULE TAG\n"
+ + "Examples:\n" + COMMAND_WORD + " "
+ + PREFIX_NAME + "John Doe\n"
+ + COMMAND_WORD + " "
+ + PREFIX_PHONE + "98765432\n"
+ + COMMAND_WORD + " "
+ + PREFIX_EMAIL + "johnd@example.com\n"
+ + COMMAND_WORD + " "
+ + PREFIX_ADDRESS + "My favourite TA\n"
+ + COMMAND_WORD + " "
+ + PREFIX_TAG + "Friends "
+ + PREFIX_TAG + "Family\n"
+ + COMMAND_WORD + " "
+ + PREFIX_MODULE_TAG + "CS2103";
+
+ private final Predicate predicate;
+
+ public FilterCommand(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.resetPersonHiddenStatus();
+ model.updateFilteredPersonList(this.predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_CONTACTS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FilterCommand // instanceof handles nulls
+ && predicate.equals(((FilterCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index d6b19b0a0de..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof FindCommand // instanceof handles nulls
- && predicate.equals(((FindCommand) other).predicate)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/LightCommand.java b/src/main/java/seedu/address/logic/commands/LightCommand.java
new file mode 100644
index 00000000000..ec33b2d0ccf
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LightCommand.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+
+/**
+ * Command to change to light theme
+ */
+public class LightCommand extends Command {
+ public static final String COMMAND_WORD = "light";
+ public static final String MESSAGE_SUCCESS = "Successfully changed to the light theme !";
+ public static final String MESSAGE_ERROR = "Already in the light theme";
+ private static final String cssFilePath = "view/LightTheme.css";
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ if (cssFilePath.equals(model.getCssFilePath())) {
+ throw new CommandException(MESSAGE_ERROR);
+ } else {
+ return new CommandResult(MESSAGE_SUCCESS, false);
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..297b52237cb 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -18,6 +18,7 @@ public class ListCommand extends Command {
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
+ model.resetPersonHiddenStatus();
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
return new CommandResult(MESSAGE_SUCCESS);
}
diff --git a/src/main/java/seedu/address/logic/commands/LoadCommand.java b/src/main/java/seedu/address/logic/commands/LoadCommand.java
new file mode 100644
index 00000000000..2d7f4d57233
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/LoadCommand.java
@@ -0,0 +1,84 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.File;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.storage.JsonAddressBookStorage;
+
+/**
+ * Chooses and loads the contents of another data file into ModCheck.
+ */
+public class LoadCommand extends Command {
+ public static final String COMMAND_WORD = "load";
+
+ public static final String MESSAGE_SUCCESS = "Contents of %1$s loaded successfully!";
+ public static final String MESSAGE_NO_FILE_SELECTED = "No data file has been selected!";
+ public static final String MESSAGE_NOT_MODCHECK_READABLE_FILE = "Data file cannot be read by ModCheck!\n"
+ + "Please only load data files generated by ModCheck.";
+ public static final String MESSAGE_ADDRESS_BOOK_EMPTY = "Address book chosen is empty!";
+ public static final String MESSAGE_NOT_VALID_PATH = "Path entered is not valid!";
+ private final String path;
+
+ public LoadCommand(String path) {
+ this.path = path;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (path == null) {
+ throw new CommandException(MESSAGE_NO_FILE_SELECTED);
+ }
+
+ Optional newAddressBook;
+ try {
+ newAddressBook = readFile(path);
+ } catch (InvalidPathException e) {
+ throw new CommandException(MESSAGE_NOT_VALID_PATH);
+ } catch (DataConversionException e) {
+ throw new CommandException(MESSAGE_NOT_MODCHECK_READABLE_FILE);
+ }
+
+ model.combine(newAddressBook.orElseThrow(() -> new CommandException(MESSAGE_ADDRESS_BOOK_EMPTY)),
+ path);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, path));
+ }
+
+ private Optional readFile(String path) throws DataConversionException, InvalidPathException {
+ if (!new File(path).canRead()) {
+ throw new InvalidPathException(path, "File not readable");
+ }
+ Optional newAddressBook = new JsonAddressBookStorage(Path.of(path)).readAddressBook();
+ return newAddressBook;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LoadCommand that = (LoadCommand) o;
+
+ return Objects.equals(path, that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return path != null ? path.hashCode() : 0;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..54671b9f26d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.Undoable;
+
+
+/**
+ * Redoes the undo command from ModCheck
+ */
+public class RedoCommand extends Command {
+ public static final String COMMAND_WORD = "redo";
+ public static final String MESSAGE_SUCCESS = "Successfully redone command:\n%1$s";
+ public static final String MESSAGE_NO_REDOABLE_COMMAND = "No command to redo!";
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.resetPersonHiddenStatus();
+ if (model instanceof Undoable) {
+ Undoable undoableModel = (Undoable) model;
+ if (!undoableModel.hasRedoableCommand()) {
+ throw new CommandException(MESSAGE_NO_REDOABLE_COMMAND);
+ }
+ String returnMessage = undoableModel.executeRedo();
+ model.resetPersonHiddenStatus();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, returnMessage));
+ } else {
+ throw new IllegalArgumentException("Model passed does not support undo!");
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..a41308ffb7d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java
@@ -0,0 +1,40 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.Undoable;
+
+
+/**
+ * Undoes the last modification command from ModCheck
+ * A modification command is any command that successfully changed data in ModCheck.
+ * For example: Add, Edit, Clear, etc.
+ * A command that would have changed data in ModCheck, but encountered an error, (eg: Adding duplicate persons), will
+ * NOT be undone
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+ public static final String MESSAGE_SUCCESS = "Successfully undone command:\n%1$s";
+ public static final String MESSAGE_NO_UNDOABLE_COMMAND = "No command to undo!";
+
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ model.resetPersonHiddenStatus();
+ if (model instanceof Undoable) {
+ Undoable undoableModel = (Undoable) model;
+ if (!undoableModel.hasUndoableCommand()) {
+ throw new CommandException(MESSAGE_NO_UNDOABLE_COMMAND);
+ }
+ String returnMessage = undoableModel.executeUndo();
+ model.resetPersonHiddenStatus();
+ return new CommandResult(String.format(MESSAGE_SUCCESS, returnMessage));
+ } else {
+ throw new IllegalArgumentException("Model passed does not support undo!");
+ }
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java
new file mode 100644
index 00000000000..b61cf6f2316
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java
@@ -0,0 +1,85 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+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.person.MatchNamePredicate;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+
+/**
+ * Views a person's contact details
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+ public static final String MESSAGE_ARGUMENTS = "Index: %1$d";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": views the contact details of the person identified "
+ + "by the index number used in the last person listing. "
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1 ";
+
+ private final List index;
+
+ /**
+ * Constructor for viewCommand
+ * @param index index of the person in the list to display their contacts
+ */
+ public ViewCommand(List index) {
+ requireNonNull(index);
+ this.index = index;
+ }
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ Optional maxIndex = this.index.stream().max(Comparator.naturalOrder());
+ int max = maxIndex.get().getOneBased();
+
+ if (max > lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX);
+ }
+
+ List nameList = index.stream().filter(x -> validateIndex(x, lastShownList))
+ .map(x -> lastShownList.get(x.getZeroBased()).getName()).collect(Collectors.toList());
+ List updatedPersonList = lastShownList.stream()
+ .filter(x -> nameList.contains(x.getName())).collect(Collectors.toList());
+ model.showPersonContact(updatedPersonList);
+ MatchNamePredicate predicate = new MatchNamePredicate(nameList);
+ model.updateFilteredPersonList(predicate);
+ StringBuilder sb = new StringBuilder();
+ nameList.stream().forEach(x -> sb.append(
+ String.format(Messages.MESSAGE_VIEW_PERSON_CONTACT_DETAILS, x) + "\n"));
+ return new CommandResult(sb.toString());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ViewCommand)) {
+ return false;
+ }
+
+ // state check
+ ViewCommand v = (ViewCommand) other;
+ return index.equals(v.index);
+ }
+
+ private boolean validateIndex(Index index, List lastShownList) {
+ return index.getZeroBased() >= lastShownList.size() ? false : true;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 3b8bfa035e8..feda6b33cec 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -3,6 +3,7 @@
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
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;
@@ -17,6 +18,7 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
/**
@@ -31,20 +33,22 @@ public class AddCommandParser implements Parser {
*/
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_TAG,
+ PREFIX_MODULE_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ 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());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).orElse(""));
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).orElse(""));
+ Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).orElse(""));
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Set moduleTagList = ParserUtil.parseModuleTags(argMultimap.getAllValues(PREFIX_MODULE_TAG));
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, address, tagList, moduleTagList);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 1e466792b46..d7b50ce31ac 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,12 +9,20 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.DarkCommand;
import seedu.address.logic.commands.DeleteCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.ExportCommand;
+import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.LightCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
+import seedu.address.logic.commands.ViewCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -34,7 +42,7 @@ public class AddressBookParser {
* @return the command based on the user input
* @throws ParseException if the user input does not conform the expected format
*/
- public Command parseCommand(String userInput) throws ParseException {
+ public Command parseCommand(String userInput) throws ParseException, CommandException {
final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
if (!matcher.matches()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
@@ -56,9 +64,6 @@ public Command parseCommand(String userInput) throws ParseException {
case ClearCommand.COMMAND_WORD:
return new ClearCommand();
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
-
case ListCommand.COMMAND_WORD:
return new ListCommand();
@@ -68,9 +73,32 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
+ case FilterCommand.COMMAND_WORD:
+ return new FilterCommandParser().parse(arguments);
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
+ case LoadCommand.COMMAND_WORD:
+ return new LoadCommandParser().parse(arguments);
+
+ case LightCommand.COMMAND_WORD:
+ return new LightCommand();
+
+ case DarkCommand.COMMAND_WORD:
+ return new DarkCommand();
+
+ case ExportCommand.COMMAND_WORD:
+ return new ExportCommandParser().parse(arguments);
+
default:
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
}
-
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..60bd221f9d4 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -9,7 +9,8 @@ public class CliSyntax {
public static final Prefix PREFIX_NAME = new Prefix("n/");
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_ADDRESS = new Prefix("d/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_MODULE_TAG = new Prefix("m/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
index 522b93081cc..0865700152f 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
@@ -2,12 +2,20 @@
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import java.util.Arrays;
+import java.util.List;
+
import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.commands.DeleteByNameCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteMultipleIndexCommand;
+import seedu.address.logic.commands.DeleteSingleIndexCommand;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
/**
- * Parses input arguments and creates a new DeleteCommand object
+ * Parses input arguments and creates a new DeleteCommand type object
*/
public class DeleteCommandParser implements Parser {
@@ -17,12 +25,23 @@ 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) {
+ if (args.isBlank()) {
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ }
+ if (StringUtil.isNonZeroUnsignedInteger(args.trim())) {
+ // Delete Single
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteSingleIndexCommand(index);
+ } else if (args.trim().matches("^[1-9][0-9]*(,[0-9]+)*$")) {
+ // Delete Multiple
+ List listOfIndexes = ParserUtil.parseIndexes(args);
+ return new DeleteMultipleIndexCommand(listOfIndexes);
+ } else {
+ // delete by name
+ String trimmedArgs = args.trim();
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+ return new DeleteByNameCommand(new NameContainsAllKeywordsPredicate(Arrays.asList(nameKeywords)));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 845644b7dea..083e6eee068 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -2,23 +2,31 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TAG;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditByIndexCommand;
+import seedu.address.logic.commands.EditByNameCommand;
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.NameContainsAllKeywordsPredicate;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
+
/**
* Parses input arguments and creates a new EditCommand object
*/
@@ -32,17 +40,15 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- Index index;
-
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG,
+ PREFIX_MODULE_TAG);
+ if (args.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE));
}
-
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ if (argMultimap.getPreamble().isBlank()) {
+ throw new ParseException(MESSAGE_INVALID_COMMAND_FORMAT);
+ }
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
@@ -56,12 +62,31 @@ public EditCommand parse(String args) throws ParseException {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+ parseModuleTagsForEdit(argMultimap.getAllValues(PREFIX_MODULE_TAG))
+ .ifPresent(editPersonDescriptor::setModuleTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}
- return new EditCommand(index, editPersonDescriptor);
+ assert !argMultimap.getPreamble().isBlank();
+ try {
+ if (ParserUtil.isValidIndex(argMultimap.getPreamble())) {
+ Index index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ return new EditByIndexCommand(index, editPersonDescriptor);
+ } else {
+ String nameArg = argMultimap.getPreamble();
+ if (ParserUtil.isValidName(nameArg)) {
+ String[] splitArgs = nameArg.trim().split("\\s");
+ return new EditByNameCommand(new NameContainsAllKeywordsPredicate(Arrays.asList(splitArgs)),
+ editPersonDescriptor);
+ } else {
+ throw new ParseException(MESSAGE_INVALID_TAG);
+ }
+ }
+ } catch (ParseException pe) {
+ throw new ParseException(MESSAGE_INVALID_TAG);
+ }
}
/**
@@ -79,4 +104,15 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars
return Optional.of(ParserUtil.parseTags(tagSet));
}
+ private Optional> parseModuleTagsForEdit(Collection moduleTags) throws ParseException {
+ assert moduleTags != null;
+
+ if (moduleTags.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection moduleTagSet = moduleTags.size() == 1
+ && moduleTags.contains("") ? Collections.emptySet() : moduleTags;
+ return Optional.of(ParserUtil.parseModuleTags(moduleTagSet));
+ }
+
}
diff --git a/src/main/java/seedu/address/logic/parser/ExportCommandParser.java b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java
new file mode 100644
index 00000000000..ba2d23a0148
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ExportCommandParser.java
@@ -0,0 +1,40 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.ExportCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parse input arguments and creates a new {@code ViewCommand} object
+ */
+public class ExportCommandParser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code RemarkCommand}
+ * and returns a {@code RemarkCommand} object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ExportCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ List indexList = new ArrayList<>();
+
+ String[] parsedIndexList = args.trim().split("\\s+");
+ for (String i: parsedIndexList) {
+ System.out.println(i);
+ try {
+ indexList.add(ParserUtil.parseIndex(i));
+ } catch (IllegalValueException ive) {
+ throw new ParseException(String.format(
+ MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_ARGUMENTS), ive);
+ }
+ }
+
+ return new ExportCommand(indexList);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
new file mode 100644
index 00000000000..ec323347c7d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java
@@ -0,0 +1,142 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_TAG;
+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.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.ContactContainsDescriptionPredicate;
+import seedu.address.model.person.ContactContainsEmailPredicate;
+import seedu.address.model.person.ContactContainsModuleTagPredicate;
+import seedu.address.model.person.ContactContainsPhoneNumberPredicate;
+import seedu.address.model.person.ContactContainsTagPredicate;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * Parses input arguments and creates a new FilterCommand object
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class FilterCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FilterCommand
+ * and returns a FilterCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FilterCommand parse(String args) throws ParseException {
+ try {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, PREFIX_MODULE_TAG);
+
+ int numOfPrefixesPresent = getNumOfPrefixesPresent(argMultimap,
+ PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_TAG, PREFIX_MODULE_TAG);
+
+ if (numOfPrefixesPresent > 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+
+ if (arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_TAG, PREFIX_MODULE_TAG)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+
+ if (!isAnyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_TAG, PREFIX_MODULE_TAG)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+
+ Prefix prefix = getPrefix(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_TAG, PREFIX_MODULE_TAG);
+
+ if (prefix.getPrefix().equals("n/") && argMultimap.getAllValues(PREFIX_NAME).size() == 1) {
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ String fullName = name.toString();
+ String trimmedFullName = fullName.trim();
+ String[] nameKeywords = trimmedFullName.split("\\s+");
+ return new FilterCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ } else if (prefix.getPrefix().equals("p/") && argMultimap.getAllValues(PREFIX_PHONE).size() == 1) {
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
+ String phoneNumber = phone.toString();
+ return new FilterCommand(new ContactContainsPhoneNumberPredicate(phoneNumber));
+ } else if (prefix.getPrefix().equals("e/") && argMultimap.getAllValues(PREFIX_EMAIL).size() == 1) {
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
+ String emailAddr = email.toString();
+ return new FilterCommand(new ContactContainsEmailPredicate(emailAddr));
+ } else if (prefix.getPrefix().equals("d/") && argMultimap.getAllValues(PREFIX_ADDRESS).size() == 1) {
+ Address description = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ String fullDescription = description.toString();
+ return new FilterCommand(new ContactContainsDescriptionPredicate(fullDescription));
+ } else if (prefix.getPrefix().equals("t/")) {
+ Set tagSet = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ return new FilterCommand(new ContactContainsTagPredicate(tagSet));
+ } else if (prefix.getPrefix().equals("m/")) {
+ Set moduleTagSet = ParserUtil.parseModuleTags(argMultimap.getAllValues(PREFIX_MODULE_TAG));
+ return new FilterCommand(new ContactContainsModuleTagPredicate(moduleTagSet));
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ FilterCommand.MESSAGE_USAGE));
+ }
+ }
+
+ /**
+ * Returns true if any of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean isAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns the type of prefix
+ * {@code ArgumentMultimap}.
+ */
+ private static Prefix getPrefix(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ Optional optionalPrefix = Stream.of(prefixes)
+ .filter(prefix -> argumentMultimap.getValue(prefix).isPresent())
+ .findFirst();
+
+ return optionalPrefix.orElse(null);
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * Returns the number of prefixes that user inputs
+ */
+ private static int getNumOfPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return (int) Stream.of(prefixes)
+ .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()).count();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
deleted file mode 100644
index 4fb71f23103..00000000000
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import java.util.Arrays;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Parses input arguments and creates a new FindCommand object
- */
-public class FindCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the FindCommand
- * and returns a FindCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public FindCommand parse(String args) throws ParseException {
- String trimmedArgs = args.trim();
- if (trimmedArgs.isEmpty()) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
- }
-
- String[] nameKeywords = trimmedArgs.split("\\s+");
-
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/LoadCommandParser.java b/src/main/java/seedu/address/logic/parser/LoadCommandParser.java
new file mode 100644
index 00000000000..93c0247393a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/LoadCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.exceptions.UiInputRequiredException;
+
+/**
+ * Parses input arguments and creates a new LoadCommand object
+ */
+public class LoadCommandParser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the LoadCommand
+ * and returns a LoadCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ * @throws UiInputRequiredException if Ui input is required
+ */
+ public LoadCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new UiInputRequiredException("Ui input required!");
+ }
+
+ if (trimmedArgs.equals("!")) {
+ return new LoadCommand(null);
+ }
+
+ return new LoadCommand(trimmedArgs);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..b2e5423309c 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,6 +2,7 @@
import static java.util.Objects.requireNonNull;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -13,6 +14,7 @@
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
/**
@@ -35,6 +37,36 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException {
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
+ /**
+ * Parses {@code oneBasedIndex} checks if validity of the index.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static boolean isValidIndex(String oneBasedIndex) {
+ String trimmedIndex = oneBasedIndex.trim();
+ return StringUtil.isNonZeroUnsignedInteger(trimmedIndex);
+ }
+
+ /**
+ * Parses {@code indexes} into an {@code listOfIndexes} and returns it. Leading and trailing whitespaces will be
+ * trimmed. String of indexes will be split by ",".
+ * @param indexes String of indexes.
+ * @return ArrayList of indexes.
+ * @throws ParseException
+ */
+ public static ArrayList parseIndexes(String indexes) throws ParseException {
+ String trimmedIndexes = indexes.trim();
+ String[] splitIndexes = trimmedIndexes.split(",");
+ ArrayList listOfIndexes = new ArrayList<>();
+ for (String index: splitIndexes) {
+ if (!StringUtil.isNonZeroUnsignedInteger(index)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ Index newIndex = Index.fromOneBased(Integer.parseInt(index));
+ listOfIndexes.add(newIndex);
+ }
+ return listOfIndexes;
+ }
+
/**
* Parses a {@code String name} into a {@code Name}.
* Leading and trailing whitespaces will be trimmed.
@@ -50,10 +82,19 @@ public static Name parseName(String name) throws ParseException {
return new Name(trimmedName);
}
+ /**
+ * Parses {@code name} and checks the validity of the name.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static boolean isValidName(String name) {
+ requireNonNull(name);
+ String trimmedName = name.trim();
+ return Name.isValidName(trimmedName);
+ }
/**
* Parses a {@code String phone} into a {@code Phone}.
* Leading and trailing whitespaces will be trimmed.
- *
+ * Allow empty "" as phone field is not compulsory.
* @throws ParseException if the given {@code phone} is invalid.
*/
public static Phone parsePhone(String phone) throws ParseException {
@@ -68,7 +109,7 @@ public static Phone parsePhone(String phone) throws ParseException {
/**
* Parses a {@code String address} into an {@code Address}.
* Leading and trailing whitespaces will be trimmed.
- *
+ * Allow empty "" as description field is not compulsory.
* @throws ParseException if the given {@code address} is invalid.
*/
public static Address parseAddress(String address) throws ParseException {
@@ -83,7 +124,7 @@ public static Address parseAddress(String address) throws ParseException {
/**
* Parses a {@code String email} into an {@code Email}.
* Leading and trailing whitespaces will be trimmed.
- *
+ * Allow empty "" as email field is not compulsory.
* @throws ParseException if the given {@code email} is invalid.
*/
public static Email parseEmail(String email) throws ParseException {
@@ -110,6 +151,21 @@ public static Tag parseTag(String tag) throws ParseException {
return new Tag(trimmedTag);
}
+ /**
+ * Parses a {@code String tag} into a {@code ModuleTag}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code Moduletag} is invalid.
+ */
+ public static ModuleTag parseModuleTag(String tag) throws ParseException {
+ requireNonNull(tag);
+ String trimmedTag = tag.trim();
+ if (!ModuleTag.isValidTagName(trimmedTag)) {
+ throw new ParseException(ModuleTag.MESSAGE_CONSTRAINTS);
+ }
+ return new ModuleTag(trimmedTag);
+ }
+
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
@@ -121,4 +177,16 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses {@code Collection moduleTags} into a {@code Set}.
+ */
+ public static Set parseModuleTags(Collection tags) throws ParseException {
+ requireNonNull(tags);
+ final Set tagSet = new HashSet<>();
+ for (String tagName : tags) {
+ tagSet.add(parseModuleTag(tagName));
+ }
+ return tagSet;
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
new file mode 100644
index 00000000000..2745dfc9595
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.ViewCommand;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parse input arguments and creates a new {@code ViewCommand} object
+ */
+public class ViewCommandParser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code RemarkCommand}
+ * and returns a {@code RemarkCommand} object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ViewCommand parse(String args) throws ParseException, CommandException {
+ requireNonNull(args);
+ List indexList = new ArrayList<>();
+
+ String[] parsedIndexList = args.trim().split("\\s+");
+ for (String i: parsedIndexList) {
+ System.out.println(i);
+ try {
+ indexList.add(ParserUtil.parseIndex(i));
+ } catch (IllegalValueException ive) {
+ if (Integer.valueOf(i) <= 0) {
+ throw new CommandException(Messages.MESSAGE_INVALID_INDEX);
+ } else {
+ throw new ParseException(String.format(
+ MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_ARGUMENTS), ive);
+ }
+ }
+ }
+
+ return new ViewCommand(indexList);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/UiInputRequiredException.java b/src/main/java/seedu/address/logic/parser/exceptions/UiInputRequiredException.java
new file mode 100644
index 00000000000..a37b25d974d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/exceptions/UiInputRequiredException.java
@@ -0,0 +1,13 @@
+package seedu.address.logic.parser.exceptions;
+
+/**
+ * Represents an error encountered by the parser where additional input from Ui is required.
+ */
+public class UiInputRequiredException extends RuntimeException {
+ public UiInputRequiredException(String commandText) {
+ super(commandText);
+ }
+ public UiInputRequiredException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..8dfdcbfdf0e 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,6 +1,7 @@
package seedu.address.model;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
@@ -11,7 +12,9 @@
* The API of the Model component.
*/
public interface Model {
- /** {@code Predicate} that always evaluate to true */
+ /**
+ * {@code Predicate} that always evaluate to true
+ */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
/**
@@ -44,12 +47,24 @@ public interface Model {
*/
void setAddressBookFilePath(Path addressBookFilePath);
+ /**
+ * Returns the user prefs' theme
+ */
+ String getCssFilePath();
+
+ /**
+ * Sets the user prefs' theme
+ */
+ void setCssFilePath(String cssFilePath);
+
/**
* Replaces address book data with the data in {@code addressBook}.
*/
void setAddressBook(ReadOnlyAddressBook addressBook);
- /** Returns the AddressBook */
+ /**
+ * Returns the AddressBook
+ */
ReadOnlyAddressBook getAddressBook();
/**
@@ -63,6 +78,14 @@ public interface Model {
*/
void deletePerson(Person target);
+ /**
+ * Deletes all the people in the list.
+ * All the people in the list must exist in the address book.
+ *
+ * @param listOfPeople ArrayList of Person.
+ */
+ void deleteMultiplePersons(List listOfPeople);
+
/**
* Adds the given person.
* {@code person} must not already exist in the address book.
@@ -76,12 +99,46 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
- /** Returns an unmodifiable view of the filtered person list */
+ /**
+ * Returns an unmodifiable view of the filtered person list
+ */
ObservableList getFilteredPersonList();
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ *
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * resets each person contact status to be hidden.
+ */
+ void resetPersonHiddenStatus();
+
+ /**
+ * Sets each person's contact in the person list to be visible.
+ * @param personList list of person.
+ */
+ void showPersonContact(List personList);
+
+ /**
+ * Adds all contacts not already in the address book into the address book.
+ * If there is name collision (but with different fields), the person already in the address book will be retained.
+ * @param toBeCombined The other address book to combine with.
+ * @param path The string representation of the path of the file to be combined with.
+ */
+ void combine(ReadOnlyAddressBook toBeCombined, String path);
+
+ //=========== UserData ==================================================================================
+
+ void setHashedPassword(String hashedPassword);
+
+ String getHashedPassword();
+
+ void setNumberOfTimesUsed(int numberOfTimesUsed);
+
+ int getNumberOfTimesUsed();
+
+ ReadOnlyUserData getUserData();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 86c1df298d7..89efb91d75d 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,40 +4,67 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
+import javafx.util.Pair;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
import seedu.address.model.person.Person;
+
/**
* Represents the in-memory model of the address book data.
*/
-public class ModelManager implements Model {
+public class ModelManager implements Model, Undoable {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final UndoManager undoManager;
+ private final UserData userData;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, ReadOnlyUserData userData) {
+ requireAllNonNull(addressBook, userPrefs, userData);
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());
+ this.undoManager = new UndoManager(this.addressBook, 5);
+ this.userData = new UserData(userData);
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new UserData());
+ }
+
+ //=========== UserData ==================================================================================
+ public void setHashedPassword(String hashedPassword) {
+ this.userData.setHashedPassword(hashedPassword);
+ }
+
+ public String getHashedPassword() {
+ return this.userData.getHashedPassword();
+ }
+
+ public void setNumberOfTimesUsed(int numberOfTimesUsed) {
+ this.userData.setNumberOfTimesUsed(numberOfTimesUsed);
+ }
+
+ public int getNumberOfTimesUsed() {
+ return this.userData.getNumberOfTimesUsed();
+ }
+
+ public ReadOnlyUserData getUserData() {
+ return this.userData;
}
//=========== UserPrefs ==================================================================================
@@ -75,11 +102,26 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
userPrefs.setAddressBookFilePath(addressBookFilePath);
}
+ @Override
+ public String getCssFilePath() {
+ return userPrefs.getCssFilePath();
+ }
+
+ @Override
+ public void setCssFilePath(String cssFilePath) {
+ requireNonNull(cssFilePath);
+ userPrefs.setCssFilePath(cssFilePath);
+ }
+
//=========== AddressBook ================================================================================
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
+ //setAddressBook is currently only used to clear the addressBook, so this method is temporarily set to
+ // show clear. Note that in future implementations if setAddressBook is used for other purposes, this method
+ // will need to be edited.
+ undoManager.addToHistory(this.addressBook, "Clear");
}
@Override
@@ -96,12 +138,22 @@ public boolean hasPerson(Person person) {
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
+ undoManager.addToHistory(this.addressBook, String.format("Delete %1$s", target));
+ }
+
+ @Override
+ public void deleteMultiplePersons(List list) {
+ for (Person target: list) {
+ addressBook.removePerson(target);
+ }
+ undoManager.addToHistory(this.addressBook, "Deleted list of people");
}
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ undoManager.addToHistory(this.addressBook, String.format("Add %1$s", person));
}
@Override
@@ -109,6 +161,21 @@ public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
addressBook.setPerson(target, editedPerson);
+ undoManager.addToHistory(this.addressBook, String.format("Edit %1$s", editedPerson));
+ }
+
+ @Override
+ public void combine(ReadOnlyAddressBook toBeCombined, String path) {
+ AddressBook newAddressBook = new AddressBook(toBeCombined);
+ for (Person p : newAddressBook.getPersonList()) {
+ if (addressBook.hasPerson(p)) {
+ //Future improvements can be made to specify whether to overwrite or keep persons with the same name
+ continue;
+ } else {
+ addressBook.addPerson(p);
+ }
+ }
+ undoManager.addToHistory(this.addressBook, String.format("Load contents of %s", path));
}
//=========== Filtered Person List Accessors =============================================================
@@ -128,6 +195,58 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ /**
+ * resets each person contact status to be hidden.
+ */
+ @Override
+ public void resetPersonHiddenStatus() {
+ List personlist = getFilteredPersonList();
+ personlist.stream().forEach(x -> {
+ if (!x.getHidden()) {
+ x.toggleHidden();
+ }
+ });
+ }
+
+ /**
+ * Sets each person's contact in the person list to be visible.
+ * @param personList list of person.
+ */
+ @Override
+ public void showPersonContact(List personList) {
+ personList.stream().forEach(x -> x.toggleHidden());
+ }
+
+ //=========== Undo management =============================================================================
+ public boolean hasUndoableCommand() {
+ return undoManager.hasUndoableCommand();
+ }
+
+ /**
+ * Undoes the changes made by the last modification command used.
+ * @return The string representation of the last modification command used.
+ */
+ public String executeUndo() {
+ Pair previousHistory = undoManager.getPreviousHistory();
+ this.addressBook.resetData(previousHistory.getKey());
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return previousHistory.getValue();
+ }
+ public boolean hasRedoableCommand() {
+ return undoManager.hasRedoableCommand();
+ }
+
+ /**
+ * Redoes the changes unmade by the last undo command
+ * @return The string representation of the command redone
+ */
+ public String executeRedo() {
+ Pair nextHistory = undoManager.getNextHistory();
+ this.addressBook.resetData(nextHistory.getKey());
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return nextHistory.getValue();
+ }
+
@Override
public boolean equals(Object obj) {
// short circuit if same object
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserData.java b/src/main/java/seedu/address/model/ReadOnlyUserData.java
new file mode 100644
index 00000000000..52693657090
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyUserData.java
@@ -0,0 +1,10 @@
+package seedu.address.model;
+
+/**
+ * Unmodifiable view of user data.
+ */
+public interface ReadOnlyUserData {
+
+ public String getHashedPassword();
+ public int getNumberOfTimesUsed();
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..a2408642335 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -13,4 +13,6 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ String getCssFilePath();
+
}
diff --git a/src/main/java/seedu/address/model/UndoManager.java b/src/main/java/seedu/address/model/UndoManager.java
new file mode 100644
index 00000000000..2c7b7687073
--- /dev/null
+++ b/src/main/java/seedu/address/model/UndoManager.java
@@ -0,0 +1,131 @@
+package seedu.address.model;
+
+import java.util.LinkedList;
+import java.util.Objects;
+
+import javafx.util.Pair;
+
+
+/**
+ * Saves snapshots of past ModCheck states, used to implement the Undo Command.
+ */
+public class UndoManager {
+ private final int maxSavedHistory;
+ private LinkedList addressBookHistory;
+ private LinkedList commandHistory;
+ //VersionTracker at 0 if ModCheck at most recent history (no undo commands), increments by 1 for each undo command
+ private int versionTracker;
+
+ /**
+ * Initialises an undo manager with given saved states and start state.
+ * @param startState The starting state of ModCheck; no undos will be possible at this state.
+ * @param maxSavedHistory The number of saved histories of ModCheck, determines the number of consecutive undos
+ * supported.
+ */
+ public UndoManager(AddressBook startState, int maxSavedHistory) {
+ this.maxSavedHistory = maxSavedHistory;
+ addressBookHistory = new LinkedList<>();
+ addressBookHistory.add(new AddressBook(startState));
+ commandHistory = new LinkedList<>();
+ commandHistory.add("");
+ versionTracker = 0;
+ }
+
+ /**
+ * Adds a copy of an address book to ModCheck saved history.
+ * @param ab The address book to be added to ModCheck's history.
+ */
+ public void addToHistory(AddressBook ab, String command) {
+ // For the case where changes are made after undoing, UndoManager will no longer track the overwritten changes
+ if (versionTracker != 0) {
+ deleteUntrackedHead();
+ }
+ if (addressBookHistory.size() == maxSavedHistory) {
+ addressBookHistory.removeLast();
+ commandHistory.removeLast();
+ }
+ //a copy of addressBook argument has to be created as ModelManager edits the AddressBook in place
+ addressBookHistory.offerFirst(new AddressBook(ab));
+ commandHistory.offerFirst(command);
+ }
+
+ /**
+ * Deletes an untracked head from the model manager.
+ * This is for changes made after undoing to be tracked unambiguously.
+ */
+ public void deleteUntrackedHead() {
+ for (int i = 0; i < versionTracker; i++) {
+ addressBookHistory.pollFirst();
+ commandHistory.pollFirst();
+ }
+ versionTracker = 0;
+ }
+
+ /**
+ * Returns true if model has a command that can be undone, false otherwise
+ */
+ public boolean hasUndoableCommand() {
+ return versionTracker != addressBookHistory.size() - 1;
+ }
+
+ /**
+ * Returns true if model has a command that can be redone, false otherwise
+ */
+ public boolean hasRedoableCommand() {
+ return versionTracker > 0;
+ }
+
+ /**
+ * Returns an AddressBook containing a saved state of ModCheck.
+ * Calling this method multiple times will return earlier saved states of ModCheck, to facilitate chained undoes.
+ * @return An AddressBook containing an earlier saved state of ModCheck
+ */
+ public Pair getPreviousHistory() {
+ assert hasUndoableCommand();
+ versionTracker++;
+ //Note that commandHistory and addressBookHistory is off by one
+ //ie: The most recent change command will lead to the second most recent address book state
+ return new Pair<>(addressBookHistory.get(versionTracker), commandHistory.get(versionTracker - 1));
+ }
+
+ /**
+ * Returns an AddressBook containing a later saved state of ModCheck.
+ * @return An AddressBook containing a later saved state of ModCheck.
+ */
+ public Pair getNextHistory() {
+ assert hasRedoableCommand();
+ versionTracker--;
+ return new Pair<>(addressBookHistory.get(versionTracker), commandHistory.get(versionTracker));
+ }
+
+ /**
+ * Returns an AddressBook with the current state of ModCheck.
+ * @return An AddressBook with the current state of ModCheck.
+ */
+ public AddressBook getCurrentState() {
+ return addressBookHistory.get(versionTracker);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ UndoManager that = (UndoManager) o;
+
+ if (maxSavedHistory != that.maxSavedHistory) {
+ return false;
+ }
+ if (versionTracker != that.versionTracker) {
+ return false;
+ }
+ if (!Objects.equals(addressBookHistory, that.addressBookHistory)) {
+ return false;
+ }
+ return Objects.equals(commandHistory, that.commandHistory);
+ }
+}
diff --git a/src/main/java/seedu/address/model/Undoable.java b/src/main/java/seedu/address/model/Undoable.java
new file mode 100644
index 00000000000..421f79690a1
--- /dev/null
+++ b/src/main/java/seedu/address/model/Undoable.java
@@ -0,0 +1,29 @@
+package seedu.address.model;
+
+/**
+ * The interface for supporting undo and redo functionality.
+ */
+public interface Undoable {
+ /**
+ * Returns if there is an undoable command in model.
+ * @return True if there is an undoable command in model, false otherwise.
+ */
+ boolean hasUndoableCommand();
+
+ /**
+ * Undoes the changes made by the last modification command used
+ * @return The string representation of the last modification command used
+ */
+ String executeUndo();
+ /**
+ * Returns if there is a redoable command in model.
+ * @return True if there is a redoable command in model, false otherwise.
+ */
+ boolean hasRedoableCommand();
+
+ /**
+ * Redoes the changes unmade by the last undo command
+ * @return The string representation of the command redone
+ */
+ String executeRedo();
+}
diff --git a/src/main/java/seedu/address/model/UserData.java b/src/main/java/seedu/address/model/UserData.java
new file mode 100644
index 00000000000..a7122d2fd30
--- /dev/null
+++ b/src/main/java/seedu/address/model/UserData.java
@@ -0,0 +1,61 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents user's data
+ */
+public class UserData implements ReadOnlyUserData {
+
+ private String hashedPassword = "";
+
+ private int numberOfTimesUsed = 0;
+
+ public UserData() {
+
+ }
+
+ /**
+ * Creates a user data with the previous read only user data
+ * @param userData
+ */
+ public UserData(ReadOnlyUserData userData) {
+ this();
+ resetData(userData);
+ }
+
+ /**
+ * Creates a UserData with given hashedPassword and numberOfTimesUsed
+ * @param hashedPassword Hashed password
+ * @param numberOfTimesUsed number of times the user has used the application
+ */
+ public UserData(String hashedPassword, int numberOfTimesUsed) {
+ this.hashedPassword = hashedPassword;
+ this.numberOfTimesUsed = numberOfTimesUsed;
+ }
+
+ /**
+ * Resets the existing data
+ */
+ public void resetData(ReadOnlyUserData userData) {
+ requireNonNull(userData);
+ this.setHashedPassword(userData.getHashedPassword());
+ this.setNumberOfTimesUsed(userData.getNumberOfTimesUsed());
+ }
+
+ public void setHashedPassword(String hashedPassword) {
+ this.hashedPassword = hashedPassword;
+ }
+
+ public String getHashedPassword() {
+ return this.hashedPassword;
+ }
+
+ public void setNumberOfTimesUsed(int numberOfTimesUsed) {
+ this.numberOfTimesUsed = numberOfTimesUsed;
+ }
+
+ public int getNumberOfTimesUsed() {
+ return this.numberOfTimesUsed;
+ }
+}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..a9ac77dc8c5 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private String cssFilePath = "view/DarkTheme.css";
/**
* Creates a {@code UserPrefs} with default values.
@@ -36,6 +37,7 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setCssFilePath(newUserPrefs.getCssFilePath());
}
public GuiSettings getGuiSettings() {
@@ -56,6 +58,14 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
this.addressBookFilePath = addressBookFilePath;
}
+ public String getCssFilePath() {
+ return cssFilePath;
+ }
+
+ public void setCssFilePath(String cssFilePath) {
+ this.cssFilePath = cssFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -68,12 +78,13 @@ public boolean equals(Object other) {
UserPrefs o = (UserPrefs) other;
return guiSettings.equals(o.guiSettings)
- && addressBookFilePath.equals(o.addressBookFilePath);
+ && addressBookFilePath.equals(o.addressBookFilePath)
+ && cssFilePath.equals(o.cssFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, addressBookFilePath, cssFilePath);
}
@Override
@@ -81,6 +92,7 @@ public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nCSS file location: " + cssFilePath);
return sb.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 60472ca22a0..2f6813bad58 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -14,8 +14,9 @@ public class Address {
/*
* The first character of the address must not be a whitespace,
* otherwise " " (a blank string) becomes a valid input.
+ * Also, empty strings "" are allowed.
*/
- public static final String VALIDATION_REGEX = "[^\\s].*";
+ public static final String VALIDATION_REGEX = "^$|[^\\s].*";
public final String value;
diff --git a/src/main/java/seedu/address/model/person/ContactContainsDescriptionPredicate.java b/src/main/java/seedu/address/model/person/ContactContainsDescriptionPredicate.java
new file mode 100644
index 00000000000..626fe2aed8b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactContainsDescriptionPredicate.java
@@ -0,0 +1,41 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * ContactContainsEmailPredicate is a predicate that filters the model based on address
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class ContactContainsDescriptionPredicate implements Predicate {
+ private final String description;
+
+ public ContactContainsDescriptionPredicate(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ // perform filtering based on description
+ String contactDescription = person.getAddress().toString();
+ String[] matchDescription = this.description.split(" ");
+ String regex = String.join("|", matchDescription);
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(contactDescription);
+ boolean isMatch = matcher.find();
+ if (isMatch) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsDescriptionPredicate // instanceof handles nulls
+ && description.equals(((ContactContainsDescriptionPredicate) other).description)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/ContactContainsEmailPredicate.java b/src/main/java/seedu/address/model/person/ContactContainsEmailPredicate.java
new file mode 100644
index 00000000000..f0373058866
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactContainsEmailPredicate.java
@@ -0,0 +1,30 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+/**
+ * ContactContainsEmailPredicate is a predicate that filters the model based on email
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class ContactContainsEmailPredicate implements Predicate {
+ private final String emailAddr;
+
+ public ContactContainsEmailPredicate(String emailAddr) {
+ this.emailAddr = emailAddr;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ // perform filtering based on email
+ String currentEmailAddr = person.getEmail().toString();
+ return (currentEmailAddr.equals(this.emailAddr) ? true : false);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsEmailPredicate // instanceof handles nulls
+ && emailAddr.equals(((ContactContainsEmailPredicate) other).emailAddr)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/ContactContainsModuleTagPredicate.java b/src/main/java/seedu/address/model/person/ContactContainsModuleTagPredicate.java
new file mode 100644
index 00000000000..7f1a59807b9
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactContainsModuleTagPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import seedu.address.model.tag.ModuleTag;
+
+/**
+ * ContactContainsModuleTagPredicate is a predicate that filters the model based on the module tags
+ */
+public class ContactContainsModuleTagPredicate implements Predicate {
+ private final Set tagSet;
+
+ public ContactContainsModuleTagPredicate(Set tagSet) {
+ this.tagSet = tagSet;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ // perform filtering based on the tag attribute
+ Set personTags = person.getModuleTags();
+ Set tempTagsSet = new HashSet<>(this.tagSet);
+ tempTagsSet.retainAll(personTags);
+ return !tempTagsSet.isEmpty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsModuleTagPredicate // instanceof handles nulls
+ && tagSet.equals(((ContactContainsModuleTagPredicate) other).tagSet)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/ContactContainsPhoneNumberPredicate.java b/src/main/java/seedu/address/model/person/ContactContainsPhoneNumberPredicate.java
new file mode 100644
index 00000000000..5d64f929527
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactContainsPhoneNumberPredicate.java
@@ -0,0 +1,30 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+/**
+ * ContactContainsPhoneNumberPredicate is a predicate that filters the model based on phone number
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class ContactContainsPhoneNumberPredicate implements Predicate {
+ private final String phoneNumber;
+
+ public ContactContainsPhoneNumberPredicate(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ // perform filtering based on phoneNumber
+ String currentNumber = person.getPhone().toString();
+ return (currentNumber.equals(this.phoneNumber) ? true : false);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsPhoneNumberPredicate // instanceof handles nulls
+ && phoneNumber.equals(((ContactContainsPhoneNumberPredicate) other).phoneNumber)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/ContactContainsTagPredicate.java b/src/main/java/seedu/address/model/person/ContactContainsTagPredicate.java
new file mode 100644
index 00000000000..360690bdf1a
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/ContactContainsTagPredicate.java
@@ -0,0 +1,43 @@
+package seedu.address.model.person;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import seedu.address.model.tag.Tag;
+
+
+/**
+ * ContactContainsTagPredicate is a predicate that filters the model based on the tags
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class ContactContainsTagPredicate implements Predicate {
+ // keywords are the different kind of tags
+ private final Set tagSet;
+
+ public ContactContainsTagPredicate(Set tagSet) {
+ this.tagSet = tagSet;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ // perform filtering based on the tag attribute
+ Set personTags = person.getTags();
+ Set tempTagsSet = new HashSet<>(this.tagSet);
+ tempTagsSet.retainAll(personTags);
+ boolean isTagsExists = tempTagsSet.isEmpty();
+ if (isTagsExists) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ContactContainsTagPredicate // instanceof handles nulls
+ && tagSet.equals(((ContactContainsTagPredicate) other).tagSet)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index f866e7133de..86d79fd14d5 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -20,7 +20,8 @@ public class Email {
+ "The domain name must:\n"
+ " - end with a domain label at least 2 characters long\n"
+ " - have each domain label start and end with alphanumeric characters\n"
- + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.";
+ + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."
+ + "Last but not least, allow empty Strings";
// 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 + "]"
@@ -29,7 +30,7 @@ public class Email {
+ "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*";
private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars
private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX;
- public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
+ public static final String VALIDATION_REGEX = "^$|" + LOCAL_PART_REGEX + "@" + DOMAIN_REGEX;
public final String value;
diff --git a/src/main/java/seedu/address/model/person/MatchNamePredicate.java b/src/main/java/seedu/address/model/person/MatchNamePredicate.java
new file mode 100644
index 00000000000..606cd9a88be
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/MatchNamePredicate.java
@@ -0,0 +1,28 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class MatchNamePredicate implements Predicate {
+ private final List names;
+
+ public MatchNamePredicate(List names) {
+ this.names = names;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return names.stream().anyMatch(name -> person.getName().equals(name));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof MatchNamePredicate // instanceof handles nulls
+ && names.equals(((MatchNamePredicate) other).names)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/NameContainsAllKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsAllKeywordsPredicate.java
new file mode 100644
index 00000000000..6e9efc9bc0b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/NameContainsAllKeywordsPredicate.java
@@ -0,0 +1,42 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches all the keywords given.
+ */
+public class NameContainsAllKeywordsPredicate implements Predicate {
+ private final List keywords;
+ public NameContainsAllKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .allMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ NameContainsAllKeywordsPredicate that = (NameContainsAllKeywordsPredicate) o;
+
+ return Objects.equals(keywords, that.keywords);
+ }
+
+ @Override
+ public int hashCode() {
+ return keywords != null ? keywords.hashCode() : 0;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index 8ff1d83fe89..f37d43ec5e7 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.Set;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
/**
@@ -23,17 +24,22 @@ public class Person {
// Data fields
private final Address address;
private final Set tags = new HashSet<>();
+ private final Set moduleTags = new HashSet<>();
+
+ private boolean hidden;
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(Name name, Phone phone, Email email, Address address, Set tags, Set moduleTags) {
+ requireAllNonNull(name, phone, email, address, tags, moduleTags);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
this.tags.addAll(tags);
+ this.moduleTags.addAll(moduleTags);
+ this.hidden = true;
}
public Name getName() {
@@ -48,6 +54,21 @@ public Email getEmail() {
return email;
}
+ /**
+ * toggle hidden.
+ */
+ public void toggleHidden() {
+ if (hidden) {
+ hidden = false;
+ } else {
+ hidden = true;
+ }
+ }
+
+ public boolean getHidden() {
+ return hidden;
+ }
+
public Address getAddress() {
return address;
}
@@ -60,6 +81,10 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ public Set getModuleTags() {
+ return Collections.unmodifiableSet(moduleTags);
+ }
+
/**
* Returns true if both persons have the same name.
* This defines a weaker notion of equality between two persons.
@@ -109,7 +134,7 @@ public String toString() {
.append(getPhone())
.append("; Email: ")
.append(getEmail())
- .append("; Address: ")
+ .append("; Description: ")
.append(getAddress());
Set tags = getTags();
@@ -117,6 +142,12 @@ public String toString() {
builder.append("; Tags: ");
tags.forEach(builder::append);
}
+
+ Set moduleTags = getModuleTags();
+ if (!moduleTags.isEmpty()) {
+ builder.append("; ModuleTags: ");
+ moduleTags.forEach(builder::append);
+ }
return builder.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index 872c76b382f..5db7d25341f 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -11,13 +11,14 @@ public class Phone {
public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
+ "Phone numbers should only be empty or "
+ + "contain numbers, and it should be at least 3 digits long";
+ public static final String VALIDATION_REGEX = "^\\d{3,}$|^$";
public final String value;
/**
* Constructs a {@code Phone}.
- *
+ * Skips the check if Phone field is empty
* @param phone A valid phone number.
*/
public Phone(String phone) {
diff --git a/src/main/java/seedu/address/model/tag/ModuleTag.java b/src/main/java/seedu/address/model/tag/ModuleTag.java
new file mode 100644
index 00000000000..cbc9da9c4ee
--- /dev/null
+++ b/src/main/java/seedu/address/model/tag/ModuleTag.java
@@ -0,0 +1,55 @@
+package seedu.address.model.tag;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Tag in the address book.
+ * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
+ */
+public class ModuleTag {
+
+ public static final String MESSAGE_CONSTRAINTS = "Module Tags names should be alphanumeric";
+ public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+
+ public final String moduleTagName;
+
+ /**
+ * Constructs a {@code Tag}.
+ *
+ * @param tagName A valid tag name.
+ */
+ public ModuleTag(String tagName) {
+ requireNonNull(tagName);
+ checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
+ this.moduleTagName = tagName;
+ }
+
+ /**
+ * Returns true if a given string is a valid tag name.
+ */
+ public static boolean isValidTagName(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ModuleTag // instanceof handles nulls
+ && moduleTagName.equals(((ModuleTag) other).moduleTagName)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return moduleTagName.hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return '[' + moduleTagName + ']';
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..d4a9ef3c855 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -11,6 +11,7 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
/**
@@ -21,22 +22,22 @@ 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")),
+ getTagSet("friends"), getModuleTagSet("CS2103")),
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")),
+ getTagSet("colleagues", "friends"), getModuleTagSet("CS2103")),
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")),
+ getTagSet("neighbours"), getModuleTagSet("CS2103")),
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")),
+ getTagSet("family"), getModuleTagSet("CS2103")),
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")),
+ getTagSet("classmates"), getModuleTagSet("CS2103")),
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"))
+ getTagSet("colleagues"), getModuleTagSet("CS2103"))
};
}
@@ -57,4 +58,10 @@ public static Set getTagSet(String... strings) {
.collect(Collectors.toSet());
}
+ public static Set getModuleTagSet(String... strings) {
+ return Arrays.stream(strings)
+ .map(ModuleTag::new)
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
index 4599182b3f9..92d7bad9c89 100644
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/AddressBookStorage.java
@@ -41,5 +41,4 @@ public interface AddressBookStorage {
* @see #saveAddressBook(ReadOnlyAddressBook)
*/
void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
-
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedModuleTag.java b/src/main/java/seedu/address/storage/JsonAdaptedModuleTag.java
new file mode 100644
index 00000000000..dbdb27a9058
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedModuleTag.java
@@ -0,0 +1,49 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.tag.ModuleTag;
+
+/**
+ * Jackson-friendly version of {@link ModuleTag}.
+ */
+class JsonAdaptedModuleTag {
+
+ private final String moduleTagName;
+
+ /**
+ * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
+ */
+ @JsonCreator
+ public JsonAdaptedModuleTag(String moduleTagName) {
+ this.moduleTagName = moduleTagName;
+ }
+
+ /**
+ * Converts a given {@code Tag} into this class for Jackson use.
+ */
+ public JsonAdaptedModuleTag(ModuleTag source) {
+ moduleTagName = source.moduleTagName;
+ }
+
+ @JsonValue
+ public String getTagName() {
+ return moduleTagName;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
+ */
+ public ModuleTag toModelType() throws IllegalValueException {
+ if (!ModuleTag.isValidTagName(moduleTagName)) {
+ throw new IllegalValueException(ModuleTag.MESSAGE_CONSTRAINTS);
+ }
+ return new ModuleTag(moduleTagName);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index a6321cec2ea..ec67c434b6a 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -15,8 +15,10 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
+
/**
* Jackson-friendly version of {@link Person}.
*/
@@ -29,6 +31,7 @@ class JsonAdaptedPerson {
private final String email;
private final String address;
private final List tagged = new ArrayList<>();
+ private final List moduleTagged = new ArrayList<>();
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
@@ -36,7 +39,8 @@ 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("tagged") List tagged,
+ @JsonProperty("moduleTagged") List moduleTagged) {
this.name = name;
this.phone = phone;
this.email = email;
@@ -44,6 +48,9 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone
if (tagged != null) {
this.tagged.addAll(tagged);
}
+ if (moduleTagged != null) {
+ this.moduleTagged.addAll(moduleTagged);
+ }
}
/**
@@ -57,6 +64,9 @@ public JsonAdaptedPerson(Person source) {
tagged.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ moduleTagged.addAll(source.getModuleTags().stream()
+ .map(JsonAdaptedModuleTag::new)
+ .collect(Collectors.toList()));
}
/**
@@ -66,6 +76,10 @@ public JsonAdaptedPerson(Person source) {
*/
public Person toModelType() throws IllegalValueException {
final List personTags = new ArrayList<>();
+ final List moduleTags = new ArrayList<>();
+ for (JsonAdaptedModuleTag tag : moduleTagged) {
+ moduleTags.add(tag.toModelType());
+ }
for (JsonAdaptedTag tag : tagged) {
personTags.add(tag.toModelType());
}
@@ -103,7 +117,8 @@ public Person toModelType() throws IllegalValueException {
final Address modelAddress = new Address(address);
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ final Set modelModuleTags = new HashSet<>(moduleTags);
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelModuleTags);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..527966ad8a8 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -17,7 +17,7 @@
* An Immutable AddressBook that is serializable to JSON format.
*/
@JsonRootName(value = "addressbook")
-class JsonSerializableAddressBook {
+public class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
diff --git a/src/main/java/seedu/address/storage/JsonUserDataStorage.java b/src/main/java/seedu/address/storage/JsonUserDataStorage.java
new file mode 100644
index 00000000000..eb8a5a1cf9d
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonUserDataStorage.java
@@ -0,0 +1,40 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.ReadOnlyUserData;
+import seedu.address.model.UserData;
+
+/**
+ * A class to access UserData stored in the hard disk as a json file
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class JsonUserDataStorage implements UserDataStorage {
+
+ private Path filepath;
+
+ public JsonUserDataStorage(Path filePath) {
+ this.filepath = filePath;
+ }
+
+ @Override
+ public Path getUserDataFilePath() {
+ return this.filepath;
+ }
+
+ @Override
+ public Optional readUserData() throws DataConversionException {
+ return JsonUtil.readJsonFile(this.filepath, UserData.class);
+ }
+
+ @Override
+ public void saveUserData(ReadOnlyUserData userData) throws IOException {
+ JsonUtil.saveJsonFile(userData, this.filepath);
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index beda8bd9f11..e58fee1d0d0 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -12,7 +12,7 @@
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, UserDataStorage {
@Override
Optional readUserPrefs() throws DataConversionException, IOException;
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 6cfa0162164..984d8db7acb 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -8,7 +8,9 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserData;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
/**
@@ -19,13 +21,16 @@ public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
+ private UserDataStorage userDataStorage;
/**
* Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage,
+ UserDataStorage userDataStorage) {
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.userDataStorage = userDataStorage;
}
// ================ UserPrefs methods ==============================
@@ -75,4 +80,21 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ // ================ Login methods ==============================
+
+ @Override
+ public Path getUserDataFilePath() {
+ return userDataStorage.getUserDataFilePath();
+ }
+
+ @Override
+ public Optional readUserData() throws DataConversionException, IOException {
+ return userDataStorage.readUserData();
+ }
+
+ @Override
+ public void saveUserData(ReadOnlyUserData userData) throws IOException {
+ userDataStorage.saveUserData(userData);
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/UserDataStorage.java b/src/main/java/seedu/address/storage/UserDataStorage.java
new file mode 100644
index 00000000000..e49116be315
--- /dev/null
+++ b/src/main/java/seedu/address/storage/UserDataStorage.java
@@ -0,0 +1,37 @@
+package seedu.address.storage;
+
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataConversionException;
+import seedu.address.model.ReadOnlyUserData;
+import seedu.address.model.UserData;
+
+/**
+ * Represents a storage for user password
+ */
+public interface UserDataStorage {
+
+ /**
+ * Returns the file path of the user password file
+ * @return path of user password file
+ */
+ Path getUserDataFilePath();
+
+ /**
+ * Returns the user data from storage
+ * @return user data
+ * @throws DataConversionException if the data in storage is not in the expected format.
+ * @throws IOException if there was any problem when reading from the storage.
+ */
+ Optional readUserData() throws DataConversionException, IOException;
+
+ /**
+ * Saves the user data to the storage
+ * @param userData cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveUserData(ReadOnlyUserData userData) throws IOException;
+}
diff --git a/src/main/java/seedu/address/ui/CreatePasswordSection.java b/src/main/java/seedu/address/ui/CreatePasswordSection.java
new file mode 100644
index 00000000000..355969a8658
--- /dev/null
+++ b/src/main/java/seedu/address/ui/CreatePasswordSection.java
@@ -0,0 +1,71 @@
+package seedu.address.ui;
+
+import javafx.event.ActionEvent;
+import javafx.event.Event;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import seedu.address.ui.events.CreatePasswordSuccessfulEvent;
+
+/**
+ * Controller for when the user creates a new password
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class CreatePasswordSection extends UiPart {
+ private static final String FXML = "CreatePasswordSection.fxml";
+
+ @FXML
+ private PasswordField firstInputField;
+
+ @FXML
+ private PasswordField secondInputField;
+
+ @FXML
+ private Label errorMessageLabel;
+
+ @FXML
+ private VBox tempContainer;
+
+ public CreatePasswordSection() {
+ super(FXML);
+ }
+
+ @FXML
+ private void handleSubmit(KeyEvent event) {
+ if (event.getCode() == KeyCode.ENTER) {
+ // Check if both passwords match
+ String newPassword = firstInputField.getText();
+ String reEnterPassword = secondInputField.getText();
+ if (newPassword.equals(reEnterPassword)) {
+ // send event to go to loading screen
+ CreatePasswordSuccessfulEvent createPasswordSuccessfulEvent =
+ new CreatePasswordSuccessfulEvent(newPassword);
+ Event.fireEvent(tempContainer, createPasswordSuccessfulEvent);
+ } else {
+ this.errorMessageLabel.setText("Passwords do not match. Please try again.");
+ }
+ }
+ }
+
+ @FXML
+ private void handleButtonSubmit(ActionEvent event) {
+ // Check if both passwords match
+ String newPassword = firstInputField.getText();
+ String reEnterPassword = secondInputField.getText();
+ if (newPassword.equals(reEnterPassword)) {
+ // send event to go to loading screen
+ CreatePasswordSuccessfulEvent createPasswordSuccessfulEvent =
+ new CreatePasswordSuccessfulEvent(newPassword);
+ Event.fireEvent(tempContainer, createPasswordSuccessfulEvent);
+ } else {
+ this.errorMessageLabel.setText("Passwords do not match. Please try again.");
+ }
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/ui/DefaultLoginSection.java b/src/main/java/seedu/address/ui/DefaultLoginSection.java
new file mode 100644
index 00000000000..1fc3619dd77
--- /dev/null
+++ b/src/main/java/seedu/address/ui/DefaultLoginSection.java
@@ -0,0 +1,79 @@
+package seedu.address.ui;
+
+import javafx.event.Event;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import seedu.address.ui.events.AttemptLoginEvent;
+import seedu.address.ui.events.LoginFailEvent;
+import seedu.address.ui.events.ProceedCreatePasswordEvent;
+
+/**
+ * A section of the UI that will display the default login page
+ */
+public class DefaultLoginSection extends UiPart {
+ private static final String FXML = "DefaultLoginSection.fxml";
+ @FXML
+ private Label errorMessageLabel;
+
+ @FXML
+ private PasswordField inputField;
+
+ @FXML
+ private VBox tempContainer;
+
+ private Stage primaryStage;
+
+ /**
+ * Creates a default login page
+ */
+ public DefaultLoginSection(Stage primaryStage) {
+ super(FXML);
+ this.primaryStage = primaryStage;
+ this.addEventHandlers();
+ }
+
+ /**
+ * Handles the event when the user submits his/her response
+ */
+ @FXML
+ private void handleSubmit(KeyEvent event) {
+ if (event.getCode() == KeyCode.ENTER) {
+ String password = inputField.getText().toLowerCase();
+ AttemptLoginEvent attemptLoginEvent = new AttemptLoginEvent(password);
+ Event.fireEvent(tempContainer, attemptLoginEvent);
+ }
+ }
+
+ /**
+ * Adds event handlers to listen for events
+ */
+ private void addEventHandlers() {
+ this.primaryStage.addEventHandler(LoginFailEvent.LOGIN_FAIL_EVENT,
+ this::handleLoginError);
+
+ }
+
+ /**
+ * Handles the event when the user decides to create a new password
+ */
+ @FXML
+ private void handleCreateNewPassword() {
+ ProceedCreatePasswordEvent proceedCreatePasswordEvent = new ProceedCreatePasswordEvent();
+ Event.fireEvent(tempContainer, proceedCreatePasswordEvent);
+ }
+
+ /**
+ * Handles the event when the user fails to login with the given password
+ * @param event
+ */
+ @FXML
+ private void handleLoginError(LoginFailEvent event) {
+ this.errorMessageLabel.setText("Password entered is incorrect. Please try again!");
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..716f201ed7e 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://ay2223s2-cs2103-f10-3.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/LoadingSection.java b/src/main/java/seedu/address/ui/LoadingSection.java
new file mode 100644
index 00000000000..cf848b43821
--- /dev/null
+++ b/src/main/java/seedu/address/ui/LoadingSection.java
@@ -0,0 +1,25 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
+
+/**
+ * The loading section that shows a loading screen before showing the MainWindow
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class LoadingSection extends UiPart {
+ private static final String FXML = "LoadingSection.fxml";
+
+ @FXML
+ private Label loadingLabel;
+
+ public LoadingSection() {
+ super(FXML);
+ }
+
+ public void setPasswordSuccessText() {
+ loadingLabel.setText("Password Created Successfully! You are currently entering into MODCheck...");
+ }
+}
diff --git a/src/main/java/seedu/address/ui/LoginWindow.java b/src/main/java/seedu/address/ui/LoginWindow.java
new file mode 100644
index 00000000000..e07f988c26b
--- /dev/null
+++ b/src/main/java/seedu/address/ui/LoginWindow.java
@@ -0,0 +1,121 @@
+package seedu.address.ui;
+
+
+import java.util.logging.Logger;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.Logic;
+
+/**
+ * Welcome page
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class LoginWindow extends UiPart {
+ private static final String FXML = "LoginWindow.fxml";
+ private final Logger logger = LogsCenter.getLogger(getClass());
+
+ private Stage primaryStage;
+ private Logic logic;
+
+ // Independent Ui parts
+ private WelcomeSection welcomeSection;
+ private LoadingSection loadingSection;
+ private CreatePasswordSection createPasswordSection;
+ private DefaultLoginSection defaultLoginSection;
+
+ @FXML
+ private VBox container;
+
+ /**
+ * Default constructor for Welcome page
+ * @param primaryStage Primary Stage of the whole application
+ */
+ public LoginWindow(Stage primaryStage, Logic logic) {
+ super(FXML, primaryStage);
+ // Set dependencies
+ this.primaryStage = primaryStage;
+ this.logic = logic;
+ setWindowDefaultSize(logic.getGuiSettings());
+ }
+
+ /**
+ * Sets the default size based on {@code guiSettings}.
+ */
+ private void setWindowDefaultSize(GuiSettings guiSettings) {
+ primaryStage.setHeight(guiSettings.getWindowHeight());
+ primaryStage.setWidth(guiSettings.getWindowWidth());
+ if (guiSettings.getWindowCoordinates() != null) {
+ primaryStage.setX(guiSettings.getWindowCoordinates().getX());
+ primaryStage.setY(guiSettings.getWindowCoordinates().getY());
+ }
+ }
+
+ /**
+ * Show the stage
+ */
+ void show() {
+ primaryStage.show();
+ }
+
+ /**
+ * Closes the stage
+ */
+ void close() {
+ primaryStage.close();
+ }
+
+ /**
+ * Fill the Vbox with WelcomeNewUserSection
+ */
+ public void fillWelcomeSection() {
+ int numOfTimesUsed = logic.getNumberOfTimesUsed();
+ if (numOfTimesUsed == 0) {
+ welcomeSection = new WelcomeSection();
+ container.getChildren().add(welcomeSection.getRoot());
+ } else {
+ defaultLoginSection = new DefaultLoginSection(this.primaryStage);
+ container.getChildren().add(defaultLoginSection.getRoot());
+ boolean isNoPassword = logic.getUserHashedPassword().equals("");
+ if (!isNoPassword) {
+ // force user to only reset in the json file if the user forgets the password
+ Label tempOrLabel = (Label) container.lookup("#orLabel");
+ Label tempcreateNewPasswordLabel = (Label) container.lookup("#createNewPasswordLabel");
+ tempOrLabel.setVisible(false);
+ tempcreateNewPasswordLabel.setVisible(false);
+ }
+ }
+ }
+
+ /**
+ * Fill the VBox with creating new password page
+ */
+ public void fillCreateNewPasswordSection() {
+ createPasswordSection = new CreatePasswordSection();
+ container.getChildren().clear();
+ container.getChildren().add(createPasswordSection.getRoot());
+ }
+
+ /**
+ * Fill the Vbox with the loading page
+ */
+ public void fillLoadingSection() {
+ loadingSection = new LoadingSection();
+ container.getChildren().clear();
+ container.getChildren().add(loadingSection.getRoot());
+ }
+
+ /**
+ * Replaces the current loading text with password success text
+ */
+ public void fillPasswordSuccessLoadingSection() {
+ loadingSection.setPasswordSuccessText();
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 9106c3aa6e5..5fd0c244a62 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -1,5 +1,6 @@
package seedu.address.ui;
+import java.io.File;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
@@ -9,6 +10,7 @@
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
+import javafx.stage.FileChooser;
import javafx.stage.Stage;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
@@ -16,6 +18,7 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.exceptions.UiInputRequiredException;
/**
* The Main Window. Provides the basic application layout containing
@@ -24,6 +27,8 @@
public class MainWindow extends UiPart {
private static final String FXML = "MainWindow.fxml";
+ private static final String darkTheme = "view/DarkTheme.css";
+ private static final String lightTheme = "view/LightTheme.css";
private final Logger logger = LogsCenter.getLogger(getClass());
@@ -60,6 +65,9 @@ public MainWindow(Stage primaryStage, Logic logic) {
this.primaryStage = primaryStage;
this.logic = logic;
+ //Set up CSS
+ this.primaryStage.getScene().getStylesheets().add(logic.getCssFilePath());
+
// Configure the UI
setWindowDefaultSize(logic.getGuiSettings());
@@ -147,6 +155,23 @@ public void handleHelp() {
}
}
+ /**
+ * Changes the theme based on the user command
+ */
+ @FXML
+ public void handleTheme(boolean isDarkTheme) {
+ if (isDarkTheme == true) {
+ this.primaryStage.getScene().getStylesheets().remove(lightTheme);
+ this.primaryStage.getScene().getStylesheets().add(darkTheme);
+ logic.setCssFilePath(darkTheme);
+ } else {
+ this.primaryStage.getScene().getStylesheets().remove(darkTheme);
+ this.primaryStage.getScene().getStylesheets().add(lightTheme);
+ logic.setCssFilePath(lightTheme);
+
+ }
+ }
+
void show() {
primaryStage.show();
}
@@ -163,6 +188,20 @@ private void handleExit() {
primaryStage.hide();
}
+ private String handleChooseFile(String commandText) {
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.getExtensionFilters().setAll(
+ new FileChooser.ExtensionFilter("JSON file", "*.json"));
+ fileChooser.setTitle("Select ModCheck data file to load!");
+ File f = fileChooser.showOpenDialog(getPrimaryStage());
+ if (f == null) {
+ commandText += " !";
+ } else {
+ commandText += (" " + f.getAbsolutePath());
+ }
+ return commandText;
+ }
+
public PersonListPanel getPersonListPanel() {
return personListPanel;
}
@@ -174,7 +213,13 @@ public PersonListPanel getPersonListPanel() {
*/
private CommandResult executeCommand(String commandText) throws CommandException, ParseException {
try {
- CommandResult commandResult = logic.execute(commandText);
+ CommandResult commandResult;
+ try {
+ commandResult = logic.execute(commandText);
+ } catch (UiInputRequiredException e) {
+ commandText = handleChooseFile(commandText);
+ commandResult = logic.execute(commandText);
+ }
logger.info("Result: " + commandResult.getFeedbackToUser());
resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());
@@ -182,6 +227,10 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleHelp();
}
+ if (commandResult.isDarkTheme() != null) {
+ handleTheme(commandResult.isDarkTheme());
+ }
+
if (commandResult.isExit()) {
handleExit();
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 7fc927bc5d9..bcf63886e22 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -40,21 +40,31 @@ public class PersonCard extends UiPart {
private Label email;
@FXML
private FlowPane tags;
+ @FXML
+ private FlowPane moduleTags;
/**
* Creates a {@code PersonCode} with the given {@code Person} and index to display.
*/
- public PersonCard(Person person, int displayedIndex) {
+ public PersonCard(Person person, int displayedIndex, boolean hidden) {
super(FXML);
+ String curr = name.getText();
this.person = person;
id.setText(displayedIndex + ". ");
name.setText(person.getName().fullName);
+ phone.setVisible(!hidden);
+ address.setVisible(!hidden);
+ email.setVisible(!hidden);
+
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)));
+ person.getModuleTags().stream()
+ .sorted(Comparator.comparing(moduleTag -> moduleTag.moduleTagName))
+ .forEach(moduleTag -> moduleTags.getChildren().add(new Label(moduleTag.moduleTagName)));
}
@Override
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..7bfc81ba8a5 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -17,6 +17,8 @@ public class PersonListPanel extends UiPart {
private static final String FXML = "PersonListPanel.fxml";
private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
+ private boolean hidden;
+
@FXML
private ListView personListView;
@@ -27,8 +29,8 @@ public PersonListPanel(ObservableList personList) {
super(FXML);
personListView.setItems(personList);
personListView.setCellFactory(listView -> new PersonListViewCell());
+ hidden = true;
}
-
/**
* Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
*/
@@ -41,7 +43,7 @@ protected void updateItem(Person person, boolean empty) {
setGraphic(null);
setText(null);
} else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
+ setGraphic(new PersonCard(person, getIndex() + 1, person.getHidden()).getRoot());
}
}
}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..9bbc568618f 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -1,16 +1,25 @@
package seedu.address.ui;
+import java.io.IOException;
import java.util.logging.Logger;
+import javafx.animation.PauseTransition;
import javafx.application.Platform;
+import javafx.event.Event;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.stage.Stage;
+import javafx.util.Duration;
import seedu.address.MainApp;
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.Logic;
+import seedu.address.ui.events.AttemptLoginEvent;
+import seedu.address.ui.events.CreatePasswordSuccessfulEvent;
+import seedu.address.ui.events.LoginFailEvent;
+import seedu.address.ui.events.ProceedCreatePasswordEvent;
+import seedu.address.ui.events.SkipCreatePasswordEvent;
/**
* The manager of the UI component.
@@ -24,7 +33,9 @@ public class UiManager implements Ui {
private Logic logic;
private MainWindow mainWindow;
+ private LoginWindow loginWindow;
+ private Stage primaryStage;
/**
* Creates a {@code UiManager} with the given {@code Logic}.
*/
@@ -34,15 +45,14 @@ public UiManager(Logic logic) {
@Override
public void start(Stage primaryStage) {
+ this.primaryStage = primaryStage;
logger.info("Starting UI...");
//Set the application icon.
- primaryStage.getIcons().add(getImage(ICON_APPLICATION));
+ this.primaryStage.getIcons().add(getImage(ICON_APPLICATION));
try {
- mainWindow = new MainWindow(primaryStage, logic);
- mainWindow.show(); //This should be called before creating other UI parts
- mainWindow.fillInnerParts();
+ this.showLoginWindow();
} catch (Throwable e) {
logger.severe(StringUtil.getDetails(e));
@@ -50,6 +60,133 @@ public void start(Stage primaryStage) {
}
}
+ /**
+ * Shows the main window which is the main application
+ */
+ public void showMainWindow() {
+ mainWindow = new MainWindow(this.primaryStage, logic);
+ mainWindow.show(); //This should be called before creating other UI parts
+ mainWindow.fillInnerParts();
+ }
+
+ /**
+ * Shows the welcome page
+ * @throws IOException Input Output Exception
+ */
+ public void showLoginWindow() {
+ try {
+ loginWindow = new LoginWindow(this.primaryStage, logic);
+ loginWindow.show();
+ loginWindow.fillWelcomeSection();
+ this.attachEventHandlers();
+ } catch (Throwable e) {
+ String error = e.getCause().getMessage();
+ logger.severe(StringUtil.getDetails(e));
+ showFatalErrorDialogAndShutdown("Fatal error when switching scenes", e);
+ }
+ }
+
+ /**
+ * Switches the next scene to enter password page
+ * @param event ProceedCreatePasswordEvent
+ */
+ private void onProceedCreatePassword(ProceedCreatePasswordEvent event) {
+ try {
+ loginWindow.fillCreateNewPasswordSection();
+ } catch (Throwable e) {
+ logger.severe(StringUtil.getDetails(e));
+ showFatalErrorDialogAndShutdown("Fatal error when switching scenes", e);
+ }
+ }
+
+ /**
+ * Switches the current stage to another stage that holds the MainWindow
+ * @param event SkipCreatePasswordEvent
+ */
+ private void onSkipCreatePassword(SkipCreatePasswordEvent event) {
+ try {
+ // show the loading section first
+ loginWindow.fillLoadingSection();
+ // then show the real app after X seconds
+ PauseTransition delay = new PauseTransition(Duration.seconds(3));
+ delay.setOnFinished(currentEvent -> {
+ loginWindow.close();
+ this.showMainWindow();
+ });
+ delay.play();
+ } catch (Throwable e) {
+ logger.severe(StringUtil.getDetails(e));
+ showFatalErrorDialogAndShutdown("Fatal error when switching stage", e);
+ }
+ }
+
+ /**
+ * Switches the current scene to show password created successfully scene
+ * @param event CreatePasswordSuccessfulEvent
+ */
+ private void onCreatePasswordSuccess(CreatePasswordSuccessfulEvent event) {
+ try {
+ // save the password
+ logic.setUserHashedPassword(event.getHashedPassword());
+ this.launchModcheckWindow();
+ } catch (Throwable e) {
+ logger.severe(StringUtil.getDetails(e));
+ showFatalErrorDialogAndShutdown("Fatal error when switching scenes", e);
+ }
+ }
+
+ /**
+ * Switches the current scene to its respective scene based on the user's
+ * login attempt
+ * @param event AttemptLoginEvent
+ */
+ private void onAttemptLogin(AttemptLoginEvent event) {
+ try {
+ String storedPassword = logic.getUserHashedPassword();
+ String userSubmittedPassword = event.getHashedPassword();
+ if (storedPassword.equals(userSubmittedPassword)) {
+ // show the loading section first
+ loginWindow.fillLoadingSection();
+ // then show the real app after X seconds
+ PauseTransition delay = new PauseTransition(Duration.seconds(3));
+ delay.setOnFinished(currentEvent -> {
+ loginWindow.close();
+ this.showMainWindow();
+ });
+ delay.play();
+ } else {
+ LoginFailEvent loginFailEvent = new LoginFailEvent();
+ Event.fireEvent(this.primaryStage, loginFailEvent);
+ }
+ } catch (Throwable e) {
+ logger.severe(StringUtil.getDetails(e));
+ showFatalErrorDialogAndShutdown("Fatal error when switching scenes", e);
+ }
+ }
+
+ private void attachEventHandlers() {
+ // Observer design pattern is used here to register events
+ this.primaryStage.addEventHandler(ProceedCreatePasswordEvent.PROCEED_CREATE_PASSWORD,
+ this::onProceedCreatePassword);
+ this.primaryStage.addEventHandler(SkipCreatePasswordEvent.SKIP_CREATE_PASSWORD_EVENT,
+ this::onSkipCreatePassword);
+ this.primaryStage.addEventHandler(CreatePasswordSuccessfulEvent.CREATE_PASSWORD_SUCCESSFUL_EVENT,
+ this::onCreatePasswordSuccess);
+ this.primaryStage.addEventHandler(AttemptLoginEvent.ATTEMPT_LOGIN_EVENT,
+ this::onAttemptLogin);
+ }
+
+ private void launchModcheckWindow() {
+ loginWindow.fillLoadingSection();
+ loginWindow.fillPasswordSuccessLoadingSection();
+ PauseTransition delay = new PauseTransition(Duration.seconds(3));
+ delay.setOnFinished(currentEvent -> {
+ loginWindow.close();
+ this.showMainWindow();
+ });
+ delay.play();
+ }
+
private Image getImage(String imagePath) {
return new Image(MainApp.class.getResourceAsStream(imagePath));
}
diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java
index fc820e01a9c..b27343aa48a 100644
--- a/src/main/java/seedu/address/ui/UiPart.java
+++ b/src/main/java/seedu/address/ui/UiPart.java
@@ -85,4 +85,8 @@ private static URL getFxmlFileUrl(String fxmlFileName) {
return requireNonNull(fxmlFileUrl);
}
+ public void hashPassword(String password) {
+ // hashing to be done in v1.4
+ }
+
}
diff --git a/src/main/java/seedu/address/ui/WelcomeSection.java b/src/main/java/seedu/address/ui/WelcomeSection.java
new file mode 100644
index 00000000000..efbcf3028eb
--- /dev/null
+++ b/src/main/java/seedu/address/ui/WelcomeSection.java
@@ -0,0 +1,55 @@
+package seedu.address.ui;
+
+import javafx.event.Event;
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import seedu.address.ui.events.ProceedCreatePasswordEvent;
+import seedu.address.ui.events.SkipCreatePasswordEvent;
+
+/**
+ * Ui to display the Welcome Section if it is the first time
+ * the user is using the app
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class WelcomeSection extends UiPart {
+
+ private static final String FXML = "WelcomeSection.fxml";
+ @FXML
+ private Label errorMessageLabel;
+
+ @FXML
+ private TextField inputField;
+
+ @FXML
+ private VBox tempContainer;
+
+ public WelcomeSection() {
+ super(FXML);
+ }
+
+ /**
+ * Handles the event when the user submits his/her response
+ */
+ @FXML
+ private void handleSubmit(KeyEvent event) {
+ if (event.getCode() == KeyCode.ENTER) {
+ String input = inputField.getText().toLowerCase();
+ if (input.equals("yes") || input.equals("y")) {
+ ProceedCreatePasswordEvent proceedCreatePasswordEvent = new ProceedCreatePasswordEvent();
+ Event.fireEvent(tempContainer, proceedCreatePasswordEvent);
+ } else if (input.equals("no") || input.equals("n")) {
+ SkipCreatePasswordEvent skipCreatePasswordEvent = new SkipCreatePasswordEvent();
+ Event.fireEvent(tempContainer, skipCreatePasswordEvent);
+ } else {
+ // show error message
+ this.errorMessageLabel.setText("Please enter only `yes`,`y`, `no`,`n` only!");
+ }
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/ui/events/AttemptLoginEvent.java b/src/main/java/seedu/address/ui/events/AttemptLoginEvent.java
new file mode 100644
index 00000000000..99b5b483226
--- /dev/null
+++ b/src/main/java/seedu/address/ui/events/AttemptLoginEvent.java
@@ -0,0 +1,34 @@
+package seedu.address.ui.events;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * An event that will trigger when user attempts to login
+ * with given password
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class AttemptLoginEvent extends Event {
+ public static final EventType ATTEMPT_LOGIN_EVENT =
+ new EventType<>("ATTEMPT_LOGIN");
+
+ private String hashedPassword;
+
+ /**
+ * Creates an attempt login event with the user's password
+ * @param hashedPassword of user
+ */
+ public AttemptLoginEvent(String hashedPassword) {
+ super(ATTEMPT_LOGIN_EVENT);
+ this.hashedPassword = hashedPassword;
+ }
+
+ /**
+ * Get the password of the user
+ * @return password of the user
+ */
+ public String getHashedPassword() {
+ return this.hashedPassword;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/events/CreatePasswordSuccessfulEvent.java b/src/main/java/seedu/address/ui/events/CreatePasswordSuccessfulEvent.java
new file mode 100644
index 00000000000..5d47753027b
--- /dev/null
+++ b/src/main/java/seedu/address/ui/events/CreatePasswordSuccessfulEvent.java
@@ -0,0 +1,27 @@
+package seedu.address.ui.events;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * Event that happens when user creates a new password successfully
+ */
+public class CreatePasswordSuccessfulEvent extends Event {
+ public static final EventType CREATE_PASSWORD_SUCCESSFUL_EVENT =
+ new EventType<>("CREATE_PASSWORD_SUCCESSFUL");
+
+ private String hashedPassword;
+
+ /**
+ * Contructor for CreatePasswordSuccessfulEvent that requires the user's password
+ * @param hashedPassword
+ */
+ public CreatePasswordSuccessfulEvent(String hashedPassword) {
+ super(CREATE_PASSWORD_SUCCESSFUL_EVENT);
+ this.hashedPassword = hashedPassword;
+ }
+
+ public String getHashedPassword() {
+ return this.hashedPassword;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/events/LoginFailEvent.java b/src/main/java/seedu/address/ui/events/LoginFailEvent.java
new file mode 100644
index 00000000000..c788ceb73e8
--- /dev/null
+++ b/src/main/java/seedu/address/ui/events/LoginFailEvent.java
@@ -0,0 +1,19 @@
+package seedu.address.ui.events;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * An event that will be triggered when user fails to login
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class LoginFailEvent extends Event {
+ public static final EventType LOGIN_FAIL_EVENT =
+ new EventType<>("LOGIN_FAIL");
+
+ public LoginFailEvent() {
+ super(LOGIN_FAIL_EVENT);
+ }
+
+}
diff --git a/src/main/java/seedu/address/ui/events/ProceedCreatePasswordEvent.java b/src/main/java/seedu/address/ui/events/ProceedCreatePasswordEvent.java
new file mode 100644
index 00000000000..7dffd76e16d
--- /dev/null
+++ b/src/main/java/seedu/address/ui/events/ProceedCreatePasswordEvent.java
@@ -0,0 +1,18 @@
+package seedu.address.ui.events;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * An event class that is created when user decides to create a password in the welcom page
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class ProceedCreatePasswordEvent extends Event {
+ public static final EventType PROCEED_CREATE_PASSWORD =
+ new EventType<>("PROCEED_CREATE_PASSWORD");
+
+ public ProceedCreatePasswordEvent() {
+ super(PROCEED_CREATE_PASSWORD);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/events/SkipCreatePasswordEvent.java b/src/main/java/seedu/address/ui/events/SkipCreatePasswordEvent.java
new file mode 100644
index 00000000000..90e739b9bdf
--- /dev/null
+++ b/src/main/java/seedu/address/ui/events/SkipCreatePasswordEvent.java
@@ -0,0 +1,18 @@
+package seedu.address.ui.events;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+
+/**
+ * Event that occurs when user skips creating password for the application
+ *
+ * @author Haiqel Bin Hanaffi
+ */
+public class SkipCreatePasswordEvent extends Event {
+ public static final EventType SKIP_CREATE_PASSWORD_EVENT =
+ new EventType<>("SKIP_CREATE_PASSWORD_EVENT");
+
+ public SkipCreatePasswordEvent() {
+ super(SKIP_CREATE_PASSWORD_EVENT);
+ }
+}
diff --git a/src/main/resources/view/CreatePasswordSection.fxml b/src/main/resources/view/CreatePasswordSection.fxml
new file mode 100644
index 00000000000..b4b4fddc7c9
--- /dev/null
+++ b/src/main/resources/view/CreatePasswordSection.fxml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..5508d0ffdef 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -350,3 +350,22 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#moduleTags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#moduleTags .label {
+ -fx-text-fill: white;
+ -fx-background-color: orange;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: #383838;
+}
diff --git a/src/main/resources/view/DefaultLoginSection.fxml b/src/main/resources/view/DefaultLoginSection.fxml
new file mode 100644
index 00000000000..3fcca67d11d
--- /dev/null
+++ b/src/main/resources/view/DefaultLoginSection.fxml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css
index bfe82a85964..414e95396cd 100644
--- a/src/main/resources/view/Extensions.css
+++ b/src/main/resources/view/Extensions.css
@@ -3,11 +3,6 @@
-fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */
}
-.list-cell:empty {
- /* Empty cells will not have alternating colours */
- -fx-background: #383838;
-}
-
.tag-selector {
-fx-border-width: 1;
-fx-border-color: white;
diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css
new file mode 100644
index 00000000000..6d04e3c72a0
--- /dev/null
+++ b/src/main/resources/view/LightTheme.css
@@ -0,0 +1,372 @@
+.background {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ background-color: #383838; /* Used in the default.html file */
+}
+
+.label {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: #555555;
+ -fx-opacity: 0.9;
+}
+
+.label-bright {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.label-header {
+ -fx-font-size: 32pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.text-field {
+ -fx-font-size: 12pt;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+.tab-pane {
+ -fx-padding: 0 0 0 1;
+}
+
+.tab-pane .tab-header-area {
+ -fx-padding: 0 0 0 0;
+ -fx-min-height: 0;
+ -fx-max-height: 0;
+}
+
+.table-view {
+ -fx-base: #1d1d1d;
+ -fx-control-inner-background: #1d1d1d;
+ -fx-background-color: #1d1d1d;
+ -fx-table-cell-border-color: transparent;
+ -fx-table-header-border-color: transparent;
+ -fx-padding: 5;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: transparent;
+}
+
+.table-view .column-header, .table-view .filler {
+ -fx-size: 35;
+ -fx-border-width: 0 0 1 0;
+ -fx-background-color: transparent;
+ -fx-border-color:
+ transparent
+ transparent
+ derive(-fx-base, 80%)
+ transparent;
+ -fx-border-insets: 0 10 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-font-size: 20pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-alignment: center-left;
+ -fx-opacity: 1;
+}
+
+.table-view:focused .table-row-cell:filled:focused:selected {
+ -fx-background-color: -fx-focus-color;
+}
+
+.split-pane:horizontal .split-pane-divider {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: transparent transparent transparent #4d4d4d;
+}
+
+.split-pane {
+ -fx-border-radius: 1;
+ -fx-border-width: 1;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-view {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(#C3B091, 20%);
+}
+
+.list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+.list-cell:filled:even {
+ -fx-background-color: #9F8E72;
+}
+
+.list-cell:filled:odd {
+ -fx-background-color: #D8CCB9;
+}
+
+.list-cell:filled:selected {
+ -fx-background-color: #424d5f;
+}
+
+.list-cell:filled:selected #cardPane {
+ -fx-border-color: #3e7b91;
+ -fx-border-width: 1;
+}
+
+.list-cell .label {
+ -fx-text-fill: black;
+}
+
+.cell_big_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-text-fill: #010504;
+}
+
+.cell_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+.stack-pane {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.pane-with-border {
+ -fx-background-color: derive(#C3B091, 20%);
+ -fx-border-color: derive(#9F9066, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.status-bar {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.result-display {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: black;
+}
+
+.result-display .label {
+ -fx-text-fill: black !important;
+}
+
+.status-bar .label {
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-padding: 4px;
+ -fx-pref-height: 30px;
+}
+
+.status-bar-with-border {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-border-width: 1px;
+}
+
+.status-bar-with-border .label {
+ -fx-text-fill: white;
+}
+
+.grid-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-border-width: 1px;
+}
+
+.grid-pane .stack-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.context-menu {
+ -fx-background-color: derive(#C3B091, 50%);
+}
+
+.context-menu .label {
+ -fx-text-fill: white;
+}
+
+.menu-bar {
+ -fx-background-color: derive(#C3B091, 20%);
+}
+
+.menu-bar .label {
+ -fx-font-size: 14pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: black;
+ -fx-opacity: 0.9;
+}
+
+.menu .left-container {
+ -fx-background-color: black;
+}
+
+/*
+ * Metro style Push Button
+ * Author: Pedro Duque Vieira
+ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
+ */
+.button {
+ -fx-padding: 5 22 5 22;
+ -fx-border-color: #e2e2e2;
+ -fx-border-width: 2;
+ -fx-background-radius: 0;
+ -fx-background-color: #1d1d1d;
+ -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-size: 11pt;
+ -fx-text-fill: #d8d8d8;
+ -fx-background-insets: 0 0 0 0, 0, 1, 2;
+}
+
+.button:hover {
+ -fx-background-color: #3a3a3a;
+}
+
+.button:pressed, .button:default:hover:pressed {
+ -fx-background-color: white;
+ -fx-text-fill: #1d1d1d;
+}
+
+.button:focused {
+ -fx-border-color: white, white;
+ -fx-border-width: 1, 1;
+ -fx-border-style: solid, segments(1, 1);
+ -fx-border-radius: 0, 0;
+ -fx-border-insets: 1 1 1 1, 0;
+}
+
+.button:disabled, .button:default:disabled {
+ -fx-opacity: 0.4;
+ -fx-background-color: #1d1d1d;
+ -fx-text-fill: white;
+}
+
+.button:default {
+ -fx-background-color: -fx-focus-color;
+ -fx-text-fill: #ffffff;
+}
+
+.button:default:hover {
+ -fx-background-color: derive(-fx-focus-color, 30%);
+}
+
+.dialog-pane {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.button-bar > *.container {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.label.content {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header *.header-panel {
+ -fx-background-color: derive(#1d1d1d, 25%);
+}
+
+.dialog-pane:header *.header-panel *.label {
+ -fx-font-size: 18px;
+ -fx-font-style: italic;
+ -fx-fill: white;
+ -fx-text-fill: white;
+}
+
+.scroll-bar {
+ -fx-background-color: derive(#C3B091, 20%);
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: derive(#1d1d1d, 70%);
+ -fx-background-insets: 3;
+}
+
+.scroll-bar .increment-button, .scroll-bar .decrement-button {
+ -fx-background-color: transparent;
+ -fx-padding: 0 0 0 0;
+}
+
+.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow {
+ -fx-shape: " ";
+}
+
+.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
+ -fx-padding: 1 8 1 8;
+}
+
+.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
+ -fx-padding: 8 1 8 1;
+}
+
+#cardPane {
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+}
+
+#commandTypeLabel {
+ -fx-font-size: 11px;
+ -fx-text-fill: #F70D1A;
+}
+
+#commandTextField {
+ -fx-background-color: derive(#C3B091, 20%);
+ -fx-background-insets: 0;
+ -fx-border-color: #d0bca4 #d0bca4 black #d0bca4;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: black;
+ -fx-prompt-text-fill: black;
+}
+
+#filterField, #personListPanel, #personWebpage {
+ -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
+}
+
+#resultDisplay .content {
+ -fx-background-color: derive(#C3B091, 20%);
+ -fx-background-radius: 0;
+}
+
+#tags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#tags .label {
+ -fx-text-fill: white;
+ -fx-background-color: #3e7b91;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+#moduleTags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#moduleTags .label {
+ -fx-text-fill: white;
+ -fx-background-color: orange;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: derive(#C3B091, 20%);
+}
diff --git a/src/main/resources/view/LoadingSection.fxml b/src/main/resources/view/LoadingSection.fxml
new file mode 100644
index 00000000000..2f1373dae7c
--- /dev/null
+++ b/src/main/resources/view/LoadingSection.fxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/LoginWindow.fxml b/src/main/resources/view/LoginWindow.fxml
new file mode 100644
index 00000000000..0e25b02ce26
--- /dev/null
+++ b/src/main/resources/view/LoginWindow.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index a431648f6c0..b1417a76b36 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -12,14 +12,13 @@
+ title="ModCheck" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
-
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f08ea32ad55..568f5c0d646 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -27,6 +27,7 @@
+
diff --git a/src/main/resources/view/WelcomePage.css b/src/main/resources/view/WelcomePage.css
new file mode 100644
index 00000000000..527f27b1283
--- /dev/null
+++ b/src/main/resources/view/WelcomePage.css
@@ -0,0 +1,11 @@
+#welcomeMessage {
+ -fx-font-family: Arial;
+ -fx-font-size: 30px;
+}
+
+#secondMessage {
+ -fx-font-family: Arial;
+ -fx-font-size: 20px;
+}
+
+
diff --git a/src/main/resources/view/WelcomeSection.fxml b/src/main/resources/view/WelcomeSection.fxml
new file mode 100644
index 00000000000..d87831af475
--- /dev/null
+++ b/src/main/resources/view/WelcomeSection.fxml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonAddressBookStorageTest/ValidAddressBook1.json b/src/test/data/JsonAddressBookStorageTest/ValidAddressBook1.json
new file mode 100644
index 00000000000..aa59f2406f4
--- /dev/null
+++ b/src/test/data/JsonAddressBookStorageTest/ValidAddressBook1.json
@@ -0,0 +1,38 @@
+{
+ "persons" : [ {
+ "name" : "One",
+ "phone" : "11111111",
+ "email" : "one111@one.com",
+ "address" : "One",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Two",
+ "phone" : "22222222",
+ "email" : "two222@two.com",
+ "address" : "Two",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Three",
+ "phone" : "33333333",
+ "email" : "three333@three.com",
+ "address" : "Three",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Four",
+ "phone" : "44444444",
+ "email" : "four444@four.com",
+ "address" : "Four",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ }, {
+ "name" : "Five",
+ "phone" : "55555555",
+ "email" : "Five555@five.com",
+ "address" : "Five",
+ "tagged" : [ ],
+ "moduleTagged" : [ ]
+ } ]
+}
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index ad923ac249a..ba6d159f92f 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -25,9 +25,11 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserDataStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.StorageManager;
import seedu.address.testutil.PersonBuilder;
@@ -46,7 +48,8 @@ public void setUp() {
JsonAddressBookStorage addressBookStorage =
new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(temporaryFolder.resolve("userData.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
logic = new LogicManager(model, storage);
}
@@ -75,7 +78,8 @@ public void execute_storageThrowsIoException_throwsCommandException() {
new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json"));
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(temporaryFolder.resolve("userData.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
logic = new LogicManager(model, storage);
// Execute add command
@@ -129,7 +133,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index cb8714bb055..d96a86ad479 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -9,6 +9,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -22,14 +23,14 @@ public class AddCommandIntegrationTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
}
@Test
public void execute_newPerson_success() {
Person validPerson = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getUserData());
expectedModel.addPerson(validPerson);
assertCommandSuccess(new AddCommand(validPerson), model,
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 5865713d5dd..d8545e4108a 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -9,6 +9,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
@@ -19,7 +20,9 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserData;
import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.Undoable;
import seedu.address.model.person.Person;
import seedu.address.testutil.PersonBuilder;
@@ -77,7 +80,7 @@ public void equals() {
/**
* A default model stub that have all of the methods failing.
*/
- private class ModelStub implements Model {
+ private class ModelStub implements Model, Undoable {
@Override
public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
throw new AssertionError("This method should not be called.");
@@ -108,8 +111,19 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public String getCssFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setCssFilePath(String cssFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public void addPerson(Person person) {
+
throw new AssertionError("This method should not be called.");
}
@@ -133,6 +147,10 @@ public void deletePerson(Person target) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void deleteMultiplePersons(List listOfPeople) {
+ throw new AssertionError("This method should not be called.");
+ }
@Override
public void setPerson(Person target, Person editedPerson) {
throw new AssertionError("This method should not be called.");
@@ -147,6 +165,62 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public void combine(ReadOnlyAddressBook ab, String s) {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public void resetPersonHiddenStatus() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void showPersonContact(List personList) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setHashedPassword(String hashedPassword) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public String getHashedPassword() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setNumberOfTimesUsed(int numberOfTimesUsed) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public int getNumberOfTimesUsed() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserData getUserData() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasUndoableCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public String executeUndo() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public boolean hasRedoableCommand() {
+ throw new AssertionError("This method should not be called.");
+ }
+ @Override
+ public String executeRedo() {
+ throw new AssertionError("This method should not be called.");
+ }
+
}
/**
@@ -185,10 +259,23 @@ public void addPerson(Person person) {
personsAdded.add(person);
}
+ @Override
+ public void resetPersonHiddenStatus() {
+ personsAdded.stream().forEach(x -> {
+ if (!x.getHidden()) {
+ x.toggleHidden();
+ }
+ });
+ }
+
+ @Override
+ public void showPersonContact(List personList) {
+ personList.stream().forEach(x -> x.toggleHidden());
+ }
+
@Override
public ReadOnlyAddressBook getAddressBook() {
return new AddressBook();
}
}
-
}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..5b6b2b1379c 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -8,6 +8,7 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
public class ClearCommandTest {
@@ -22,8 +23,8 @@ public void execute_emptyAddressBook_success() {
@Test
public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
expectedModel.setAddressBook(new AddressBook());
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..eaceeffc1d0 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -51,7 +51,7 @@ public class CommandTestUtil {
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
- public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
+ public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS + " "; // blank string not allowed
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
diff --git a/src/test/java/seedu/address/logic/commands/DarkCommandTest.java b/src/test/java/seedu/address/logic/commands/DarkCommandTest.java
new file mode 100644
index 00000000000..e7deee2b237
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DarkCommandTest.java
@@ -0,0 +1,45 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.DarkCommand.MESSAGE_ERROR;
+import static seedu.address.logic.commands.DarkCommand.MESSAGE_SUCCESS;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+
+public class DarkCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ //Default Start up Dark Theme
+ //Expected Behaviour: Error
+ @Test
+ public void execute_defaultdark_failure() {
+ assertCommandFailure(new DarkCommand(), model, MESSAGE_ERROR);
+ }
+
+ //From Light Theme
+ //Expected Behaviour to change from Light Theme -> Dark Theme
+ @Test
+ public void execute_light_success() {
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_SUCCESS, true);
+ model.setCssFilePath("view/LightTheme.css");
+ expectedModel.setCssFilePath("view/LightTheme.css");
+ assertCommandSuccess(new DarkCommand(), model, expectedCommandResult, expectedModel);
+ }
+
+ //Already in Dark Theme
+ //Expected Behavior : Throw error
+ @Test
+ void execute_light_failure() {
+ model.setCssFilePath("view/DarkTheme.css");
+ assertCommandFailure(new DarkCommand(), model, MESSAGE_ERROR);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index 45a8c910ba1..be92074ac45 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -15,6 +15,7 @@
import seedu.address.commons.core.index.Index;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
@@ -24,16 +25,16 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
@Test
public void execute_validIndexUnfilteredList_success() {
Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteCommand = new DeleteSingleIndexCommand(INDEX_FIRST_PERSON);
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
expectedModel.deletePerson(personToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -42,7 +43,7 @@ public void execute_validIndexUnfilteredList_success() {
@Test
public void execute_invalidIndexUnfilteredList_throwsCommandException() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteCommand deleteCommand = new DeleteSingleIndexCommand(outOfBoundIndex);
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@@ -52,11 +53,11 @@ public void execute_validIndexFilteredList_success() {
showPersonAtIndex(model, INDEX_FIRST_PERSON);
Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteCommand = new DeleteSingleIndexCommand(INDEX_FIRST_PERSON);
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete);
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
expectedModel.deletePerson(personToDelete);
showNoPerson(expectedModel);
@@ -71,21 +72,21 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
// ensures that outOfBoundIndex is still in bounds of address book list
assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteCommand deleteCommand = new DeleteSingleIndexCommand(outOfBoundIndex);
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@Test
public void equals() {
- DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
- DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON);
+ DeleteCommand deleteFirstCommand = new DeleteSingleIndexCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteSecondCommand = new DeleteSingleIndexCommand(INDEX_SECOND_PERSON);
// same object -> returns true
assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
// same values -> returns true
- DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteCommand deleteFirstCommandCopy = new DeleteSingleIndexCommand(INDEX_FIRST_PERSON);
assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
// different types -> returns false
diff --git a/src/test/java/seedu/address/logic/commands/DeleteMultipleIndexCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteMultipleIndexCommandTest.java
new file mode 100644
index 00000000000..d3112aa99a1
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteMultipleIndexCommandTest.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+
+
+class DeleteMultipleIndexCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ @Test
+ void execute_deleteMultipleValidIndex_success() {
+ List list = model.getFilteredPersonList();
+ int size = list.size();
+ System.out.println(size);
+ ArrayList indexes = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ indexes.add(Index.fromZeroBased(i));
+ }
+ DeleteMultipleIndexCommand deleteCommand = new DeleteMultipleIndexCommand(indexes);
+
+ String expectedMessage = String.format(DeleteMultipleIndexCommand.MESSAGE_DELETE_PERSON_SUCCESS);
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new UserData());
+ expectedModel.deleteMultiplePersons(new ArrayList<>(list));
+
+ assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
+ }
+
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ DeleteCommand deleteCommand = new DeleteMultipleIndexCommand(List.of(INDEX_FIRST_PERSON,
+ outOfBoundIndex));
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_MULTIPLE_INDEX);
+ }
+
+ @Test
+ public void execute_invalidIndexReversedFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ DeleteCommand deleteCommand = new DeleteMultipleIndexCommand(List.of(outOfBoundIndex,
+ INDEX_FIRST_PERSON));
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_MULTIPLE_INDEX);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditByIndexCommandTest.java
similarity index 72%
rename from src/test/java/seedu/address/logic/commands/EditCommandTest.java
rename to src/test/java/seedu/address/logic/commands/EditByIndexCommandTest.java
index 214c6c2507b..1cbcc97e99c 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditByIndexCommandTest.java
@@ -22,6 +22,7 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -30,22 +31,23 @@
/**
* Contains integration tests (interaction with the Model) and unit tests for EditCommand.
*/
-public class EditCommandTest {
+public class EditByIndexCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
Person editedPerson = new PersonBuilder().build();
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(INDEX_FIRST_PERSON, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new UserData());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ assertCommandSuccess(editByIndexCommand, model, expectedMessage, expectedModel);
}
@Test
@@ -59,26 +61,28 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
- EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(indexLastPerson, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new UserData());
expectedModel.setPerson(lastPerson, editedPerson);
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ assertCommandSuccess(editByIndexCommand, model, expectedMessage, expectedModel);
}
@Test
public void execute_noFieldSpecifiedUnfilteredList_success() {
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor());
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor());
Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new UserData());
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ assertCommandSuccess(editByIndexCommand, model, expectedMessage, expectedModel);
}
@Test
@@ -87,24 +91,25 @@ public void execute_filteredList_success() {
Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build();
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(INDEX_FIRST_PERSON,
new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson);
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs(),
+ new UserData());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ assertCommandSuccess(editByIndexCommand, model, expectedMessage, expectedModel);
}
@Test
public void execute_duplicatePersonUnfilteredList_failure() {
Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build();
- EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor);
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(INDEX_SECOND_PERSON, descriptor);
- assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
+ assertCommandFailure(editByIndexCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
}
@Test
@@ -113,19 +118,19 @@ public void execute_duplicatePersonFilteredList_failure() {
// edit person in filtered list into a duplicate in address book
Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased());
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(INDEX_FIRST_PERSON,
new EditPersonDescriptorBuilder(personInList).build());
- assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
+ assertCommandFailure(editByIndexCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
}
@Test
public void execute_invalidPersonIndexUnfilteredList_failure() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build();
- EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor);
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(outOfBoundIndex, descriptor);
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(editByIndexCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
/**
@@ -139,19 +144,19 @@ public void execute_invalidPersonIndexFilteredList_failure() {
// ensures that outOfBoundIndex is still in bounds of address book list
assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
- EditCommand editCommand = new EditCommand(outOfBoundIndex,
+ EditByIndexCommand editByIndexCommand = new EditByIndexCommand(outOfBoundIndex,
new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(editByIndexCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@Test
public void equals() {
- final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
+ final EditByIndexCommand standardCommand = new EditByIndexCommand(INDEX_FIRST_PERSON, DESC_AMY);
// same values -> returns true
EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY);
- EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor);
+ EditByIndexCommand commandWithSameValues = new EditByIndexCommand(INDEX_FIRST_PERSON, copyDescriptor);
assertTrue(standardCommand.equals(commandWithSameValues));
// same object -> returns true
@@ -164,10 +169,10 @@ public void equals() {
assertFalse(standardCommand.equals(new ClearCommand()));
// different index -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY)));
+ assertFalse(standardCommand.equals(new EditByIndexCommand(INDEX_SECOND_PERSON, DESC_AMY)));
// different descriptor -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB)));
+ assertFalse(standardCommand.equals(new EditByIndexCommand(INDEX_FIRST_PERSON, DESC_BOB)));
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ExportCommandTest.java b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java
new file mode 100644
index 00000000000..069dd822e03
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ExportCommandTest.java
@@ -0,0 +1,52 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+
+public class ExportCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ void execute_exportCommandFailure() {
+ List indexList = new ArrayList<>();
+ Index i = Index.fromOneBased(Integer.MAX_VALUE);
+ indexList.add(i);
+ assertCommandFailure(new ExportCommand(indexList), model, Messages.MESSAGE_INVALID_INDEX);
+ }
+
+ @Test
+ void execute_exportCommandSuccess() {
+ List personList = model.getFilteredPersonList();
+ model.showPersonContact(personList);
+ List indexList = new ArrayList<>();
+
+ for (int i = 1; i <= personList.size(); i++) {
+ indexList.add(Index.fromOneBased(i));
+ }
+
+ List nameList = indexList.stream()
+ .map(x -> personList.get(x.getZeroBased()).getName())
+ .collect(Collectors.toList());
+
+ StringBuilder sb = new StringBuilder();
+ nameList.stream().forEach(x -> sb.append(
+ String.format(Messages.MESSAGE_EXPORT_PERSON_CONTACT_DETAILS, x) + "\n"));
+ assertCommandSuccess(new ExportCommand(indexList), model, sb.toString(), model);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FilterCommandTest.java b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
new file mode 100644
index 00000000000..31294195198
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FilterCommandTest.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.commons.core.Messages.MESSAGE_CONTACTS_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.ContactContainsTagPredicate;
+import seedu.address.model.tag.Tag;
+
+
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code FilterCommand}.
+ */
+public class FilterCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ private Set emptyTagSetStub = new HashSet<>();
+
+
+ private ContactContainsTagPredicate emptyTagsPredicate =
+ new ContactContainsTagPredicate(emptyTagSetStub);
+
+ private ContactContainsTagPredicate multipleTagsPredicate =
+ new ContactContainsTagPredicate(prepareMultilpleTags());
+
+ /**
+ * Test for no contact found if tag keyword given is empty
+ */
+ @Test
+ void executeFilter_noContactFound_emptyTags() {
+ String expectedMessage = String.format(MESSAGE_CONTACTS_LISTED_OVERVIEW, 0);
+ FilterCommand command = new FilterCommand(emptyTagsPredicate);
+ expectedModel.updateFilteredPersonList(this.emptyTagsPredicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.emptyList(), model.getFilteredPersonList());
+ }
+
+ /**
+ * Test if there are multiple contacts found when given multiple tags
+ */
+ @Test
+ void executeFilter_multipleContactsFound_multipleTags() {
+ String expectedMessage = String.format(MESSAGE_CONTACTS_LISTED_OVERVIEW, 3);
+ FilterCommand command = new FilterCommand(multipleTagsPredicate);
+ expectedModel.updateFilteredPersonList(this.multipleTagsPredicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ }
+
+ /**
+ * Sets up a stub tag hashSet
+ * @return a set of tags
+ */
+ private Set prepareMultilpleTags() {
+ Set multipleTagsSet = new HashSet<>();
+ Tag fakeTag1 = new Tag("family");
+ Tag fakeTag2 = new Tag("friends");
+ multipleTagsSet.add(fakeTag1);
+ multipleTagsSet.add(fakeTag2);
+ return multipleTagsSet;
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
deleted file mode 100644
index 9b15db28bbb..00000000000
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package seedu.address.logic.commands;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.testutil.TypicalPersons.CARL;
-import static seedu.address.testutil.TypicalPersons.ELLE;
-import static seedu.address.testutil.TypicalPersons.FIONA;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import org.junit.jupiter.api.Test;
-
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Contains integration tests (interaction with the Model) for {@code FindCommand}.
- */
-public class FindCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
-
- @Test
- public void equals() {
- NameContainsKeywordsPredicate firstPredicate =
- new NameContainsKeywordsPredicate(Collections.singletonList("first"));
- NameContainsKeywordsPredicate secondPredicate =
- new NameContainsKeywordsPredicate(Collections.singletonList("second"));
-
- FindCommand findFirstCommand = new FindCommand(firstPredicate);
- FindCommand findSecondCommand = new FindCommand(secondPredicate);
-
- // same object -> returns true
- assertTrue(findFirstCommand.equals(findFirstCommand));
-
- // same values -> returns true
- FindCommand findFirstCommandCopy = new FindCommand(firstPredicate);
- assertTrue(findFirstCommand.equals(findFirstCommandCopy));
-
- // different types -> returns false
- assertFalse(findFirstCommand.equals(1));
-
- // null -> returns false
- assertFalse(findFirstCommand.equals(null));
-
- // different person -> returns false
- assertFalse(findFirstCommand.equals(findSecondCommand));
- }
-
- @Test
- public void execute_zeroKeywords_noPersonFound() {
- String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
- NameContainsKeywordsPredicate predicate = preparePredicate(" ");
- FindCommand command = new FindCommand(predicate);
- expectedModel.updateFilteredPersonList(predicate);
- assertCommandSuccess(command, model, expectedMessage, expectedModel);
- assertEquals(Collections.emptyList(), model.getFilteredPersonList());
- }
-
- @Test
- public void execute_multipleKeywords_multiplePersonsFound() {
- String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3);
- NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
- FindCommand command = new FindCommand(predicate);
- expectedModel.updateFilteredPersonList(predicate);
- assertCommandSuccess(command, model, expectedMessage, expectedModel);
- assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
- }
-
- /**
- * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
- */
- private NameContainsKeywordsPredicate preparePredicate(String userInput) {
- return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
- }
-}
diff --git a/src/test/java/seedu/address/logic/commands/LightCommandTest.java b/src/test/java/seedu/address/logic/commands/LightCommandTest.java
new file mode 100644
index 00000000000..7f2c5c30698
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/LightCommandTest.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.LightCommand.MESSAGE_ERROR;
+import static seedu.address.logic.commands.LightCommand.MESSAGE_SUCCESS;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+
+public class LightCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ //Default Start up Dark Theme
+ //Expected Behaviour to change from Dark Theme -> Light Theme
+ @Test
+ public void execute_defaultlight_success() {
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_SUCCESS, false);
+ assertCommandSuccess(new LightCommand(), model, expectedCommandResult, expectedModel);
+ }
+
+ //From Dark Theme
+ //Expected Behaviour to change from Dark Theme -> Light Theme
+ @Test
+ public void execute_light_success() {
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_SUCCESS, false);
+ model.setCssFilePath("view/DarkTheme.css");
+ expectedModel.setCssFilePath("view/DarkTheme.css");
+ assertCommandSuccess(new LightCommand(), model, expectedCommandResult, expectedModel);
+ }
+
+ //Already in Light Theme
+ //Expected Behavior : Throw error
+ @Test
+ void execute_light_failure() {
+ model.setCssFilePath("view/LightTheme.css");
+ assertCommandFailure(new LightCommand(), model, MESSAGE_ERROR);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..ef7a19d8153 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -10,6 +10,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
import seedu.address.model.UserPrefs;
/**
@@ -22,8 +23,8 @@ public class ListCommandTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/LoadCommandTest.java b/src/test/java/seedu/address/logic/commands/LoadCommandTest.java
new file mode 100644
index 00000000000..fe87e8774b4
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/LoadCommandTest.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+
+class LoadCommandTest {
+ private Model model;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ }
+ @Test
+ void execute_pathNotSpecified() {
+ LoadCommand c = new LoadCommand(null);
+ assertThrows(CommandException.class, () -> c.execute(model));
+ }
+ @Test
+ void execute_pathInvalid() {
+ LoadCommand c = new LoadCommand("INVALID PATH");
+ assertThrows(CommandException.class, () -> c.execute(model));
+ }
+ @Test
+ void execute_notModcheckReadableFile() {
+ LoadCommand c = new LoadCommand("data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json");
+ assertThrows(CommandException.class, () -> c.execute(model));
+ }
+ @Test
+ void execute_readableFile_testSuccess() {
+ //Success scenario designed to require Ui input
+ //Testing done manually
+ assertTrue(true);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
new file mode 100644
index 00000000000..fc0e03e7556
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.fail;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.testutil.TypicalPersons;
+
+class RedoCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ @Test
+ void execute_noRedoableCommand_failure() {
+ assertCommandFailure(new RedoCommand(), model, RedoCommand.MESSAGE_NO_REDOABLE_COMMAND);
+ }
+ @Test
+ void execute_undoableCommandPresent_success() {
+ model.addPerson(TypicalPersons.IDA);
+ try {
+ new UndoCommand().execute(model);
+ } catch (CommandException e) {
+ fail("Unable to execute undo command!");
+ }
+
+ assertCommandSuccess(new RedoCommand(), model,
+ String.format("Successfully redone command:\nAdd %1$s", TypicalPersons.IDA), model);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
new file mode 100644
index 00000000000..41f270242e7
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.testutil.TypicalPersons;
+
+class UndoCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ @Test
+ void execute_noUndoableCommand_failure() {
+ assertCommandFailure(new UndoCommand(), model, UndoCommand.MESSAGE_NO_UNDOABLE_COMMAND);
+ }
+ @Test
+ void execute_undoableCommandPresent_success() {
+ model.addPerson(TypicalPersons.IDA);
+
+ assertCommandSuccess(new UndoCommand(), model,
+ String.format("Successfully undone command:\nAdd %1$s", TypicalPersons.IDA), model);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
new file mode 100644
index 00000000000..ecf1dca0b35
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java
@@ -0,0 +1,52 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserData;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+
+public class ViewCommandTest {
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ void execute_viewCommandFailure() {
+ List indexList = new ArrayList<>();
+ Index i = Index.fromOneBased(Integer.MAX_VALUE);
+ indexList.add(i);
+ assertCommandFailure(new ViewCommand(indexList), model, Messages.MESSAGE_INVALID_INDEX);
+ }
+
+ @Test
+ void execute_viewCommandSuccess() {
+ List personList = model.getFilteredPersonList();
+ model.showPersonContact(personList);
+ List indexList = new ArrayList<>();
+
+ for (int i = 1; i <= personList.size(); i++) {
+ indexList.add(Index.fromOneBased(i));
+ }
+
+ List nameList = indexList.stream()
+ .map(x -> personList.get(x.getZeroBased()).getName())
+ .collect(Collectors.toList());
+
+ StringBuilder sb = new StringBuilder();
+ nameList.stream().forEach(x -> sb.append(
+ String.format(Messages.MESSAGE_VIEW_PERSON_CONTACT_DETAILS, x) + "\n"));
+ assertCommandSuccess(new ViewCommand(indexList), model, sb.toString(), model);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5cf487d7ebb..be47ee35c5b 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -32,7 +32,6 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
-import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
@@ -90,18 +89,6 @@ public void parse_compulsoryFieldMissing_failure() {
assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
expectedMessage);
- // missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
-
// all prefixes missing
assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
expectedMessage);
@@ -121,10 +108,6 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
+ TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
- // invalid address
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
-
// invalid tag
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index d9659205b57..ea53122c293 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -6,24 +6,31 @@
import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.DeleteByNameCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteMultipleIndexCommand;
+import seedu.address.logic.commands.DeleteSingleIndexCommand;
+import seedu.address.logic.commands.EditByIndexCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.logic.parser.exceptions.UiInputRequiredException;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -47,10 +54,27 @@ public void parseCommand_clear() throws Exception {
}
@Test
- public void parseCommand_delete() throws Exception {
- DeleteCommand command = (DeleteCommand) parser.parseCommand(
+ public void parseCommand_deleteSingleIndexCommand() throws Exception {
+ DeleteSingleIndexCommand command = (DeleteSingleIndexCommand) parser.parseCommand(
DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
- assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command);
+ assertEquals(new DeleteSingleIndexCommand(INDEX_FIRST_PERSON), command);
+ }
+
+ @Test
+ public void parseCommand_deleteMultipleIndexCommand() throws Exception {
+ DeleteMultipleIndexCommand command = (DeleteMultipleIndexCommand) parser.parseCommand(
+ DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + ","
+ + INDEX_SECOND_PERSON.getOneBased() + "," + INDEX_THIRD_PERSON.getOneBased());
+
+ assertEquals(new DeleteMultipleIndexCommand(List.of(INDEX_FIRST_PERSON,
+ INDEX_SECOND_PERSON, INDEX_THIRD_PERSON)), command);
+ }
+ @Test
+ public void parseCommand_deleteByNameCommand() throws Exception {
+ DeleteByNameCommand command = (DeleteByNameCommand) parser.parseCommand(
+ DeleteCommand.COMMAND_WORD + " " + "John Doe");
+
+ assertEquals(new DeleteByNameCommand(new NameContainsAllKeywordsPredicate(List.of("John", "Doe"))), command);
}
@Test
@@ -59,7 +83,7 @@ public void parseCommand_edit() throws Exception {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build();
EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " "
+ INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor));
- assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
+ assertEquals(new EditByIndexCommand(INDEX_FIRST_PERSON, descriptor), command);
}
@Test
@@ -68,14 +92,6 @@ public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand);
}
- @Test
- public void parseCommand_find() throws Exception {
- List keywords = Arrays.asList("foo", "bar", "baz");
- FindCommand command = (FindCommand) parser.parseCommand(
- FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
- assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command);
- }
-
@Test
public void parseCommand_help() throws Exception {
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand);
@@ -88,6 +104,25 @@ public void parseCommand_list() throws Exception {
assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
}
+ @Test
+ public void parseCommand_undo() throws Exception {
+ assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD) instanceof UndoCommand);
+ assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD + " 3") instanceof UndoCommand);
+ }
+
+ @Test
+ public void parseCommand_redo() throws Exception {
+ assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD) instanceof RedoCommand);
+ assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD + " 3") instanceof RedoCommand);
+ }
+
+ @Test
+ public void parseCommand_load() throws Exception {
+ assertTrue(parser.parseCommand(LoadCommand.COMMAND_WORD + " PATH") instanceof LoadCommand);
+ assertTrue(parser.parseCommand(LoadCommand.COMMAND_WORD + " !") instanceof LoadCommand);
+ assertThrows(UiInputRequiredException.class, () -> parser.parseCommand(LoadCommand.COMMAND_WORD));
+ }
+
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
index 27eaec84450..2cb2e38fb11 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
@@ -4,10 +4,18 @@
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON;
+
+import java.util.List;
import org.junit.jupiter.api.Test;
+import seedu.address.logic.commands.DeleteByNameCommand;
import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteMultipleIndexCommand;
+import seedu.address.logic.commands.DeleteSingleIndexCommand;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
/**
* As we are only doing white-box testing, our test cases do not cover path variations
@@ -21,12 +29,27 @@ public class DeleteCommandParserTest {
private DeleteCommandParser parser = new DeleteCommandParser();
@Test
- public void parse_validArgs_returnsDeleteCommand() {
- assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON));
+ public void parse_validArgs_returnsDeleteSingleIndexCommand() {
+ assertParseSuccess(parser, "1", new DeleteSingleIndexCommand(INDEX_FIRST_PERSON));
}
-
@Test
- public void parse_invalidArgs_throwsParseException() {
- assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ public void parse_validArgs_returnsDeleteMultipleIndexCommand() {
+ assertParseSuccess(parser, "1,2,3", new DeleteMultipleIndexCommand(
+ List.of(INDEX_FIRST_PERSON, INDEX_SECOND_PERSON, INDEX_THIRD_PERSON)));
}
+ @Test
+ public void parse_validArgs_returnsDeleteByNameCommand() {
+ assertParseSuccess(parser, "John Doe",
+ new DeleteByNameCommand(new NameContainsAllKeywordsPredicate(List.of("John", "Doe"))));
+ }
+ @Test
+ public void parse_negativeIndex_returnsDeleteByNameCommand() {
+ assertParseSuccess(parser, "-1",
+ new DeleteByNameCommand(new NameContainsAllKeywordsPredicate(List.of("-1"))));
+ }
+ @Test
+ public void parse_invalidArgs_returnsMessageUsage() {
+ assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index 2ff31522486..49f25d2ec1f 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -5,12 +5,12 @@
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
@@ -20,6 +20,7 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
@@ -31,14 +32,19 @@
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON;
+import java.util.Arrays;
+
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditByIndexCommand;
+import seedu.address.logic.commands.EditByNameCommand;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
+import seedu.address.model.person.NameContainsAllKeywordsPredicate;
import seedu.address.model.person.Phone;
import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
@@ -55,7 +61,7 @@ public class EditCommandParserTest {
@Test
public void parse_missingParts_failure() {
// no index specified
- assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, VALID_NAME_AMY, EditCommand.MESSAGE_NOT_EDITED);
// no field specified
assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED);
@@ -64,19 +70,26 @@ public void parse_missingParts_failure() {
assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
}
+
@Test
public void parse_invalidPreamble_failure() {
- // negative index
- assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT);
-
- // zero index
- assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT);
-
// invalid arguments being parsed as preamble
- assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, "1 some random string", EditCommand.MESSAGE_NOT_EDITED);
// invalid prefix being parsed as preamble
- assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, "1 i/ string", EditCommand.MESSAGE_NOT_EDITED);
+
+ assertParseFailure(parser, "1 i/ string n/testname", Messages.MESSAGE_INVALID_TAG);
+ }
+
+
+ @Test
+ public void parse_namePreamble_success() {
+ assertParseSuccess(parser, VALID_NAME_AMY + NAME_DESC_BOB,
+ new EditByNameCommand(
+ new NameContainsAllKeywordsPredicate(Arrays.asList(VALID_NAME_AMY.split("\\s"))),
+ new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()));
+
}
@Test
@@ -84,7 +97,6 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email
- assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address
assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
// invalid phone followed by valid email
@@ -114,7 +126,7 @@ public void parse_allFieldsSpecified_success() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -126,7 +138,7 @@ public void parse_someFieldsSpecified_success() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB)
.withEmail(VALID_EMAIL_AMY).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -137,31 +149,31 @@ public void parse_oneFieldSpecified_success() {
Index targetIndex = INDEX_THIRD_PERSON;
String userInput = targetIndex.getOneBased() + NAME_DESC_AMY;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// phone
userInput = targetIndex.getOneBased() + PHONE_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// email
userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// address
userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// tags
userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND;
descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -175,7 +187,7 @@ public void parse_multipleRepeatedFields_acceptsLast() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB)
.withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
.build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -186,7 +198,7 @@ public void parse_invalidValueFollowedByValidValue_success() {
Index targetIndex = INDEX_FIRST_PERSON;
String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// other valid values specified
@@ -194,7 +206,7 @@ public void parse_invalidValueFollowedByValidValue_success() {
+ PHONE_DESC_BOB;
descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
.withAddress(VALID_ADDRESS_BOB).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -204,7 +216,7 @@ public void parse_resetTags_success() {
String userInput = targetIndex.getOneBased() + TAG_EMPTY;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditCommand expectedCommand = new EditByIndexCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
new file mode 100644
index 00000000000..218a879791a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java
@@ -0,0 +1,154 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.FilterCommand;
+import seedu.address.model.person.ContactContainsDescriptionPredicate;
+import seedu.address.model.person.ContactContainsEmailPredicate;
+import seedu.address.model.person.ContactContainsPhoneNumberPredicate;
+import seedu.address.model.person.ContactContainsTagPredicate;
+import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.tag.Tag;
+
+
+public class FilterCommandParserTest {
+ private FilterCommandParser parser = new FilterCommandParser();
+
+ @Test
+ public void parse_multipleArgs_returnsError() {
+ assertParseFailure(parser, "filter n/Alex t/family",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser does not work when multiple name arguments are given
+ */
+ @Test
+ public void parse_multipleNameArgs_returnsError() {
+ assertParseFailure(parser, "filter n/Alex n/Megan",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser does not work when multiple phone arguments are given
+ */
+ @Test
+ public void parse_multiplePhoneArgs_returnsError() {
+ assertParseFailure(parser, "filter p/90069637 p/91899654",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser does not work when multiple email arguments are given
+ */
+ @Test
+ public void parse_multipleEmailArgs_returnsError() {
+ assertParseFailure(parser, "filter e/gg@gmail.com e/what@hotmail.com",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser does not work when multiple description arguments are given
+ */
+ @Test
+ public void parse_multipleDescArgs_returnsError() {
+ assertParseFailure(parser, "filter d/what is this d/no idea",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser works with a single name argument that can
+ * either have 1 name or multiple different names
+ */
+ @Test
+ public void parse_singleNameArg_returnsFilterCommand() {
+ List firstKeywordList = new ArrayList(Arrays.asList("Alex"));
+ List secondKeywordList = new ArrayList(Arrays.asList("Alex", "Bernice"));
+ FilterCommand expectedFilterCommand = new FilterCommand(new NameContainsKeywordsPredicate(firstKeywordList));
+ FilterCommand expectedSecondFilterCommand = new FilterCommand(
+ new NameContainsKeywordsPredicate(secondKeywordList));
+ assertParseSuccess(parser, "filter n/Alex", expectedFilterCommand);
+ assertParseSuccess(parser, "filter n/Alex Bernice", expectedSecondFilterCommand);
+ }
+
+ /**
+ * Test if the parser works with a single phone argument
+ */
+ @Test
+ public void parse_singlePhoneArg_returnsFilterCommand() {
+ FilterCommand expectedFilterCommand = new FilterCommand(
+ new ContactContainsPhoneNumberPredicate("90069637"));
+ assertParseSuccess(parser, "filter p/90069637", expectedFilterCommand);
+ }
+
+ /**
+ * Test if the parser fails with a single phone argument if
+ * multiple phone numbers are given
+ */
+ @Test
+ public void parse_singlePhoneArgMultipleNum_returnsError() {
+ assertParseFailure(parser, "filter p/90069637 p/99999999",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser works with a single email address
+ */
+ @Test
+ public void parse_singleEmailArg_returnsFilterCommand() {
+ FilterCommand expectedFilterCommand = new FilterCommand(
+ new ContactContainsEmailPredicate("gg@gmail.com"));
+ assertParseSuccess(parser, "filter e/gg@gmail.com", expectedFilterCommand);
+ }
+
+ /**
+ * Test if the parser fails with a single email address
+ * if multiple emails are given
+ */
+ @Test
+ public void parse_singleEmailArgMultipleEmails_returnsError() {
+ assertParseFailure(parser, "filter e/gg@gmail.com what@gmail.com",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+
+ /**
+ * Test if the parser works with a single description argument
+ */
+ @Test
+ public void parse_singleDescriptionArg_returnsFilterCommand() {
+ FilterCommand expectedFilterCommand = new FilterCommand(
+ new ContactContainsDescriptionPredicate("Testing 123 hehe"));
+ assertParseSuccess(parser, "filter d/Testing 123 hehe", expectedFilterCommand);
+ }
+
+ /**
+ * Test if the parser works with multiple tags passed as arguments
+ */
+ @Test
+ public void parse_multipleTagArgs_returnsFilterCommand() {
+ Set multipleTags = new HashSet<>();
+ multipleTags.add(new Tag("family"));
+ multipleTags.add(new Tag("friends"));
+ FilterCommand expectedFilterCommand = new FilterCommand(new ContactContainsTagPredicate(multipleTags));
+ assertParseSuccess(parser, "filter t/family t/friends", expectedFilterCommand);
+ }
+
+ /**
+ * Test if the parser fails to run when no arguments are given
+ */
+ @Test
+ public void parse_emptyArgs_returnsError() {
+ assertParseFailure(parser, "filter ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
deleted file mode 100644
index 70f4f0e79c4..00000000000
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
-import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
-
-import java.util.Arrays;
-
-import org.junit.jupiter.api.Test;
-
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-public class FindCommandParserTest {
-
- private FindCommandParser parser = new FindCommandParser();
-
- @Test
- public void parse_emptyArg_throwsParseException() {
- assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
- }
-
- @Test
- public void parse_validArgs_returnsFindCommand() {
- // no leading and trailing whitespaces
- FindCommand expectedFindCommand =
- new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")));
- assertParseSuccess(parser, "Alice Bob", expectedFindCommand);
-
- // multiple whitespaces between keywords
- assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand);
- }
-
-}
diff --git a/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java b/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java
new file mode 100644
index 00000000000..9d95dfa6d43
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/LoadCommandParserTest.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.LoadCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.exceptions.UiInputRequiredException;
+
+class LoadCommandParserTest {
+ private static final String TEST_PATH = "C:\\Users\\joellow88\\Desktop\\Modules\\CS2103\\tp\\src\\test\\data"
+ + "\\JsonAddressBookStorageTest\\ValidAddressBook1.json";
+
+ @Test
+ void parse_noInput() {
+ assertThrows(UiInputRequiredException.class, () -> new LoadCommandParser().parse(""));
+ }
+
+ @Test
+ void parse_noPath() {
+ try {
+ assertEquals(new LoadCommand(null), new LoadCommandParser().parse("!"));
+ } catch (ParseException e) {
+ fail();
+ }
+ }
+
+ @Test
+ void parse_invalidPath() {
+ try {
+ assertEquals(new LoadCommand("INVALID PATH"), new LoadCommandParser().parse("INVALID PATH"));
+ } catch (ParseException e) {
+ fail();
+ }
+ }
+ @Test
+ void parse_validPath() {
+ try {
+ assertEquals(
+ new LoadCommand(TEST_PATH),
+ new LoadCommandParser().parse(TEST_PATH));
+ } catch (ParseException e) {
+ fail();
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..9f6da0dda7f 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -107,11 +107,6 @@ public void parseAddress_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null));
}
- @Test
- public void parseAddress_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS));
- }
-
@Test
public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception {
Address expectedAddress = new Address(VALID_ADDRESS);
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..e40424a525b 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -16,7 +16,10 @@
import seedu.address.commons.core.GuiSettings;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
+import seedu.address.testutil.TypicalPersons;
public class ModelManagerTest {
@@ -92,16 +95,88 @@ public void hasPerson_personInAddressBook_returnsTrue() {
public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
}
+ @Test
+ public void combine_differentAddressBook() {
+ modelManager.combine(TypicalPersons.getTypicalAddressBook(), "DUMMY PATH");
+ assertEquals(modelManager.getAddressBook(), TypicalPersons.getTypicalAddressBook());
+ }
+ @Test
+ public void combine_sameAddressBook() {
+ modelManager.setAddressBook(TypicalPersons.getTypicalAddressBook());
+ modelManager.combine(TypicalPersons.getTypicalAddressBook(), "DUMMY PATH");
+ assertEquals(modelManager.getAddressBook(), TypicalPersons.getTypicalAddressBook());
+ }
+ @Test
+ public void combine_partiallySimilarAddressBook() {
+ AddressBook ab1 =
+ new AddressBookBuilder()
+ .withPerson(TypicalPersons.ALICE)
+ .withPerson(TypicalPersons.BOB)
+ .withPerson(TypicalPersons.CARL)
+ .build();
+
+ AddressBook ab2 =
+ new AddressBookBuilder()
+ .withPerson(TypicalPersons.CARL)
+ .withPerson(TypicalPersons.DANIEL)
+ .withPerson(TypicalPersons.ELLE)
+ .build();
+
+ AddressBook abExpected =
+ new AddressBookBuilder()
+ .withPerson(TypicalPersons.ALICE)
+ .withPerson(TypicalPersons.BOB)
+ .withPerson(TypicalPersons.CARL)
+ .withPerson(TypicalPersons.DANIEL)
+ .withPerson(TypicalPersons.ELLE)
+ .build();
+ modelManager.setAddressBook(ab1);
+ modelManager.combine(ab2, "DUMMY PATH");
+ assertEquals(modelManager.getAddressBook(), abExpected);
+ }
+ @Test
+ public void combine_sameNameButDifferentDetailsAddressBook() {
+ Person p1 =
+ new PersonBuilder()
+ .withName("Xiao Ming")
+ .withPhone("99999999")
+ .withEmail("XiaoMing@email.com")
+ .withAddress("Dummy address")
+ .withTags("friend")
+ .build();
+ Person p2 =
+ new PersonBuilder()
+ .withName("Xiao Ming")
+ .withPhone("90000000")
+ .withEmail("XiaoMing@yahoo.com")
+ .withAddress("Dummy address")
+ .withTags("enemy")
+ .build();
+ AddressBook ab1 =
+ new AddressBookBuilder()
+ .withPerson(p1)
+ .build();
+
+ AddressBook ab2 =
+ new AddressBookBuilder()
+ .withPerson(p2)
+ .build();
+
+ modelManager.setAddressBook(ab1);
+ modelManager.combine(ab2, "DUMMY PATH");
+ assertEquals(modelManager.getAddressBook(), ab1);
+ }
@Test
public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
AddressBook differentAddressBook = new AddressBook();
UserPrefs userPrefs = new UserPrefs();
+ UserData userData = new UserData();
// same values -> returns true
- modelManager = new ModelManager(addressBook, userPrefs);
- ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
+ modelManager = new ModelManager(addressBook, userPrefs, userData);
+ ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs, userData);
assertTrue(modelManager.equals(modelManagerCopy));
// same object -> returns true
@@ -114,12 +189,12 @@ public void equals() {
assertFalse(modelManager.equals(5));
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs, userData)));
// different filteredList -> returns false
String[] keywords = ALICE.getName().fullName.split("\\s+");
modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords)));
- assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs, userData)));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
@@ -127,6 +202,6 @@ public void equals() {
// different userPrefs -> returns false
UserPrefs differentUserPrefs = new UserPrefs();
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs, userData)));
}
}
diff --git a/src/test/java/seedu/address/model/UndoManagerTest.java b/src/test/java/seedu/address/model/UndoManagerTest.java
new file mode 100644
index 00000000000..6c5e50c8d29
--- /dev/null
+++ b/src/test/java/seedu/address/model/UndoManagerTest.java
@@ -0,0 +1,106 @@
+package seedu.address.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.util.Pair;
+import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.TypicalPersons;
+
+class UndoManagerTest {
+ private static final String TEST_COMMAND_ONE = "TEST COMMAND ONE";
+ private static final String TEST_COMMAND_TWO = "TEST COMMAND TWO";
+ private static final String TEST_COMMAND_THREE = "TEST COMMAND THREE";
+ private UndoManager undoManager = new UndoManager(new AddressBook(), 5);
+
+ @Test
+ void addToHistory() {
+ AddressBook ab = TypicalPersons.getTypicalAddressBook();
+ undoManager.addToHistory(ab, TEST_COMMAND_ONE);
+ assertEquals(undoManager.getCurrentState(), ab);
+ assertNotSame(undoManager.getCurrentState(), ab);
+
+ ab.addPerson(TypicalPersons.IDA);
+ undoManager.addToHistory(ab, TEST_COMMAND_TWO);
+ assertEquals(undoManager.getCurrentState(), ab);
+ assertNotSame(undoManager.getCurrentState(), ab);
+ }
+
+ @Test
+ void deleteUntrackedHead() {
+ }
+
+ @Test
+ void hasUndoableCommand_undoableCommandPresent_returnTrue() {
+ undoManager.addToHistory(TypicalPersons.getTypicalAddressBook(), TEST_COMMAND_ONE);
+ assertTrue(undoManager.hasUndoableCommand());
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ assertTrue(undoManager.hasUndoableCommand());
+ }
+ @Test
+ void hasUndoableCommand_previousHistoryGotten_returnTrue() {
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ undoManager.getPreviousHistory();
+ assertTrue(undoManager.hasUndoableCommand());
+ }
+
+ @Test
+ void hasUndoableCommand_undoableCommandNotPresent_returnFalse() {
+ assertFalse(undoManager.hasUndoableCommand());
+ undoManager.addToHistory(TypicalPersons.getTypicalAddressBook(), TEST_COMMAND_ONE);
+ undoManager.getPreviousHistory();
+ assertFalse(undoManager.hasUndoableCommand());
+ }
+ @Test
+ void hasUndoableCommand_previousHistoryGotten_returnFalse() {
+ undoManager.addToHistory(TypicalPersons.getTypicalAddressBook(), TEST_COMMAND_ONE);
+ undoManager.getPreviousHistory();
+ assertFalse(undoManager.hasUndoableCommand());
+ }
+
+ @Test
+ void hasRedoableCommand_redoableCommandNotPresent_returnFalse() {
+ assertFalse(undoManager.hasRedoableCommand());
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ assertFalse(undoManager.hasRedoableCommand());
+ }
+ @Test
+ void hasRedoableCommand_redoableCommandPresent_returnTrue() {
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ undoManager.getPreviousHistory();
+ assertTrue(undoManager.hasRedoableCommand());
+ }
+
+ @Test
+ void getPreviousHistory() {
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ Pair previousHistory = undoManager.getPreviousHistory();
+ assertEquals(previousHistory.getKey(), new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build());
+ assertEquals(previousHistory.getValue(), TEST_COMMAND_THREE);
+ }
+
+ @Test
+ void getNextHistory() {
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.CARL).build(), TEST_COMMAND_ONE);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.ALICE).build(), TEST_COMMAND_TWO);
+ undoManager.addToHistory(new AddressBookBuilder().withPerson(TypicalPersons.BOB).build(), TEST_COMMAND_THREE);
+ undoManager.getPreviousHistory();
+ Pair previousHistory = undoManager.getNextHistory();
+ assertEquals(previousHistory.getKey(), new AddressBookBuilder().withPerson(TypicalPersons.BOB).build());
+ assertEquals(previousHistory.getValue(), TEST_COMMAND_THREE);
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java
index dcd3be87b3a..5284037d6e6 100644
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ b/src/test/java/seedu/address/model/person/AddressTest.java
@@ -13,19 +13,12 @@ public void constructor_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new Address(null));
}
- @Test
- public void constructor_invalidAddress_throwsIllegalArgumentException() {
- String invalidAddress = "";
- assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress));
- }
-
@Test
public void isValidAddress() {
// null address
assertThrows(NullPointerException.class, () -> Address.isValidAddress(null));
// invalid addresses
- assertFalse(Address.isValidAddress("")); // empty string
assertFalse(Address.isValidAddress(" ")); // spaces only
// valid addresses
diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java
index bbcc6c8c98e..b2f675ed446 100644
--- a/src/test/java/seedu/address/model/person/EmailTest.java
+++ b/src/test/java/seedu/address/model/person/EmailTest.java
@@ -13,19 +13,12 @@ public void constructor_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new Email(null));
}
- @Test
- public void constructor_invalidEmail_throwsIllegalArgumentException() {
- String invalidEmail = "";
- assertThrows(IllegalArgumentException.class, () -> new Email(invalidEmail));
- }
-
@Test
public void isValidEmail() {
// null email
assertThrows(NullPointerException.class, () -> Email.isValidEmail(null));
// blank email
- assertFalse(Email.isValidEmail("")); // empty string
assertFalse(Email.isValidEmail(" ")); // spaces only
// missing parts
diff --git a/src/test/java/seedu/address/model/person/NameContainsAllKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsAllKeywordsPredicateTest.java
new file mode 100644
index 00000000000..a8b53dafdfe
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/NameContainsAllKeywordsPredicateTest.java
@@ -0,0 +1,49 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+class NameContainsAllKeywordsPredicateTest {
+
+ @Test
+ public void test_nameContainsAllKeywords_returnsTrue() {
+ // All keywords match exactly
+ NameContainsAllKeywordsPredicate predicate =
+ new NameContainsAllKeywordsPredicate(Arrays.asList("Alice", "Bob"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
+
+ // One keyword only
+ predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("Alice"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice").build()));
+
+ // All keywords match, with longer name
+ predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("Bob", "Carol"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Bob Alice Carol").build()));
+
+ // Mixed-case keywords
+ predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("aLIce", "bOB"));
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
+ }
+
+ @Test
+ public void test_nameDoesNotContainAllKeywords_returnsFalse() {
+ // Non-matching keyword
+ NameContainsAllKeywordsPredicate predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("Carol"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
+
+ // Partially matching keyword, multiple keywords
+ predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("Carol", "Lewis"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Carol").build()));
+
+ // Partially matching keyword, multi-word name
+ predicate = new NameContainsAllKeywordsPredicate(Arrays.asList("Carol", "Lewis"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice Carol").build()));
+
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java
index 8dd52766a5f..9dea2f5c75f 100644
--- a/src/test/java/seedu/address/model/person/PhoneTest.java
+++ b/src/test/java/seedu/address/model/person/PhoneTest.java
@@ -13,19 +13,12 @@ public void constructor_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new Phone(null));
}
- @Test
- public void constructor_invalidPhone_throwsIllegalArgumentException() {
- String invalidPhone = "";
- assertThrows(IllegalArgumentException.class, () -> new Phone(invalidPhone));
- }
-
@Test
public void isValidPhone() {
// null phone number
assertThrows(NullPointerException.class, () -> Phone.isValidPhone(null));
// invalid phone numbers
- assertFalse(Phone.isValidPhone("")); // empty string
assertFalse(Phone.isValidPhone(" ")); // spaces only
assertFalse(Phone.isValidPhone("91")); // less than 3 numbers
assertFalse(Phone.isValidPhone("phone")); // non-numeric
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..e8511a474b2 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -32,6 +32,9 @@ public class JsonAdaptedPersonTest {
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
+ private static final List VALID_MODULE_TAGS = BENSON.getModuleTags().stream()
+ .map(JsonAdaptedModuleTag::new)
+ .collect(Collectors.toList());
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON);
@@ -41,14 +44,16 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS,
+ VALID_MODULE_TAGS);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_TAGS, VALID_MODULE_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +61,16 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS,
+ VALID_MODULE_TAGS);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS,
+ VALID_TAGS, VALID_MODULE_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +78,16 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS,
+ VALID_MODULE_TAGS);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS,
+ VALID_TAGS, VALID_MODULE_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,14 +95,16 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS,
+ VALID_MODULE_TAGS);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null,
+ VALID_TAGS, VALID_MODULE_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -103,7 +114,8 @@ public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags,
+ VALID_MODULE_TAGS);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..2b75724aeb4 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -26,7 +26,8 @@ public class StorageManagerTest {
public void setUp() {
JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs"));
- storageManager = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(getTempFilePath("userdata"));
+ storageManager = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
}
private Path getTempFilePath(String fileName) {
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..44ba2b0798c 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -8,6 +8,7 @@
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.tag.ModuleTag;
import seedu.address.model.tag.Tag;
import seedu.address.model.util.SampleDataUtil;
@@ -26,6 +27,8 @@ public class PersonBuilder {
private Email email;
private Address address;
private Set tags;
+ private Set moduleTags;
+ private boolean hidden;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -36,6 +39,8 @@ public PersonBuilder() {
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
tags = new HashSet<>();
+ moduleTags = new HashSet<>();
+ hidden = true;
}
/**
@@ -47,6 +52,8 @@ public PersonBuilder(Person personToCopy) {
email = personToCopy.getEmail();
address = personToCopy.getAddress();
tags = new HashSet<>(personToCopy.getTags());
+ moduleTags = new HashSet<>(personToCopy.getModuleTags());
+ hidden = personToCopy.getHidden();
}
/**
@@ -65,6 +72,14 @@ public PersonBuilder withTags(String ... tags) {
return this;
}
+ /**
+ * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
+ */
+ public PersonBuilder withModuleTags(String ... tags) {
+ this.moduleTags = SampleDataUtil.getModuleTagSet(tags);
+ return this;
+ }
+
/**
* Sets the {@code Address} of the {@code Person} that we are building.
*/
@@ -90,7 +105,7 @@ public PersonBuilder withEmail(String email) {
}
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, address, tags, moduleTags);
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..bedd206e3be 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -30,7 +30,8 @@ public class TypicalPersons {
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
+ .withTags("owesMoney", "friends")
+ .withModuleTags("CS2103").build();
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
.withEmail("heinz@example.com").withAddress("wall street").build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")