diff --git a/.github/check-line-endings.sh b/.github/check-line-endings.sh index 3de67ea87f6..57ab7d3bbc9 100755 --- a/.github/check-line-endings.sh +++ b/.github/check-line-endings.sh @@ -1,8 +1,9 @@ #!/bin/sh # Checks for prohibited line endings. # Prohibited line endings: \r\n +# Exclude .vcf files as the RFC states that they must use \r\n -git grep --cached -I -n --no-color -P '\r$' -- ':/' | +git grep --cached -I -n --no-color -P '\r$' -- ':/' ':/!*.vcf' | awk ' BEGIN { FS = ":" diff --git a/.gitignore b/.gitignore index 284c4ca7cd9..59f3b5f7ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,28 @@ # Gradle build files /.gradle/ /build/ -src/main/resources/docs/ +/src/main/resources/docs/ + +# Vscode files +/.vscode/ # IDEA files /.idea/ /out/ /*.iml +/bin # Storage/log files +/exports/ /data/ /config.json /preferences.json /*.log.* -hs_err_pid[0-9]*.log +/hs_err_pid[0-9]*.log # Test sandbox files -src/test/data/sandbox/ +/src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store -docs/_site/ +/docs/_site/ diff --git a/README.md b/README.md index 16208adb9b6..9780b8f410a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2425S1-CS2103-F10-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103-F10-3/tp/actions) [![codecov](https://codecov.io/gh/AY2425S1-CS2103-F10-3/tp/graph/badge.svg?token=XDT6OIN0I7)](https://codecov.io/gh/AY2425S1-CS2103-F10-3/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
+* This is an **address book application catered towards B2B Sales Representatives in the Food and Beverage Industry**.
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. + * as a tool to more easily retrieve information from clients to improve overall workflow efficiency +* The project builds on a 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/#contributing-to-se-edu) for more info. +* It is named `BizBook` as it aims to improve the workflow of business to business dealings by prioritizing efficiency and offering features specific to this target demographic. +* For the detailed documentation of this project, see the **[BizBook Website](https://ay2425s1-cs2103-f10-3.github.io/tp/)**. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). +* This projects icon was created using [canva](https://www.canva.com). + diff --git a/build.gradle b/build.gradle index 0db3743584e..60a17a834cf 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'jacoco' } -mainClassName = 'seedu.address.Main' +mainClassName = 'bizbook.Main' sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -41,7 +41,7 @@ task coverage(type: JacocoReport) { } dependencies { - String jUnitVersion = '5.4.0' + String jUnitVersion = '5.10.0' String javaFxVersion = '17.0.7' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' @@ -57,16 +57,23 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' - implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' + implementation 'com.googlecode.ez-vcard:ez-vcard:0.12.1' + + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.0' + implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.18.0' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion + testImplementation 'org.mockito:mockito-core:4.8.0' // Mockito core testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'bizbook.jar' } defaultTasks 'clean', 'test' + +run { + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index eb761a9b9a7..d35fe4c211b 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -418,6 +418,9 @@ + + + diff --git a/docs/AboutUs.md b/docs/AboutUs.md index ff3f04abd02..5530195eee8 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,53 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Nicholas Cheng De Fei - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/Nicholas-Cheng-De-Fei)] +[[linkedin](https://www.linkedin.com/in/nicholas-cheng-)] -* Role: Project Advisor +- Role: Team Lead +- Responsibilities: Project Coordination, VScode Expert -### Jane Doe +### Joel Tio - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[website](https://joelt.io)] +[[github](https://github.com/joeltio)] +[[linkedin](https://www.linkedin.com/in/joel-tio)] -* Role: Team Lead -* Responsibilities: UI +- Role: Developer +- Responsibilities: Code Quality, Git Expert -### Johnny Doe +### Lim Jia Wei - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/ITLimJiaWei)] +[[linkedin](https://www.linkedin.com/in/jiawei88)] -* Role: Developer -* Responsibilities: Data +- Role: Developer +- Responsibilities: Testing, IntelliJ Expert -### Jean Doe +### Kenneth Teo - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/KennethTeo2002)] +[[linkedin](https://www.linkedin.com/in/kenneth-teo-boon-jun)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +- Role: Developer +- Responsibilities: Integration, Deliverables and deadlines -### James Doe +### Sheen - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/sheenkerr)] +[[linkedin](https://www.linkedin.com/in/sheenkerr)] -* Role: Developer -* Responsibilities: UI +- Role: Developer +- Responsibilities: Documentation diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..f28652df046 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -73,7 +73,7 @@ Any warnings or errors will be printed out to the console. Here are the steps to create a new release. -1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). +1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/bizbook/MainApp.java). 1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`). 1. Tag the repo with the version number. e.g. `v0.1` 1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created. diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 743c65a49d2..b7ed0be0fcb 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,63 +2,65 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- [ez-vcard](https://github.com/mangstadt/ez-vcard) by [mangstadt](https://github.com/mangstadt/) +- [mockito](https://github.com/mockito/mockito) --------------------------------------------------------------------------------------------------------------------- +--- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design**
:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +
### Architecture -The ***Architecture Diagram*** given above explains the high-level design of the App. +The **_Architecture Diagram_** given above explains the high-level design of the App. Given below is a quick overview of main components and how they interact with each other. **Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. -* At app launch, it initializes the other components in the correct sequence, and connects them up with each other. -* At shut down, it shuts down the other components and invokes cleanup methods where necessary. +**`Main`** (consisting of classes [`Main`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/Main.java) and [`MainApp`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/MainApp.java)) is in charge of the app launch and shut down. + +- At app launch, it initializes the other components in the correct sequence, and connects them up with each other. +- At shut down, it shuts down the other components and invokes cleanup methods where necessary. The bulk of the app's work is done by the following four components: -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +- [**`UI`**](#ui-component): The UI of the App. +- [**`Logic`**](#logic-component): The command executor. +- [**`Model`**](#model-component): Holds the data of the App in memory. +- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. Each of the four main components (also shown in the diagram above), -* defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +- defines its _API_ in an `interface` with the same name as the Component. +- implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -68,24 +70,24 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, -* executes user commands using the `Logic` component. -* listens for changes to `Model` data so that the UI can be updated with the modified data. -* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +- executes user commands using the `Logic` component. +- listens for changes to `Model` data so that the UI can be updated with the modified data. +- keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. +- depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -95,89 +97,99 @@ The sequence diagram below illustrates the interactions within the `Logic` compo ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram. +
+ +:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram. +
How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
+2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +- When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. +- All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - +**API** : [`Model.java`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/model/Model.java) + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. -* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) +- 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. +- 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.
+: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.
- ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2425S1-CS2103-F10-3/tp/blob/master/src/main/java/bizbook/storage/Storage.java) The `Storage` component, -* can save both address book data and user preference data in JSON format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, 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`) + +- 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). +- 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 -Classes used by multiple components are in the `seedu.address.commons` package. +Classes used by multiple components are in the `bizbook.commons` package. --------------------------------------------------------------------------------------------------------------------- +--- ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Undo/redo feature -#### 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 undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally in an `addressBookOlderVersionList` for the undo history and `addressBookNewerVersionList` for the redo history. Additionally, it implements the following operations: -* `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. +- `VersionedAddressBook#canRedo()` — Checks if there is a version to redo to. +- `VersionedAddressBook#canUndo()` — Checks if there is a version to undo to. +- `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. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#canRedo()`, `Model#canUndo()`, `Model#saveAddressBookVersion()`, `Model#revertAddressBookVersion()` and `Model#redoAddressBookVersion()` respectively. + +The `addressBookOlderVersionList` will only hold up to 5 seperate unique versions of the address book, therefore BizBook will only remember up till 5 previously executed commands that in some way have modified the address book. 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 `addressBookOlderVersionList` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. While the `addressBookNewerVersionList` will be empty. ![UndoRedoState0](images/UndoRedoState0.png) -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 `Model#saveAddressBookVersion()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookOlderVersionList`, and the `currentStatePointer` is shifted to the newly inserted address book state. ![UndoRedoState1](images/UndoRedoState1.png) -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 `Model#saveAddressBookVersion()`, causing another modified address book state to be saved into the `addressBookOlderVersionList`. ![UndoRedoState2](images/UndoRedoState2.png) @@ -185,7 +197,7 @@ Step 3. The user executes `add n/David …​` to add a new person. The `add` co
-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 `Model#revertAddressBookVersion()`, which remove the item at the `currentStatePointer` and add it into the `addressBookNewerVersionList`. In the process the `currentStatePointer` moves left, points to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -206,17 +218,17 @@ Similarly, how an undo operation goes through the `Model` component is shown bel ![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite — it calls `Model#redoAddressBookVersion()`, which removes the item at the `redoPointer` and adds it to the back of the `addressBookOlderVersionList`, it also shifts the `currentStatePointer` to the right as well as restores the address book to that state. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +
:information_source: **Note:** If the `redoPointer` is at index `addressBookNewerVersionList.size() - 1` or the list is empty, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedo()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. 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#saveAddressBookVersion()`, `Model#revertAddressBookVersion()` or `Model#redoAddressBookVersion()`. Thus, the `addressBookOlderVersionList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#saveAddressBookVersion()`. Since the `redoPointer` is pointing at an item in the `addressBookNewerVersionList`, all address book states in `addressBookNewerVersionList` 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. ![UndoRedoState5](images/UndoRedoState5.png) @@ -224,111 +236,437 @@ The following activity diagram summarizes what happens when a user executes a ne -#### Design considerations: -**Aspect: How undo & redo executes:** +## **Documentation, logging, testing, configuration, dev-ops** -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) -* **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. +--- -_{more aspects and alternatives to be added}_ +## **Appendix: Requirements** -### \[Proposed\] Data archiving +### Product scope -_{Explain here how the data archiving feature will be implemented}_ +**Target user profile**: +A sales and customer relations representative working in the F&B industry. In +particular, this representative works with B2B sales. --------------------------------------------------------------------------------------------------------------------- +- has a need to manage a significant number of business contacts +- prefer desktop apps over other types +- can type fast +- prefers typing to mouse interactions +- is reasonably comfortable using CLI apps -## **Documentation, logging, testing, configuration, dev-ops** +**Value proposition**: This product aims to streamline and simplify sales management for Food and Beverage outlets. By providing an organized, easy-to-use platform for managing business contacts, it helps sales representatives save time and improve efficiency. -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +### User stories --------------------------------------------------------------------------------------------------------------------- +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -## **Appendix: Requirements** +| Priority | As a … | I want to … | So that I can … | +| -------- |------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------| +| `* * *` | user | add a new contact | save the contact information of people | +| `* * *` | user | delete a contact | free up space in my app | +| `* * *` | user | view all contact | see the full list of contacts | +| `* * *` | user | view a contact | retrieve contact information of a person | +| `* * *` | user | save all contact | retain all information for when i reopen the app | +| `* * *` | sales rep | have a low query time | avoid wasting much time querying my desired contact | +| `* *` | user | find a person by name | locate details of persons without having to go through the entire list | +| `* *` | user | search through my contacts | find a specific person | +| `* *` | new user | see usage instructions | know how to use the app | +| `* *` | user | edit contact | update contact with new information | +| `* *` | user | delete a tag from a person | remove tags that are invalid or are not applicable to the person | +| `* *` | user | sort contact by name | see whose contact I have saved | +| `* *` | user | pin a specific contact | view them on a separate list | +| `* *` | user | unpin a specific contact | clear the pin that is no longer needed | +| `* *` | user | archive contact | hide less frequently used contacts without deleting them | +| `* *` | user | be alerted when a contact already exist | avoid accidentally creating a duplicate | +| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | +| `* *` | user | undo a command | fix a mistake I made | +| `* *` | user | redo a command | revert back the changes I undid | +| `* *` | new user | import all contact details into the app | start using without manual setup | +| `* *` | sales rep | keep track of clients I have contacted by seeing when I last contacted them | avoid wasting time calling them again about the same product | +| `* *` | sales rep | view my most popular/active clients | promote the new product | +| `* *` | sales rep | remember the client's preferred products | recommend related products | +| `* *` | sales rep | add notes to client's contact | keep track of my conversation with them | +| `* *` | sales rep | edit notes saved to client's contact | keep track of my conversation with them | +| `* *` | sales rep | delete notes from a client's contact | remove incorrect or outdated notes | +| `* *` | sales rep | group my clients by industry | tell if sales are doing well in that industry among other metrics | +| `* *` | sales rep | add tags to clients | categorize them | +| `* *` | sales rep | keep note of my client's email addresses | potentially send promotions or survey forms | +| `* *` | sales rep | export a list of contact emails | add them to a mailing list | +| `* *` | sales rep | export my contacts | send it to my coworker who needs it for his work | +| `* *` | sales rep | add a tag to multiple clients | tag the clients more easily | +| `*` | user | sort contacts by name | locate a person easily | +| `*` | experienced user | use keyboard shortcuts | navigate the app faster | +| `*` | sales rep | contact my client quickly from the app | avoid typing numbers repeatedly on my _device_ | +| `*` | user | use my previous command quickly | avoid retyping a command | +| `*` | user | toggle my application between light and dark mode | see the application in my preferred theme | -### Product scope +### Use cases -**Target user profile**: +(For all use cases below, the **System** is the `Bizbook` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: UC1 - Add a person** -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +**MSS** -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +1. Actor requests to add a new person. +2. System shows details of the newly added person. + Use case ends. -### User stories +**Extensions** -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +- 1a. The details entered about the new person are invalid. -| 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 | + - 1a1. System shows an error message. -*{More to be added}* + Use case ends. -### Use cases +- 1b. A person with the same identifier is already in contact list. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) + - 1b1. System shows duplicate person message. -**Use case: Delete a person** + Use case ends. + +**Use case: UC2 - List all people** **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. Actor requests to list all people saved in the System. +2. System shows a list of people. Use case ends. **Extensions** -* 2a. The list is empty. +- 1a. No contacts stored in the System. Use case ends. -* 3a. The given index is invalid. +**Use case: UC3 - Delete a person** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to delete a specific person in the list. +3. System shows details of deleted person. + + Use case ends. + +**Extensions** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +**Use case: UC4 - Delete a person** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to delete a specific tag from a person. +3. System remove the tag from the person. + + Use case ends. + +**Extensions** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +- 2b. The specified tag does not exist. + + - 2b1. System shows an error message. + + Use case ends. + +**Use case: UC5 - View person contact** + +**MSS** + +1. Actor requests to see specific person's detail. +2. System shows person's contact details. + + Use case ends. + +**Extensions** + +- 1a. The specified person is invalid. + + - 1a1. System shows an error message. + + Use case ends. + +**Use case: UC6 - Find people** + +**MSS** + +1. Actor requests to find specific people. +2. System shows a filtered list of people. + + Use case ends. + +**Extensions** + +- 1a. No contacts match keywords. + + Use case ends. + +**Use case: UC7 - Add note to a person contact** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to add a note to a specific person. +3. System shows details of the newly added note to that person. + + Use case ends. + +**Extensions** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +- 2b. The note content is invalid. + + - 2b1. System shows an error message. + + Use case ends. + +**Use case: UC8 - Edit a note of a person contact** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to edit a note to a specific person. +3. System shows details of the newly edited note of that person. + + Use case ends. + +**Extensions** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +- 2b. The note index is invalid. + + - 2b1. System shows an error message. + + Use case ends. + +- 2c. The note content is invalid. + + - 2c1. System shows an error message. + + Use case ends. + +**Use case: UC9 - Delete note from a person contact** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to delete a specific note from a specific person. +3. System shows updated details excluding deleted note from that person. + + Use case ends. + +**Extensions** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +- 2b. The note index is invalid. + + - 2b1. System shows an error message. + + Use case ends. + +**Use case: UC10 - Pin a person** + +**MSS** + +1. Actor performs list all people (UC2). +2. Actor requests to pin a specific person. +3. System shows details of newly pinned person. + +**Extension** + +- 2a. The specified person is invalid. + + - 2a1. System shows an error message. + + Use case ends. + +- 2b. The person is already pinned. + + - 2b1. System shows duplicated pin message. + + Use case ends. + +**Use case: UC11 - Unpin a person** - * 3a1. AddressBook shows an error message. +_Similar to UC10 except without extension 2b._ - Use case resumes at step 2. +**Use case: UC12 - Undo a command** -*{More to be added}* +**MSS** + +1. Actor performs a command that updates the System. +2. System executes the command. +3. Actor requests to undo the recently executed command. +4. System reverts changes made by the actor. + + Use case ends. + +**Extensions** + +- 3a. There is no version to revert to. + + - 3a1. System shows an error message. + + Use case ends. + +**Use case: UC13 - Redo a command** + +**MSS** + +1. Actor performs a command that updates the System. +2. System executes the command. +3. Actor requests to undo the recently executed command. +4. System reverts changes made by the actor. +5. Actor requests to redo the recently executed undo command. +6. System reverts changes made by the actor. + + Use case ends. + +**Extensions** + +- 5a. There is no version to revert to. + + - 5a1. System shows an error message. + + Use case ends. + +**Use case: UC14 - Export contact list** + +**MSS** + +1. Actor requests to export contact list to a specific file. +2. System exports the contact information into the file. + + Use case ends. + +**Extensions** + +- 1a. System detects that the directory does not exist. + + - 1a1. System creates the directory. + + Use case resumes from step 2. + +- 1b. System detects that the file is used by another process. + + - 1b1. System shows an error message. + + Use case ends. + +**Use case: UC15 - Import a contact list** + +**MSS** + +1. Actor requests to import a contact list from a specific file. +2. System imports the contact information into the file. + + Use case ends. + +**Extensions** + +- 1a. System detects that the file does not exist. + + - 1a1. System shows an error message + + Use case ends. + +- 1b. System detects that the file is not supported by the program + + - 1b1. System shows an error message + + Use case ends. + +**Use case: UC16 - Toggle application's theme** + +**MSS** + +1. Actor toggle the system's theme. +2. System changes theme. + + Use case ends. + +**Use case: UC17 - Command History** + +**MSS** + +1. Actor inputs a command into the System. +2. System processes the command and confirms its success. +3. Actor presses the "Up" arrow key to retrieve and re-populate the previous command in the input field. + + Use case ends. + +**Extensions** + +- 2a. Command fails. + + - 2a1. System displays an error message indicating the failure reason. + + Use case resumes from step 1. + +- 3a. Multiple previous commands available. + + - 3a1. Actor presses the "Up" arrow key multiple times to cycle through the command history. + - 3a2. System displays each previous command in sequence. + + Use case ends. ### Non-Functional Requirements -1. Should work on any _mainstream OS_ as long as it has Java `17` 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. +1. The system should work on any _mainstream OS_ as long as it has Java `17` or above installed. +2. The system should be able to hold up to 1000 contacts without a noticeable sluggishness in performance for typical usage. +3. The system should be developed in a modular way for easier updates and bug fixes. +4. The system should ensure data consistency across all instances. +5. The system should continue functioning in the event of a missing or corrupted save file. +6. The system should encrypt sensitive data to follow data protection laws. +7. The interface should be intuitive and easy to use. -*{More to be added}* +_{More to be added}_ ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +- **Mainstream OS**: Windows, Linux, Unix, MacOS +- **Private contact detail**: A contact detail that is not meant to be shared with others +- **Device**: system with dialing and calling capabilities --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Instructions for manual testing** @@ -343,40 +681,40 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1a. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 2b. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -1. Saving window preferences +2. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 2a. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + 2b. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +3. _{ more test cases …​ }_ ### Deleting a person 1. Deleting a person while all persons are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1a. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1b. 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 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 1c. Test case: `delete 0`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. + 1d. 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 …​ }_ +2. _{ more test cases …​ }_ ### Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 1a. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ -1. _{ more test cases …​ }_ +2. _{ more test cases …​ }_ diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index bac5eb36d35..d83abf139ec 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -216,6 +216,8 @@ GEM nokogiri (1.16.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) + nokogiri (1.16.5-x64-mingw32) + racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) @@ -254,6 +256,7 @@ GEM unf_ext (0.0.8.2) unf_ext (0.0.8.2-x64-mingw32) unicode-display_width (1.8.0) + wdm (0.1.1) webrick (1.8.1) PLATFORMS @@ -263,7 +266,8 @@ PLATFORMS DEPENDENCIES github-pages jekyll + wdm (~> 0.1.0) webrick BUNDLED WITH - 2.1.4 + 2.5.19 diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 9f832a19674..07f3a7a0d88 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -3,30 +3,33 @@ layout: page title: Setting up and getting started --- -* Table of Contents -{:toc} +- Table of Contents + {:toc} - --------------------------------------------------------------------------------------------------------------------- +--- ## Setting up the project in your computer -
:exclamation: **Caution:** +
+ +:exclamation: **Caution:** Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps. +
First, **fork** this repo, and **clone** the fork into your computer. If you plan to use Intellij IDEA (highly recommended): + 1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to ensure Intellij is configured to use **JDK 17**. 1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. + :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. 1. **Verify the setup**: - 1. Run the `seedu.address.Main` and try a few commands. + 1. Run the `bizbook.Main` and try a few commands. 1. [Run the tests](Testing.md) to ensure they all pass. --------------------------------------------------------------------------------------------------------------------- +--- ## Before writing code @@ -34,7 +37,9 @@ If you plan to use Intellij IDEA (highly recommended): If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours. -
:bulb: **Tip:** +
+ + :bulb: **Tip:** Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
@@ -45,11 +50,4 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). - -1. **Do the tutorials** - These tutorials will help you get acquainted with the codebase. - - * [Tracing code](tutorials/TracingCode.md) - * [Adding a new command](tutorials/AddRemark.md) - * [Removing fields](tutorials/RemovingFields.md) + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [BizBook’s architecture](DeveloperGuide.md#architecture). diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..c677257858e 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -29,8 +29,8 @@ There are two ways to run tests. This project has three types of tests: 1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest` + e.g. `bizbook.commons.StringUtilTest` 1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest` + e.g. `bizbook.storage.StorageManagerTest` 1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest` + e.g. `bizbook.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 84b4ddc4e40..f1b49d33c57 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,41 +3,41 @@ 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. +BizBook (BB) 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, BB can get your contact management tasks done faster than traditional GUI apps. -* Table of Contents -{:toc} +- Table of Contents + {:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## Quick start 1. Ensure you have Java `17` or above installed in your Computer. -1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `.jar` file from [here](https://github.com/AY2425S1-CS2103-F10-3/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for your 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.
+4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar bizbook.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.
![Ui](images/Ui.png) -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.
+5. 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 a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. - * `delete 3` : Deletes the 3rd contact shown in the current list. + - `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. +6. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- +--- ## Features @@ -45,33 +45,39 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo **:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+- 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.
+- 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.
+- Items with `…`​ after them can be used multiple times including zero times.
e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order.
+- Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+- Command keywords are case insensitive.
+ e.g. `add` and `ADD` are all acceptable. + +- 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`. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a table of basic commands and their usage syntax. + +Redirect link to user guide which explains the commands in detail is also provided below. + +In the event that redirect is unavailable due to OS/browser restrictions, URL will be copied to clipboard as the fail-safe. ![help message](images/helpMessage.png) Format: `help` - ### Adding a person: `add` Adds a person to the address book. @@ -83,8 +89,9 @@ A person can have any number of tags (including 0)
Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` + +- `add 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` ### Listing all persons : `list` @@ -98,16 +105,17 @@ Edits an existing person in the address book. Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* 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. +- Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ +- At least one of the optional fields must be provided. +- Existing values will be updated to the input values. +- When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. +- You can remove all the person’s tags by typing `t/` without + specifying any tags after it. 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 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. +- `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. ### Locating persons by name: `find` @@ -115,16 +123,17 @@ Finds persons whose names contain any of the given keywords. Format: `find KEYWORD [MORE_KEYWORDS]` -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). +- 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. +- Partial words will be matched e.g. `Han` will 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`
+ +- `find John` returns `john`, `John Doe` and `Johnny` +- `find alex david` returns `Alex Yeoh`, `David Li`
![result for 'find alex david'](images/findAlexDavidResult.png) ### Deleting a person : `delete` @@ -133,13 +142,196 @@ Deletes the specified person from the address book. Format: `delete INDEX` -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +- Deletes the person at the specified `INDEX`. +- The index refers to the index number shown in the displayed person list. +- The index **must be a positive integer** 1, 2, 3, …​ + +Examples: + +- `list` followed by `delete 2` deletes the 2nd person in the address book. +- `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. + +### Delete a Tag of an existing contact: `deletetag` + +Deletes a tag from a person in the address book. + +Format: `deletetag INDEX t/[TAG]` + +- Deletes the `TAG` for 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, …​ +- The tag **must be a existing tag** on the person. + +Examples: + +- `deletetag 1 t/friends` +- `deletetag 2 t/Client` + +### Adding Notes to an existing contact: `addnote` + +Adds a note to a person in the address book. + +Format: `addnote INDEX n/[NOTE]` + +
:bulb: **Tip:** +A person can have any number of notes (including 0) +
+ +- Add note to the person at the specified `INDEX`. +- The index refers to the index number shown in the displayed person list. +- The index **must be a positive integer** 1, 2, 3, …​ + +Examples: + +- `addnote 1 n/Supplier 1` +- `addnote 2 n/Supplier 2` + +### Edit a Note of an existing contact: `editnote` + +Edits a note of a person in the address book. + +Format: `editnote INDEX i/NOTE_INDEX n/[NOTE]` + +- Edits the note at the specified `NOTE_INDEX` of the person at the specified `INDEX`. + -The index refers to the index number shown in the displayed person list. +- The note index refers to the index number shown in the notes list of the contact details of the displayed person. +- The index and notes index **must be a positive integer** 1, 2, 3, …​ + +Examples: + +- `editnote 1 i/1 n/Customer 1` +- `editnote 2 i/1 n/Customer 2` + +### Deleting a note from an existing contact: `deletenote` + +Deletes a note from a person in the address book. + +Format: `deletnote INDEX n/[NOTE_INDEX]` + +- Delete note from the person at the specified `INDEX` and specified `NOTE_INDEX`. +- The index refers to the index number shown in the displayed person list. +- The note index refers to the index number shown in the notes list of the contact details of the displayed person. +- The index and note index **must be a positive integer** 1, 2, 3, …​ + +Examples: + +- `deletenote 1 i/1` +- `deletenote 2 i/2` + +### Viewing an existing contact's details : `view` + +Views the details of a person in the address book. + +Format: `view INDEX` + +- Views the details of 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, ... +- The index **must be within the range** shown on the displayed person list. 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. + +- `view 1` shows the contact details of the first person shown on the displayed person list. + +### Pinning a contact: `pin` + +Pins the contact of a person into a dedicated panel. + +Format: `pin INDEX` + +- Pin the contact of the person at the specified `INDEX`. +- The index refers to the index number shown in the displayed person list, not the pinned list. +- The index **must be a positive integer** 1, 2, 3, ... +- The index **must be within the range** shown on the displayed person list. + +Examples: + +- `pin 1` pins the contact of the first person shown on the displayed person list into the pinned person list. + +### Unpinning a contact: `unpin` + +Unpins the contact of a person from the pinned list. + +Format: `unpin INDEX` + +- Unpin the contact of the person at the specified `INDEX`. +- The index refers to the index number shown in the pinned list, not the displayed person list. +- The index **must be a positive integer** 1, 2, 3, ... +- The index **must be within the range** shown on the pinned list. + +Examples: + +- `unpin 1` unpins the contact of the first person shown on the pinned person list. + +### Undoing a previously executed command: `undo` + +Undoes the previous command that was executed. + +Format: `undo` + +- The undo feature saves the **5 most recent executed commands**. +- The undo feature only tracks commands that **modifies the address book**. +- The undo feature will clear the focus person panel upon execution. + +Examples of commands tracked by undo: +- `add` +- `delete` +- `clear` +- `edit` +- `pin` + +### Redoing a previously executed undo command: `redo` + +Redoes the previous undo command that was executed. + +Format: `redo` + +- The redo feature saves the **5 most recent executed undo commands**. +- The redo feature only tracks changed made by the undo command +- The redo feature will clear the focus person panel upon execution. + +### Importing a contact list : `import` + +Imports a file containing contacts from a specified file type and overwrites all +existing contacts with the imported data. + +Format: `import f/FILETYPE p/PATH` + +- Imports the contact list from the specified `FILETYPE`. +- The only supported file type currently is **vcf**. + +Examples: + +- `import f/vcf p/bizbook.vcf` imports the contacts from the file `bizbook.vcf` that is located in the same folder as this application. +- `import f/vcf p//Users/Name/myAddress.bcf` imports the contacts from the file located at `/Users/Name/myAddress.bcf` + +### Exporting the contact list : `export` + +Exports the contacts in the contact list into the specified file type. The file will be named bizbook.<file extension> and will be located in a folder named exports. + +Format: `export f/FILETYPE` + +- Exports the contact list into the specified `FILETYPE`. +- The file type must be **either csv or vcf**. + +Examples: + +- `export f/csv` exports the contact list into a csv file. +- `export f/vcf` exports the contact list into a vcf file. + +### Changing the application's theme : `toggle` + +Changes the application theme from light to dark or from dark to light. + +Format: `toggle` + +- If application is currently in light mode, toggle command will set it to dark mode. +- If application is currently in dark mode, toggle command will set it to light mode. +- Please note that our application does not save your theme preference, so it will always open in dark mode by default. + +Examples: + +- `toggle` changes the application theme. ### Clearing all entries : `clear` @@ -163,37 +355,49 @@ AddressBook data are saved automatically as a JSON file `[JAR file location]/dat
: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. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. +Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
### Archiving data files `[coming in v2.0]` _Details coming soon ..._ --------------------------------------------------------------------------------------------------------------------- +--- ## FAQ **Q**: How do I transfer my data to another Computer?
**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. --------------------------------------------------------------------------------------------------------------------- +--- ## Known issues 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. 2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window. --------------------------------------------------------------------------------------------------------------------- +--- ## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +| Action | 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` | +| **List** | `list` | +| **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` | +| **Delete** | `delete INDEX`
e.g., `delete 3` | +| **Deletetag** | `deletetag INDEX t/[TAG]`
e.g. `deletetag 1 t/Client` | +| **Addnote** | `addnote INDEX n/[NOTE]`
e.g. `addnote 1 n/Customer 1` | +| **Editnote** | `editnote INDEX i/NOTE_INDEX n/[NOTE]`
e.g. `editnote 1 i/1 n/Customer 1` | +| **Deletenote** | `deletenote INDEX i/[NOTE_INDEX]`
e.g. `deletenote 1 i/1` | +| **View** | `view INDEX`
e.g. `view 1` | +| **Pin** | `pin INDEX`
e.g. `pin 1` | +| **Unpin** | `unpin INDEX`
e.g. `unpin 1` | +| **Undo** | `undo` | +| **Redo** | `redo` | +| **Toggle** | `toggle` | +| **Export** | `export f/FILETYPE`
e.g. `export f/csv` | +| **Import** | `import f/FILETYPE p/PATH`
e.g. `import f/vcf p/myVcf.vcf` | +| **Clear** | `clear` | +| **Help** | `help` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..9009f20da98 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "BB" theme: minima header_pages: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..fc9ce25c3e7 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "BizBook"; font-size: 32px; } } diff --git a/docs/diagrams/ArchitectureSequenceDiagram2.puml b/docs/diagrams/ArchitectureSequenceDiagram2.puml new file mode 100644 index 00000000000..7dcf0965e98 --- /dev/null +++ b/docs/diagrams/ArchitectureSequenceDiagram2.puml @@ -0,0 +1,27 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +Actor User as user USER_COLOR +Participant ":UI" as ui UI_COLOR +Participant ":Logic" as logic LOGIC_COLOR +Participant ":Model" as model MODEL_COLOR + +user -[USER_COLOR]> ui : "export f/csv" +activate ui UI_COLOR + +ui -[UI_COLOR]> logic : execute("export f/csv") +activate logic LOGIC_COLOR + +logic -[LOGIC_COLOR]> model : getFilteredPersonList() +activate model MODEL_COLOR + +model -[MODEL_COLOR]-> logic +deactivate model + +logic --[LOGIC_COLOR]> ui +deactivate logic + +ui--[UI_COLOR]> user +deactivate ui +@enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..5cadccc7cc5 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -13,6 +13,7 @@ UniqueTagList -right-> "*" Tag UniquePersonList -right-> Person Person -up-> "*" Tag +Person -right->"*" Note Person *--> Name Person *--> Phone diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..1b6fdc7948f 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -8,6 +8,7 @@ participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant "r:CommandResult" as CommandResult LOGIC_COLOR + end box box Model MODEL_COLOR_T1 @@ -34,7 +35,7 @@ create DeleteCommand DeleteCommandParser -> DeleteCommand activate DeleteCommand -DeleteCommand --> DeleteCommandParser : +DeleteCommand --> DeleteCommandParser deactivate DeleteCommand DeleteCommandParser --> AddressBookParser : d @@ -49,7 +50,13 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : getFilteredPersonList() +activate Model + +Model --> DeleteCommand +deactivate Model + +DeleteCommand -> Model : deletePerson(p) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..17589892322 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -19,6 +19,7 @@ Class Email Class Name Class Phone Class Tag +Class Note Class I #FFFFFF } @@ -42,6 +43,7 @@ Person *--> Phone Person *--> Email Person *--> Address Person *--> "*" Tag +Person *--> "*" Note Person -[hidden]up--> I UniquePersonList -[hidden]right-> I diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..e253578bd6c 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -20,6 +20,7 @@ Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson Class JsonAdaptedTag +Class JsonAdaptedNote } } @@ -39,5 +40,6 @@ JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonAdaptedPerson --> "*" JsonAdaptedNote @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..f50c51d7186 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -13,8 +13,12 @@ Class HelpWindow Class ResultDisplay Class PersonListPanel Class PersonCard +Class ContactDetails Class StatusBarFooter Class CommandBox +Class SearchBox +Class PinnedPersonListPanel +Class PinnedPersonCard } package Model <> { @@ -30,31 +34,44 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> "1" MainWindow + MainWindow *-down-> "1" CommandBox +MainWindow *-down-> "1" SearchBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" PinnedPersonListPanel MainWindow *-down-> "1" StatusBarFooter -MainWindow --> "0..1" HelpWindow +MainWindow -down-> "0..1" HelpWindow +MainWindow *-down-> "0..1" ContactDetails PersonListPanel -down-> "*" PersonCard +PinnedPersonListPanel -down-> "*" PinnedPersonCard MainWindow -left-|> UiPart +ContactDetails -down-|> UiPart + +ResultDisplay -down-|> UiPart +CommandBox -down-|> UiPart +SearchBox -down-|> UiPart +PersonListPanel -down-|> UiPart +PersonCard -down-|> UiPart +PinnedPersonListPanel -down-|> UiPart +PinnedPersonCard -down-|> UiPart +StatusBarFooter -down-|> UiPart +HelpWindow -down-|> UiPart -ResultDisplay --|> UiPart -CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart -StatusBarFooter --|> UiPart -HelpWindow --|> UiPart +PersonCard .down.> Model +PinnedPersonCard .down.> Model +ContactDetails .down.> Model -PersonCard ..> Model UiManager -right-> Logic -MainWindow -left-> Logic +MainWindow -right-> Logic PersonListPanel -[hidden]left- HelpWindow -HelpWindow -[hidden]left- CommandBox +HelpWindow -[hidden]left- SearchBox +SearchBox -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter -MainWindow -[hidden]-|> UiPart +MainWindow -[hidden]down-|> UiPart @enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 43a45903ac9..a656ab398a8 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -6,15 +6,17 @@ skinparam ClassBackgroundColor #FFFFAA title Initial state -package States { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" class State3 as "ab2:AddressBook" } + State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -hide State2 -hide State3 + +hide UndoStateList.State2 +hide UndoStateList.State3 class Pointer as "Current State" #FFFFFF Pointer -up-> State1 diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 5a41e9e1651..5b4fc4b8da3 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -6,7 +6,7 @@ skinparam ClassBackgroundColor #FFFFAA title After command "delete 5" -package States <> { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" class State3 as "ab2:AddressBook" @@ -15,7 +15,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -hide State3 +hide UndoStateList.State3 class Pointer as "Current State" #FFFFFF diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index ad32fce1b0b..e0a4e91d6c7 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -6,7 +6,7 @@ skinparam ClassBackgroundColor #FFFFAA title After command "add n/David" -package States <> { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" class State3 as "ab2:AddressBook" diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index 9187a690036..fc7b1e021b0 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -6,16 +6,22 @@ skinparam ClassBackgroundColor #FFFFAA title After command "undo" -package States <> { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 class Pointer as "Current State" #FFFFFF Pointer -up-> State2 + +package RedoStateList <> { + class State3 as "ab2:AddressBook" +} + +class RedoPointer as "Redo Pointer" #FFFFFF + +RedoPointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 2bc631ffcd0..06e2aa24acd 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -6,16 +6,22 @@ skinparam ClassBackgroundColor #FFFFAA title After command "list" -package States <> { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 class Pointer as "Current State" #FFFFFF Pointer -up-> State2 + +package RedoStateList <> { + class State3 as "ab2:AddressBook" +} + +class RedoPointer as "Redo Pointer" #FFFFFF + +RedoPointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index e77b04104aa..63c464b5516 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -6,7 +6,7 @@ skinparam ClassBackgroundColor #FFFFAA title After command "clear" -package States <> { +package UndoStateList <> { class State1 as "ab0:AddressBook" class State2 as "ab1:AddressBook" class State3 as "ab3:AddressBook" @@ -18,5 +18,5 @@ State2 -[hidden]right-> State3 class Pointer as "Current State" #FFFFFF Pointer -up-> State3 -note right on link: State ab2 deleted. +note right on link: RedoStateList will be cleared. @end diff --git a/docs/diagrams/UndoSequenceDiagram-Logic.puml b/docs/diagrams/UndoSequenceDiagram-Logic.puml index e57368c5159..feb235b50ef 100644 --- a/docs/diagrams/UndoSequenceDiagram-Logic.puml +++ b/docs/diagrams/UndoSequenceDiagram-Logic.puml @@ -30,7 +30,7 @@ deactivate AddressBookParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : revertAddressBookVersion() activate Model Model --> UndoCommand diff --git a/docs/diagrams/UndoSequenceDiagram-Model.puml b/docs/diagrams/UndoSequenceDiagram-Model.puml index 54d83208cb8..8c116174a60 100644 --- a/docs/diagrams/UndoSequenceDiagram-Model.puml +++ b/docs/diagrams/UndoSequenceDiagram-Model.puml @@ -7,13 +7,12 @@ participant ":Model" as Model MODEL_COLOR participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR end box -[-> Model : undoAddressBook() +[-> Model : revertAddressBookVersion() activate Model Model -> VersionedAddressBook : undo() activate VersionedAddressBook -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) VersionedAddressBook --> Model : deactivate VersionedAddressBook diff --git a/docs/images/ArchitectureSequenceDiagram2.png b/docs/images/ArchitectureSequenceDiagram2.png new file mode 100644 index 00000000000..40a156993c6 Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagram2.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..7f5b9858c2b 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index ac2ae217c51..e0fe4ba56da 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..0cc9bc26da3 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 18fa4d0d51f..c6441cda087 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..5b7399164bc 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 11f06d68671..6b7f0aa1401 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png index c5f91b58533..790e999288b 100644 Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png index 2d3ad09c047..4bc9f9d78a5 100644 Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 20853694e03..e9eda912914 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 1a9551b31be..bad4f5f879e 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png index 46dfae78c94..a5899fee1db 100644 Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png index f45889b5fdf..56ec43b3fd4 100644 Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ diff --git a/docs/images/UndoSequenceDiagram-Logic.png b/docs/images/UndoSequenceDiagram-Logic.png index 78e95214294..a90bc4232e7 100644 Binary files a/docs/images/UndoSequenceDiagram-Logic.png and b/docs/images/UndoSequenceDiagram-Logic.png differ diff --git a/docs/images/UndoSequenceDiagram-Model.png b/docs/images/UndoSequenceDiagram-Model.png index f0f3da6ae50..e8b56c3e637 100644 Binary files a/docs/images/UndoSequenceDiagram-Model.png and b/docs/images/UndoSequenceDiagram-Model.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..5f9133ebd6b 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/itlimjiawei.png b/docs/images/itlimjiawei.png new file mode 100644 index 00000000000..fc4500be3ca Binary files /dev/null and b/docs/images/itlimjiawei.png differ diff --git a/docs/images/joeltio.png b/docs/images/joeltio.png new file mode 100644 index 00000000000..bdba6479eb1 Binary files /dev/null and b/docs/images/joeltio.png differ diff --git a/docs/images/kennethteo2002.png b/docs/images/kennethteo2002.png new file mode 100644 index 00000000000..86bc34e7e1e Binary files /dev/null and b/docs/images/kennethteo2002.png differ diff --git a/docs/images/nicholas-cheng-de-fei.png b/docs/images/nicholas-cheng-de-fei.png new file mode 100644 index 00000000000..224ab5e034f Binary files /dev/null and b/docs/images/nicholas-cheng-de-fei.png differ diff --git a/docs/images/sheenkerr.png b/docs/images/sheenkerr.png new file mode 100644 index 00000000000..45260a8025f Binary files /dev/null and b/docs/images/sheenkerr.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..2fc9201858a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,20 @@ --- layout: page -title: AddressBook Level-3 +title: BizBook --- - -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) + +[![CI Status](https://github.com/AY2425S1-CS2103-F10-3/tp/actions/workflows/gradle.yml/badge.svg?branch=master)](https://github.com/AY2425S1-CS2103-F10-3/tp/actions/workflows/gradle.yml) +[![codecov](https://codecov.io/gh/AY2425S1-CS2103-F10-3/tp/graph/badge.svg?token=XDT6OIN0I7)](https://codecov.io/gh/AY2425S1-CS2103-F10-3/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**BizBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using BizBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing BizBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5), [Mockito](https://github.com/mockito/mockito) + diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/bizbook/AppParameters.java similarity index 92% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/bizbook/AppParameters.java index 3d603622d4e..c3befd7de45 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/bizbook/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package bizbook; import java.nio.file.Path; import java.nio.file.Paths; @@ -6,10 +6,10 @@ import java.util.Objects; import java.util.logging.Logger; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.util.FileUtil; +import bizbook.commons.util.ToStringBuilder; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.ToStringBuilder; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/bizbook/Main.java similarity index 96% rename from src/main/java/seedu/address/Main.java rename to src/main/java/bizbook/Main.java index 9461d6da769..c1aae614d2e 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/bizbook/Main.java @@ -1,9 +1,9 @@ -package seedu.address; +package bizbook; import java.util.logging.Logger; +import bizbook.commons.core.LogsCenter; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; /** * The main entry point to the application. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/bizbook/MainApp.java similarity index 85% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/bizbook/MainApp.java index 678ddc8c218..db37b9c9b49 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/bizbook/MainApp.java @@ -1,35 +1,35 @@ -package seedu.address; +package bizbook; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Logger; +import bizbook.commons.core.Config; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.core.Version; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.commons.util.ConfigUtil; +import bizbook.commons.util.StringUtil; +import bizbook.logic.Logic; +import bizbook.logic.LogicManager; +import bizbook.model.AddressBook; +import bizbook.model.Model; +import bizbook.model.ModelManager; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.UserPrefs; +import bizbook.model.util.SampleDataUtil; +import bizbook.storage.AddressBookStorage; +import bizbook.storage.JsonAddressBookStorage; +import bizbook.storage.JsonUserPrefsStorage; +import bizbook.storage.Storage; +import bizbook.storage.StorageManager; +import bizbook.storage.UserPrefsStorage; +import bizbook.ui.Ui; +import bizbook.ui.UiManager; import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -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.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; /** * Runs the application. diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/bizbook/commons/core/Config.java similarity index 94% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/bizbook/commons/core/Config.java index 485f85a5e05..2a357208d61 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/bizbook/commons/core/Config.java @@ -1,11 +1,11 @@ -package seedu.address.commons.core; +package bizbook.commons.core; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.logging.Level; -import seedu.address.commons.util.ToStringBuilder; +import bizbook.commons.util.ToStringBuilder; /** * Config values used by the app diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/bizbook/commons/core/GuiSettings.java similarity index 96% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/bizbook/commons/core/GuiSettings.java index a97a86ee8d7..ec6ee16a502 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/bizbook/commons/core/GuiSettings.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; +package bizbook.commons.core; import java.awt.Point; import java.io.Serializable; import java.util.Objects; -import seedu.address.commons.util.ToStringBuilder; +import bizbook.commons.util.ToStringBuilder; /** * A Serializable class that contains the GUI settings. diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/bizbook/commons/core/LogsCenter.java similarity index 99% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/bizbook/commons/core/LogsCenter.java index 8cf8e15a0f0..8679301e887 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/bizbook/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package bizbook.commons.core; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/bizbook/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/bizbook/commons/core/Version.java index 491d24559b4..98bc95a81e4 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/bizbook/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package bizbook.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -50,6 +50,7 @@ public boolean isEarlyAccess() { /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/bizbook/commons/core/index/Index.java similarity index 95% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/bizbook/commons/core/index/Index.java index dd170d8b68d..81ee3341cfc 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/bizbook/commons/core/index/Index.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core.index; +package bizbook.commons.core.index; -import seedu.address.commons.util.ToStringBuilder; +import bizbook.commons.util.ToStringBuilder; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java b/src/main/java/bizbook/commons/exceptions/DataLoadingException.java similarity index 82% rename from src/main/java/seedu/address/commons/exceptions/DataLoadingException.java rename to src/main/java/bizbook/commons/exceptions/DataLoadingException.java index 9904ba47afe..476fc6cb013 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java +++ b/src/main/java/bizbook/commons/exceptions/DataLoadingException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package bizbook.commons.exceptions; /** * Represents an error during loading of data from a file. diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/bizbook/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/bizbook/commons/exceptions/IllegalValueException.java index 19124db485c..28ee01e6e83 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/bizbook/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package bizbook.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/bizbook/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/bizbook/commons/util/AppUtil.java index 87aa89c0326..270c4ca9412 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/bizbook/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package bizbook.commons.util; import static java.util.Objects.requireNonNull; +import bizbook.MainApp; import javafx.scene.image.Image; -import seedu.address.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/bizbook/commons/util/CollectionUtil.java similarity index 91% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/bizbook/commons/util/CollectionUtil.java index eafe4dfd681..50a969362e9 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/bizbook/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package bizbook.commons.util; import static java.util.Objects.requireNonNull; @@ -12,7 +12,9 @@ */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/bizbook/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/bizbook/commons/util/ConfigUtil.java index 7b829c3c4cc..cd978003ec9 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/bizbook/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package bizbook.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataLoadingException; +import bizbook.commons.core.Config; +import bizbook.commons.exceptions.DataLoadingException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/bizbook/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/bizbook/commons/util/FileUtil.java index b1e2767cdd9..778b7e569b6 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/bizbook/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package bizbook.commons.util; import java.io.IOException; import java.nio.file.Files; @@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) { /** * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -33,6 +34,7 @@ public static boolean isValidPath(String path) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/bizbook/commons/util/JsonUtil.java similarity index 86% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/bizbook/commons/util/JsonUtil.java index 100cb16c395..aa14fa739ce 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/bizbook/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package bizbook.commons.util; import static java.util.Objects.requireNonNull; @@ -11,17 +11,20 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.exceptions.DataLoadingException; /** * Converts a Java object instance to JSON and vice versa @@ -37,7 +40,8 @@ public class JsonUtil { .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) .registerModule(new SimpleModule("SimpleModule") .addSerializer(Level.class, new ToStringSerializer()) - .addDeserializer(Level.class, new LevelDeserializer(Level.class))); + .addDeserializer(Level.class, new LevelDeserializer(Level.class)) + .addSerializer(Path.class, new PathSerializer(Path.class))); static void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException { FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize)); @@ -80,6 +84,7 @@ public static Optional readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -94,6 +99,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -103,6 +109,7 @@ public static T fromJsonString(String json, Class instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string @@ -141,4 +148,17 @@ public Class handledType() { } } + /** + * Represents a class that can serialize to + */ + private static class PathSerializer extends StdSerializer { + protected PathSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Path path, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeString(path.toString()); + } + } } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/bizbook/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/bizbook/commons/util/StringUtil.java index 61cc8c9a1cb..d48ac80fd76 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/bizbook/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package bizbook.commons.util; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; @@ -20,6 +20,7 @@ public class StringUtil { * containsWordIgnoreCase("ABc def", "DEF") == true * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match * + * * @param sentence cannot be null * @param word cannot be null, cannot be empty, must be a single word */ @@ -53,6 +54,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/bizbook/commons/util/ToStringBuilder.java similarity index 97% rename from src/main/java/seedu/address/commons/util/ToStringBuilder.java rename to src/main/java/bizbook/commons/util/ToStringBuilder.java index d979b926734..b65c0910be2 100644 --- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java +++ b/src/main/java/bizbook/commons/util/ToStringBuilder.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package bizbook.commons.util; /** * Builds a string representation of an object that is suitable as the return value of {@link Object#toString()}. diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/bizbook/logic/Logic.java similarity index 62% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/bizbook/logic/Logic.java index 92cd8fa605a..58c202e42b4 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/bizbook/logic/Logic.java @@ -1,14 +1,16 @@ -package seedu.address.logic; +package bizbook.logic; import java.nio.file.Path; +import bizbook.commons.core.GuiSettings; +import bizbook.logic.commands.CommandResult; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.Model; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; /** * API of the Logic component @@ -16,6 +18,7 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. @@ -26,13 +29,16 @@ public interface Logic { /** * Returns the AddressBook. * - * @see seedu.address.model.Model#getAddressBook() + * @see Model#getAddressBook() */ ReadOnlyAddressBook getAddressBook(); /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the list of pinned persons */ + ObservableList getPinnedPersonList(); + /** * Returns the user prefs' address book file path. */ @@ -43,6 +49,11 @@ public interface Logic { */ GuiSettings getGuiSettings(); + /** + * Returns the person who will be focused on. + */ + ObjectProperty getFocusedPerson(); + /** * Set the user prefs' GUI settings. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/bizbook/logic/LogicManager.java similarity index 74% rename from src/main/java/seedu/address/logic/LogicManager.java rename to src/main/java/bizbook/logic/LogicManager.java index 5aa3b91c7d0..47b6d2dc6dc 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/bizbook/logic/LogicManager.java @@ -1,22 +1,23 @@ -package seedu.address.logic; +package bizbook.logic; import java.io.IOException; import java.nio.file.AccessDeniedException; import java.nio.file.Path; import java.util.logging.Logger; +import bizbook.commons.core.GuiSettings; +import bizbook.commons.core.LogsCenter; +import bizbook.logic.commands.Command; +import bizbook.logic.commands.CommandResult; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.AddressBookParser; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.Model; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.person.Person; +import bizbook.storage.Storage; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; /** * The main LogicManager of the app. @@ -49,6 +50,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE CommandResult commandResult; Command command = addressBookParser.parseCommand(commandText); commandResult = command.execute(model); + model.saveAddressBookVersion(); try { storage.saveAddressBook(model.getAddressBook()); @@ -71,6 +73,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getPinnedPersonList() { + return model.getPinnedPersonList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); @@ -81,6 +88,11 @@ public GuiSettings getGuiSettings() { return model.getGuiSettings(); } + @Override + public ObjectProperty getFocusedPerson() { + return model.getFocusedPerson(); + } + @Override public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/bizbook/logic/Messages.java similarity index 61% rename from src/main/java/seedu/address/logic/Messages.java rename to src/main/java/bizbook/logic/Messages.java index ecd32c31b53..6ef559529ab 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/bizbook/logic/Messages.java @@ -1,23 +1,26 @@ -package seedu.address.logic; +package bizbook.logic; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import seedu.address.logic.parser.Prefix; -import seedu.address.model.person.Person; +import bizbook.logic.parser.Prefix; +import bizbook.model.person.Person; /** * Container for user visible messages. */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + 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_INDEX = "The person index provided is invalid."; + public static final String MESSAGE_INVALID_NOTE_INDEX = "The note index provided is invalid."; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_UNSUPPORTED_FILE_TYPE = "This file type is not supported. \n%1$s"; + public static final String MESSAGE_INVALID_FILE_PATH = "The following file path is invalid: %1$s"; /** * Returns an error message indicating the duplicate prefixes. @@ -32,7 +35,7 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref } /** - * Formats the {@code person} for display to the user. + * Formats contact detail of {@code person} for display to the user. */ public static String format(Person person) { final StringBuilder builder = new StringBuilder(); @@ -45,6 +48,21 @@ public static String format(Person person) { .append(person.getAddress()) .append("; Tags: "); person.getTags().forEach(builder::append); + + builder.append("; Notes: "); + person.getNotes().forEach(builder::append); + + return builder.toString(); + } + + /** + * Formats contact detail of {@code person} to display only name and phone + * to the user. + */ + public static String formatShort(Person person) { + final StringBuilder builder = new StringBuilder(); + builder.append(person.getName()) + .append(String.format(" (%s)", person.getPhone())); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/bizbook/logic/commands/AddCommand.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/bizbook/logic/commands/AddCommand.java index 5d7185a9680..875be726169 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/bizbook/logic/commands/AddCommand.java @@ -1,17 +1,17 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; +import static bizbook.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static bizbook.logic.parser.CliSyntax.PREFIX_EMAIL; +import static bizbook.logic.parser.CliSyntax.PREFIX_NAME; +import static bizbook.logic.parser.CliSyntax.PREFIX_PHONE; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Person; /** * Adds a person to the address book. diff --git a/src/main/java/bizbook/logic/commands/AddNoteCommand.java b/src/main/java/bizbook/logic/commands/AddNoteCommand.java new file mode 100644 index 00000000000..a0bb1a73499 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/AddNoteCommand.java @@ -0,0 +1,106 @@ +package bizbook.logic.commands; + +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; +import static bizbook.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Note; +import bizbook.model.person.Person; + + +/** + * Changes or adds the notes of an existing person in BizBook. + */ +public class AddNoteCommand extends Command { + + public static final String COMMAND_WORD = "addnote"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds the note of the person identified " + + "by the person index number used on the left display panel. " + + "New note(letters and numbers) will be appended to the notes currently stored.\n" + + "Parameters: INDEX (must be a positive integer) " + + "n/NOTE\n" + + "Example: " + COMMAND_WORD + " 1 " + + "n/High profile client."; + + public static final String MESSAGE_ADD_NOTE_SUCCESS = "Added note to Person: %1$s"; + public static final String DUPLICATE_MESSAGE_CONSTRAINTS = "There is already an existing note with this name."; + + private final Index index; + private final Note note; + + + /** + * @param index of the person in the filtered person list to add a new note + * @param note of the person to be added + */ + public AddNoteCommand(Index index, Note note) { + requireAllNonNull(index, note); + + this.index = index; + this.note = note; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + + ArrayList notesList = new ArrayList<>(personToEdit.getNotes()); + + if (notesList.contains(note)) { + throw new CommandException(DUPLICATE_MESSAGE_CONSTRAINTS); + } + + notesList.add(note); + + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), personToEdit.getTags(), notesList); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + model.getFocusedPerson().set(editedPerson); + + return new CommandResult(String.format(generateSuccessMessage(editedPerson), index.getOneBased()), + false, false); + } + + /** + * Generates a command execution success message + * the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + return String.format(MESSAGE_ADD_NOTE_SUCCESS, Messages.format(personToEdit)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddNoteCommand)) { + return false; + } + + AddNoteCommand otherAddNoteCommand = (AddNoteCommand) other; + return note.equals(otherAddNoteCommand.note); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/bizbook/logic/commands/ClearCommand.java similarity index 50% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/bizbook/logic/commands/ClearCommand.java index 9c86b1fa6e4..f42d876f319 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/bizbook/logic/commands/ClearCommand.java @@ -1,16 +1,19 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; +import bizbook.model.AddressBook; +import bizbook.model.Model; /** * Clears the address book. */ public class ClearCommand extends Command { - public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes all contacts.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; @@ -18,6 +21,8 @@ public class ClearCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); + model.setFocusPerson(null); + CommandResult commandResult = new CommandResult(MESSAGE_SUCCESS); + return commandResult; } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/bizbook/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/bizbook/logic/commands/Command.java index 64f18992160..894f249f3da 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/bizbook/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/bizbook/logic/commands/CommandResult.java similarity index 75% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/bizbook/logic/commands/CommandResult.java index 249b6072d0d..d1c1237bc16 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/bizbook/logic/commands/CommandResult.java @@ -1,10 +1,10 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; import static java.util.Objects.requireNonNull; import java.util.Objects; -import seedu.address.commons.util.ToStringBuilder; +import bizbook.commons.util.ToStringBuilder; /** * Represents the result of a command execution. @@ -19,6 +19,9 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** The application should toggle its theme. */ + private final boolean themeChange; + /** * Constructs a {@code CommandResult} with the specified fields. */ @@ -26,6 +29,17 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.themeChange = false; + } + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean themeChange) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.showHelp = showHelp; + this.exit = exit; + this.themeChange = themeChange; } /** @@ -48,6 +62,10 @@ public boolean isExit() { return exit; } + public boolean isThemeChange() { + return themeChange; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -78,5 +96,4 @@ public String toString() { .add("exit", exit) .toString(); } - } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/bizbook/logic/commands/DeleteCommand.java similarity index 72% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/bizbook/logic/commands/DeleteCommand.java index 1135ac19b74..e6ff3130e67 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/bizbook/logic/commands/DeleteCommand.java @@ -1,15 +1,15 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; import static java.util.Objects.requireNonNull; import java.util.List; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import bizbook.commons.core.index.Index; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Person; /** * Deletes a person identified using it's displayed index from the address book. @@ -27,6 +27,11 @@ public class DeleteCommand extends Command { private final Index targetIndex; + /** + * Creates a DeleteCommand to delete the specified person. + * + * @param targetIndex of the person in the filtered person list to be deleted + */ public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; } @@ -42,7 +47,12 @@ public CommandResult execute(Model model) throws CommandException { Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); + + model.updateFocusPerson(personToDelete, null); + + CommandResult commandResult = new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, + Messages.formatShort(personToDelete))); + return commandResult; } @Override diff --git a/src/main/java/bizbook/logic/commands/DeleteNoteCommand.java b/src/main/java/bizbook/logic/commands/DeleteNoteCommand.java new file mode 100644 index 00000000000..735d99fd068 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/DeleteNoteCommand.java @@ -0,0 +1,115 @@ +package bizbook.logic.commands; + +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; +import static bizbook.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; + +/** + * Deletes note of an existing person in the address book. + */ +public class DeleteNoteCommand extends Command { + + public static final String COMMAND_WORD = "deletenote"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the note of the person identified " + + "by the index number used in the person listing and the index of the note.\n" + + "Parameters: INDEX (must be a positive integer) " + + "i/NOTE_INDEX\n" + + "Example: " + COMMAND_WORD + " 1 " + + "i/1"; + + public static final String MESSAGE_DELETE_NOTE_SUCCESS = "Deleted note of Person: %1$s"; + + /** + * Index of the person whose note is to be deleted + */ + private final Index index; + + /** + * Index of the note be deleted + */ + private final Index noteIndex; + + /** + * @param index of the person in the filtered person list to edit the notes + * @param noteIndex of the note to be deleted + */ + public DeleteNoteCommand(Index index, Index noteIndex) { + requireAllNonNull(index, noteIndex); + + this.index = index; + this.noteIndex = noteIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + + List notesList = new ArrayList<>(personToEdit.getNotes()); + + // if there are no notes with this index + if (noteIndex.getOneBased() > notesList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_NOTE_INDEX); + } + + // remove the specified note + Note noteToRemove = notesList.get(noteIndex.getZeroBased()); + notesList.remove(noteToRemove); + + ArrayList updatedNotesList = new ArrayList<>(notesList); + + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), personToEdit.getTags(), updatedNotesList); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + ObjectProperty focusedPerson = model.getFocusedPerson(); + focusedPerson.set(editedPerson); + + return new CommandResult(String.format(generateSuccessMessage(editedPerson), index.getOneBased()), + false, false); + } + + /** + * Generates a command execution success message + * the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + return String.format(MESSAGE_DELETE_NOTE_SUCCESS, Messages.format(personToEdit)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteNoteCommand)) { + return false; + } + + DeleteNoteCommand otherDeleteNoteCommand = (DeleteNoteCommand) other; + return index.equals(otherDeleteNoteCommand.index) && noteIndex.equals(otherDeleteNoteCommand.noteIndex); + } +} diff --git a/src/main/java/bizbook/logic/commands/DeleteTagCommand.java b/src/main/java/bizbook/logic/commands/DeleteTagCommand.java new file mode 100644 index 00000000000..dad23c010b6 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/DeleteTagCommand.java @@ -0,0 +1,102 @@ +package bizbook.logic.commands; + +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import bizbook.commons.core.index.Index; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; + +/** + * Delete a tag from an existing person in the address book. + */ +public class DeleteTagCommand extends Command { + + public static final String COMMAND_WORD = "deletetag"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the tag from a specfic person.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_TAG + "BusinessMan"; + + public static final String MESSAGE_REMOVE_TAG_SUCCESS = "Tag was removed from Person: %1$s"; + public static final String TAG_DOES_NOT_EXIST = "Unable to delete tag named %1$s " + + "because the tag does not exist for the person."; + + private final Index personIndex; + private final Tag tagToDelete; + + /** + * @param personIndex of the person in the filtered person list to remove the tag from. + * @param tag is the name of the tag to be removed from the {@code Person}. + */ + public DeleteTagCommand(Index personIndex, Tag tag) { + requireAllNonNull(personIndex, tag); + + this.personIndex = personIndex; + this.tagToDelete = tag; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + + List lastShownList = model.getFilteredPersonList(); + + if (personIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(personIndex.getZeroBased()); + Set tagSet = personToEdit.getTags(); + + if (!tagSet.contains(tagToDelete)) { + throw new CommandException(String.format(TAG_DOES_NOT_EXIST, tagToDelete.tagName)); + } + + Set updatedTagSet = tagSet.stream() + .filter(t -> !t.equals(tagToDelete)) + .collect(Collectors.toSet()); + + Name name = personToEdit.getName(); + Email email = personToEdit.getEmail(); + Phone phone = personToEdit.getPhone(); + Address address = personToEdit.getAddress(); + ArrayList notesList = personToEdit.getNotes(); + + Person updatedPerson = new Person(name, phone, email, address, updatedTagSet, notesList); + + model.setPerson(personToEdit, updatedPerson); + + return new CommandResult(String.format(MESSAGE_REMOVE_TAG_SUCCESS, Messages.format(updatedPerson))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DeleteTagCommand)) { + return false; + } + + DeleteTagCommand otherDeleteTagCommand = (DeleteTagCommand) other; + return this.personIndex.equals(otherDeleteTagCommand.personIndex) + && this.tagToDelete.equals(otherDeleteTagCommand.tagToDelete); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/bizbook/logic/commands/EditCommand.java similarity index 83% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/bizbook/logic/commands/EditCommand.java index 4b581c7331e..c9efbe2eed4 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/bizbook/logic/commands/EditCommand.java @@ -1,32 +1,34 @@ -package seedu.address.logic.commands; - +package bizbook.logic.commands; + +import static bizbook.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static bizbook.logic.parser.CliSyntax.PREFIX_EMAIL; +import static bizbook.logic.parser.CliSyntax.PREFIX_NAME; +import static bizbook.logic.parser.CliSyntax.PREFIX_PHONE; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; +import static bizbook.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import bizbook.commons.core.index.Index; +import bizbook.commons.util.CollectionUtil; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; /** * Edits the details of an existing person in the address book. @@ -83,6 +85,8 @@ public CommandResult execute(Model model) throws CommandException { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + model.updateFocusPerson(personToEdit, editedPerson); + model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); @@ -100,8 +104,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()); + ArrayList updatedNotes = editPersonDescriptor.getNotes().orElse(personToEdit.getNotes()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, updatedNotes); } @Override @@ -138,6 +143,7 @@ public static class EditPersonDescriptor { private Email email; private Address address; private Set tags; + private ArrayList notes; public EditPersonDescriptor() {} @@ -151,6 +157,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setEmail(toCopy.email); setAddress(toCopy.address); setTags(toCopy.tags); + setNotes(toCopy.notes); } /** @@ -192,12 +199,20 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + public void setNotes(ArrayList notes) { + this.notes = (notes != null) ? new ArrayList<>(notes) : null; + } + + public Optional> getNotes() { + return (notes != null) ? Optional.of(notes) : Optional.empty(); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. */ public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + this.tags = (tags != null) ? new LinkedHashSet<>(tags) : null; } /** diff --git a/src/main/java/bizbook/logic/commands/EditNoteCommand.java b/src/main/java/bizbook/logic/commands/EditNoteCommand.java new file mode 100644 index 00000000000..46ab5d881d8 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/EditNoteCommand.java @@ -0,0 +1,117 @@ +package bizbook.logic.commands; + +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; +import static bizbook.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Note; +import bizbook.model.person.Person; + +/** + * Edits a note of an existing person in the address book. + */ +public class EditNoteCommand extends Command { + + public static final String COMMAND_WORD = "editnote"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the note of the person identified " + + "by the person index number used on the left display panel. " + + "The note will replace the currently stored note at the specified index.\n" + + "Parameters: INDEX i/NOTE_INDEX n/NOTE\n" + + "Example: " + COMMAND_WORD + " 1 i/1 n/High profile client."; + + public static final String MESSAGE_EDIT_NOTE_SUCCESS = "Edit note of Person: %1$s"; + public static final String DUPLICATE_MESSAGE_CONSTRAINTS = "There is already an existing note with this name."; + + private final Index personIndex; + private final Index noteIndex; + private final Note note; + + /** + * @param personIndex of the person in the filtered person list to edit the note + * @param noteIndex of the person in the filtered person list to edit the note + * @param note of the person to be updated to + */ + public EditNoteCommand(Index personIndex, Index noteIndex, Note note) { + requireAllNonNull(personIndex, note, noteIndex); + + this.personIndex = personIndex; + this.noteIndex = noteIndex; + this.note = note; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (personIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(personIndex.getZeroBased()); + + ArrayList notesList = new ArrayList<>(personToEdit.getNotes()); + + if (notesList.contains(note)) { + throw new CommandException(DUPLICATE_MESSAGE_CONSTRAINTS); + } else if (noteIndex.getZeroBased() >= notesList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_NOTE_INDEX); + } + + Person editedPerson = updateNote(personToEdit, notesList); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + model.getFocusedPerson().set(editedPerson); + + return new CommandResult(String.format(generateSuccessMessage(editedPerson), personIndex.getOneBased()), + false, false); + } + + /** + * Generates a command execution success message the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + return String.format(MESSAGE_EDIT_NOTE_SUCCESS, Messages.format(personToEdit)); + } + + /** + * Updates the notes of the given person with the given note + * {@code personToEdit, notesToEdit}. + */ + private Person updateNote(Person personToEdit, ArrayList notesToEdit) { + // Convert Set to List for indexed access + List notesList = new ArrayList<>(notesToEdit); + notesList.set(noteIndex.getZeroBased(), note); + ArrayList editedNotes = new ArrayList<>(notesList); + + return new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), personToEdit.getTags(), editedNotes); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditNoteCommand otherEditNotesCommand)) { + return false; + } + + return personIndex.equals(otherEditNotesCommand.personIndex) + && noteIndex.equals(otherEditNotesCommand.noteIndex) + && note.equals(otherEditNotesCommand.note); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/bizbook/logic/commands/ExitCommand.java similarity index 62% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/bizbook/logic/commands/ExitCommand.java index 3dd85a8ba90..121163dbf49 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/bizbook/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; -import seedu.address.model.Model; +import bizbook.model.Model; /** * Terminates the program. @@ -8,6 +8,10 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Exits the app.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; diff --git a/src/main/java/bizbook/logic/commands/ExportCommand.java b/src/main/java/bizbook/logic/commands/ExportCommand.java new file mode 100644 index 00000000000..b161fd07229 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/ExportCommand.java @@ -0,0 +1,78 @@ +package bizbook.logic.commands; + +import static bizbook.logic.parser.CliSyntax.PREFIX_FILE; +import static java.util.Objects.requireNonNull; + +import java.io.IOException; + +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.commands.exporter.Exporter; +import bizbook.logic.commands.exporter.FileType; +import bizbook.logic.commands.exporter.exceptions.InvalidAddressBookException; +import bizbook.model.Model; + +/** + * Exports contact list to a csv file. + */ +public class ExportCommand extends Command { + + public static final String COMMAND_WORD = "export"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exports the whole contact list " + + "to a specified file type. Only CSV and VCF are supported.\n" + + "Parameters: " + PREFIX_FILE + "FILETYPE\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_FILE + "csv"; + + public static final String MESSAGE_SUCCESS = "Contact list successfully exported to a %1$s file"; + public static final String MESSAGE_FAILED_TO_SAVE = "Unable to export contact list to a %1$s file" + + ", please ensure that the file is closed before exporting"; + + private final FileType fileType; + + /** + * Creates an ExportCsvCommand. + * + * @param fileType to export the data into. + */ + public ExportCommand(FileType fileType) { + requireNonNull(fileType); + this.fileType = fileType; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + try { + Exporter exporter = fileType.exporter(model.getUserPrefs()); + exporter.exportAddressBook(model.getAddressBook()); + } catch (IOException io) { + throw new CommandException(String.format(MESSAGE_FAILED_TO_SAVE, fileType)); + } catch (InvalidAddressBookException iabe) { + throw new CommandException(iabe.getMessage()); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, fileType)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ExportCommand)) { + return false; + } + + ExportCommand otherExportCommand = (ExportCommand) other; + return fileType.equals(otherExportCommand.fileType); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("fileType", fileType) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/bizbook/logic/commands/FindCommand.java similarity index 87% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/bizbook/logic/commands/FindCommand.java index 72b9eddd3a7..80268066a72 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/bizbook/logic/commands/FindCommand.java @@ -1,11 +1,11 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.model.Model; +import bizbook.model.person.NameContainsKeywordsPredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/bizbook/logic/commands/HelpCommand.java similarity index 88% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/bizbook/logic/commands/HelpCommand.java index bf824f91bd0..5414a378dfc 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/bizbook/logic/commands/HelpCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; -import seedu.address.model.Model; +import bizbook.model.Model; /** * Format full help instructions for every command for display. diff --git a/src/main/java/bizbook/logic/commands/ImportCommand.java b/src/main/java/bizbook/logic/commands/ImportCommand.java new file mode 100644 index 00000000000..2ae359a0a18 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/ImportCommand.java @@ -0,0 +1,89 @@ +package bizbook.logic.commands; + +import static bizbook.logic.parser.CliSyntax.PREFIX_FILE; +import static bizbook.logic.parser.CliSyntax.PREFIX_PATH; +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; + +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.commands.exporter.FileType; +import bizbook.logic.commands.exporter.Importer; +import bizbook.logic.commands.exporter.exceptions.InvalidFileException; +import bizbook.model.Model; + +/** + * Imports contact list to a csv file. + */ +public class ImportCommand extends Command { + + public static final String COMMAND_WORD = "import"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports contacts from a file into the address book." + + " Only VCF is supported.\n" + + "Parameters: " + PREFIX_FILE + "FILETYPE " + PREFIX_PATH + "PATH\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_FILE + "vcf " + + PREFIX_PATH + "./path/to/contacts.vcf"; + + public static final String MESSAGE_SUCCESS = "%1$d contacts were successfully imported from file"; + public static final String MESSAGE_FAILED_TO_LOAD = "Unable to find or load the file to import the contact list"; + + private final FileType fileType; + private final Path path; + + /** + * Creates an ImportCommand. + * + * @param fileType of the file at the path. + * @param path to import the data from. + */ + public ImportCommand(FileType fileType, Path path) { + requireNonNull(fileType); + requireNonNull(path); + + this.fileType = fileType; + this.path = path; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + try { + Importer importer = fileType.importer(); + model.setAddressBook(importer.importAddressBook(path)); + } catch (IOException io) { + throw new CommandException(String.format(MESSAGE_FAILED_TO_LOAD)); + } catch (InvalidFileException ife) { + throw new CommandException(ife.getMessage()); + } + + int numContacts = model.getAddressBook().getPersonList().size(); + return new CommandResult(String.format(MESSAGE_SUCCESS, numContacts)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ImportCommand)) { + return false; + } + + ImportCommand otherExportCommand = (ImportCommand) other; + return fileType.equals(otherExportCommand.fileType) + && path.equals(otherExportCommand.path); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("fileType", fileType) + .add("path", path) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/bizbook/logic/commands/ListCommand.java similarity index 60% rename from src/main/java/seedu/address/logic/commands/ListCommand.java rename to src/main/java/bizbook/logic/commands/ListCommand.java index 84be6ad2596..6d29f41a5d3 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/bizbook/logic/commands/ListCommand.java @@ -1,9 +1,9 @@ -package seedu.address.logic.commands; +package bizbook.logic.commands; +import static bizbook.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import seedu.address.model.Model; +import bizbook.model.Model; /** * Lists all persons in the address book to the user. @@ -11,6 +11,10 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows a list of all persons in the address book.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; public static final String MESSAGE_SUCCESS = "Listed all persons"; diff --git a/src/main/java/bizbook/logic/commands/PinCommand.java b/src/main/java/bizbook/logic/commands/PinCommand.java new file mode 100644 index 00000000000..fde2bc9486a --- /dev/null +++ b/src/main/java/bizbook/logic/commands/PinCommand.java @@ -0,0 +1,81 @@ +package bizbook.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Person; + +/** + * Pins a person in address book to a pinned list. + */ +public class PinCommand extends Command { + public static final String COMMAND_WORD = "pin"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Pins the person identified by the index number.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_PIN_PERSON_SUCCESS = "Pinned Person: %1$s"; + public static final String MESSAGE_ALREADY_PINNED = "This person already pinned"; + + private final Index targetIndex; + + /** + * Creates a PinCommand to pin the specified person. + * + * @param targetIndex of the person in the filtered person list to be pinned + */ + public PinCommand(Index targetIndex) { + assert targetIndex != null; + 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 personToPin = lastShownList.get(targetIndex.getZeroBased()); + + if (model.isPinned(personToPin)) { + throw new CommandException(MESSAGE_ALREADY_PINNED); + } + + model.pinPerson(personToPin); + CommandResult commandResult = new CommandResult(String.format(MESSAGE_PIN_PERSON_SUCCESS, + Messages.formatShort(personToPin)), false, false); + return commandResult; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PinCommand)) { + return false; + } + + PinCommand otherPinCommand = (PinCommand) other; + return targetIndex.equals(otherPinCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/bizbook/logic/commands/RedoCommand.java b/src/main/java/bizbook/logic/commands/RedoCommand.java new file mode 100644 index 00000000000..bce48293606 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/RedoCommand.java @@ -0,0 +1,43 @@ +package bizbook.logic.commands; + +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; + +/** + * Reverts the changes made by the undo function. + */ +public class RedoCommand extends Command { + public static final String COMMAND_WORD = "redo"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Redoes the previous undo command that was executed.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_REDO_SUCCESS = "Changes has been reverted!"; + public static final String MESSAGE_REDO_FAILURE = "Unable revert changes because " + + "there is no newer version to revert to!"; + + @Override + public CommandResult execute(Model model) throws CommandException { + + if (!model.canRedo()) { + throw new CommandException(MESSAGE_REDO_FAILURE); + } + + model.setFocusPerson(null); + model.redoAddressBookVersion(); + return new CommandResult(MESSAGE_REDO_SUCCESS); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof RedoCommand)) { + return false; + } + return true; + } +} diff --git a/src/main/java/bizbook/logic/commands/ToggleCommand.java b/src/main/java/bizbook/logic/commands/ToggleCommand.java new file mode 100644 index 00000000000..fd5ba176e87 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/ToggleCommand.java @@ -0,0 +1,41 @@ +package bizbook.logic.commands; + +import static java.util.Objects.requireNonNull; + +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; + +/** + * Toggle system between light and dark mode. + */ +public class ToggleCommand extends Command { + + public static final String COMMAND_WORD = "toggle"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Toggles between " + + "light mode and dark mode.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_TOGGLE_SUCCESS = "Toggled theme successfully!"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + return new CommandResult(MESSAGE_TOGGLE_SUCCESS, false, false, true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ToggleCommand)) { + return false; + } + return true; + } +} diff --git a/src/main/java/bizbook/logic/commands/UndoCommand.java b/src/main/java/bizbook/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..59dc01c1078 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/UndoCommand.java @@ -0,0 +1,45 @@ +package bizbook.logic.commands; + +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; + +/** + * Reverts the changes made to the address book. + */ +public class UndoCommand extends Command { + + public static final String COMMAND_WORD = "undo"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Undoes the previous command that was executed.\n" + + "Parameters: None\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_UNDO_SUCCESS = "Changes has been reverted!"; + public static final String MESSAGE_UNDO_FAILURE = "Unable revert changes because " + + "there is no older version to revert to!"; + + @Override + public CommandResult execute(Model model) throws CommandException { + + if (!model.canUndo()) { + throw new CommandException(MESSAGE_UNDO_FAILURE); + } + + model.setFocusPerson(null); + model.revertAddressBookVersion(); + return new CommandResult(MESSAGE_UNDO_SUCCESS); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UndoCommand)) { + return false; + } + return true; + } +} diff --git a/src/main/java/bizbook/logic/commands/UnpinCommand.java b/src/main/java/bizbook/logic/commands/UnpinCommand.java new file mode 100644 index 00000000000..16aba22d33b --- /dev/null +++ b/src/main/java/bizbook/logic/commands/UnpinCommand.java @@ -0,0 +1,74 @@ +package bizbook.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Person; + +/** + * Unpins a person in pinned list. + */ +public class UnpinCommand extends Command { + public static final String COMMAND_WORD = "unpin"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Unpins the person identified by the index number.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNPIN_PERSON_SUCCESS = "Unpinned Person: %1$s"; + + private final Index targetIndex; + + /** + * Creates a Unpin Command to unpin the specified person. + * + * @param targetIndex of the person in the pinned person list. + */ + public UnpinCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getPinnedPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToUnpin = lastShownList.get(targetIndex.getZeroBased()); + + model.unpinPerson(personToUnpin); + CommandResult commandResult = new CommandResult(String.format(MESSAGE_UNPIN_PERSON_SUCCESS, + Messages.formatShort(personToUnpin)), false, false); + return commandResult; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnpinCommand)) { + return false; + } + + UnpinCommand otherUnpinCommand = (UnpinCommand) other; + return targetIndex.equals(otherUnpinCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/bizbook/logic/commands/ViewCommand.java b/src/main/java/bizbook/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..82809522df3 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/ViewCommand.java @@ -0,0 +1,72 @@ +package bizbook.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import bizbook.commons.core.index.Index; +import bizbook.commons.util.ToStringBuilder; +import bizbook.logic.Messages; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.model.Model; +import bizbook.model.person.Person; + +/** + * Views the details of a specfied person using it's displayed index from the address book. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": View more details of " + + "a particular person in the contact list.\n" + + "Parameters: INDEX (Must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_VIEW_PERSON_SUCCESS = "Fetched details of person: %1$s"; + + private final Index targetIndex; + + public ViewCommand(Index index) { + this.targetIndex = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List personList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= personList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person person = personList.get(targetIndex.getZeroBased()); + model.setFocusPerson(person); + + return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewCommand)) { + return false; + } + + ViewCommand otherViewCommand = (ViewCommand) other; + return this.targetIndex.equals(otherViewCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/bizbook/logic/commands/exceptions/CommandException.java similarity index 89% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/bizbook/logic/commands/exceptions/CommandException.java index a16bd14f2cd..25e1230cdb7 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/bizbook/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package bizbook.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/bizbook/logic/commands/exporter/CsvExporter.java b/src/main/java/bizbook/logic/commands/exporter/CsvExporter.java new file mode 100644 index 00000000000..e6f1f958aa6 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/CsvExporter.java @@ -0,0 +1,83 @@ +package bizbook.logic.commands.exporter; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import bizbook.commons.util.FileUtil; +import bizbook.logic.commands.exporter.exceptions.InvalidAddressBookException; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.person.Note; +import bizbook.model.person.Person; + +/** + * Represents a class that can export to CSV + */ +public class CsvExporter implements Exporter { + private static final String CSV_HEADERS = "Name,Phone No,Email,Address,Tags,Notes"; + + private final Path exportSubPath = Paths.get("bizbook.csv"); + private final ReadOnlyUserPrefs userPrefs; + + /** + * Constructs a {@code VcfExporter} class configured with {@code UserPrefs} + */ + public CsvExporter(ReadOnlyUserPrefs userPrefs) { + this.userPrefs = userPrefs; + } + + @Override + public void exportAddressBook(ReadOnlyAddressBook addressBook) throws IOException, InvalidAddressBookException { + if (addressBook.getPersonList().isEmpty()) { + throw new InvalidAddressBookException(MESSAGE_EMPTY_ADDRESS_BOOK); + } + + Path exportPath = getExportPath(); + FileUtil.createIfMissing(exportPath); + + StringJoiner sj = new StringJoiner(System.lineSeparator()); + sj.add(CSV_HEADERS); + + addressBook.getPersonList() + .forEach(person -> sj.add(convertToCsv(person))); + + FileUtil.writeToFile(exportPath, sj + System.lineSeparator()); + } + + /** + * Converts a {@code Person} object into csv format. + * + * @param person to be encoded into a csv format. + * @return A csv representation of the {@Code Person} object. + */ + private String convertToCsv(Person person) { + StringJoiner sj = new StringJoiner(","); + + sj.add(person.getName().fullName); + sj.add(person.getPhone().value); + sj.add(person.getEmail().value); + + // Prevent excel from separating entries due to commas + String address = "\"" + person.getAddress().value + "\""; + sj.add(address); + + String tags = person.getTags().stream() + .map(tag -> tag.tagName).collect(Collectors.joining(",")); + + String notes = person.getNotes().stream() + .map(Note::getNote).collect(Collectors.joining(",")); + + sj.add("\"" + tags + "\""); + sj.add("\"" + notes + "\""); + + return sj.toString(); + } + + @Override + public Path getExportPath() { + return userPrefs.getExportDirectoryPath().resolve(exportSubPath); + } +} diff --git a/src/main/java/bizbook/logic/commands/exporter/Exporter.java b/src/main/java/bizbook/logic/commands/exporter/Exporter.java new file mode 100644 index 00000000000..e7aabb007b3 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/Exporter.java @@ -0,0 +1,24 @@ +package bizbook.logic.commands.exporter; + +import java.io.IOException; +import java.nio.file.Path; + +import bizbook.logic.commands.exporter.exceptions.InvalidAddressBookException; +import bizbook.model.ReadOnlyAddressBook; + +/** + * Represents a class that can export an address book to a particular format + */ +public interface Exporter { + String MESSAGE_EMPTY_ADDRESS_BOOK = "Address book is empty. Consider adding a person first."; + + /** + * Exports the address book + */ + void exportAddressBook(ReadOnlyAddressBook addressBook) throws IOException, InvalidAddressBookException; + + /** + * Returns the path that the addressbook will be exported to + */ + Path getExportPath(); +} diff --git a/src/main/java/bizbook/logic/commands/exporter/FileType.java b/src/main/java/bizbook/logic/commands/exporter/FileType.java new file mode 100644 index 00000000000..5418b4936e4 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/FileType.java @@ -0,0 +1,55 @@ +package bizbook.logic.commands.exporter; + +import bizbook.model.ReadOnlyUserPrefs; + +/** + * Represents a file type that can be exported + */ +public enum FileType { + CSV, + VCF; + + public static final String MESSAGE_CONSTRAINTS = "File type should either be CSV or VCF"; + public static final String MESSAGE_NOT_SUPPORTED = "File type is not supported."; + + /** + * Creates an {@link Exporter} for this file type + */ + public Exporter exporter(ReadOnlyUserPrefs userPrefs) { + return switch (this) { + case CSV -> new CsvExporter(userPrefs); + case VCF -> new VcfExporter(userPrefs); + default -> throw new UnsupportedOperationException(MESSAGE_NOT_SUPPORTED); + }; + } + + /** + * Returns true if this file type has an {@link Exporter} + */ + public boolean hasExporter() { + return switch (this) { + case CSV, VCF -> true; + default -> false; + }; + } + + /** + * Creates an {@link Importer} for this file type + */ + public Importer importer() { + return switch (this) { + case VCF -> new VcfImporter(); + default -> throw new UnsupportedOperationException(MESSAGE_NOT_SUPPORTED); + }; + } + + /** + * Returns true if this file type has an {@link Importer} + */ + public boolean hasImporter() { + return switch (this) { + case VCF -> true; + default -> false; + }; + } +} diff --git a/src/main/java/bizbook/logic/commands/exporter/Importer.java b/src/main/java/bizbook/logic/commands/exporter/Importer.java new file mode 100644 index 00000000000..7ef4d847429 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/Importer.java @@ -0,0 +1,19 @@ +package bizbook.logic.commands.exporter; + +import java.io.IOException; +import java.nio.file.Path; + +import bizbook.logic.commands.exporter.exceptions.InvalidFileException; +import bizbook.model.AddressBook; + +/** + * Represents a class that can import an address book from a particular format + */ +public interface Importer { + String MESSAGE_EMPTY_FILE = "There are no people to import from the file."; + String MESSAGE_INVALID_FORMAT = "File is not in the proper format or the file is corrupted."; + /** + * Imports an address book + */ + AddressBook importAddressBook(Path filePath) throws IOException, InvalidFileException; +} diff --git a/src/main/java/bizbook/logic/commands/exporter/VcfExporter.java b/src/main/java/bizbook/logic/commands/exporter/VcfExporter.java new file mode 100644 index 00000000000..41284afaed6 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/VcfExporter.java @@ -0,0 +1,83 @@ +package bizbook.logic.commands.exporter; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import bizbook.commons.util.FileUtil; +import bizbook.logic.commands.exporter.exceptions.InvalidAddressBookException; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.person.Person; +import ezvcard.Ezvcard; +import ezvcard.VCard; +import ezvcard.property.Address; +import ezvcard.property.Categories; +import ezvcard.property.StructuredName; + +/** + * Represents a class that can export to VCF + */ +public class VcfExporter implements Exporter { + private final Path exportSubPath = Paths.get("bizbook.vcf"); + private final ReadOnlyUserPrefs userPrefs; + + /** + * Constructs a {@code VcfExporter} class configured with {@code ReadOnlyUserPrefs} + */ + public VcfExporter(ReadOnlyUserPrefs userPrefs) { + this.userPrefs = userPrefs; + } + + @Override + public void exportAddressBook(ReadOnlyAddressBook addressBook) throws IOException, InvalidAddressBookException { + if (addressBook.getPersonList().isEmpty()) { + throw new InvalidAddressBookException(MESSAGE_EMPTY_ADDRESS_BOOK); + } + + List vCards = addressBook.getPersonList() + .parallelStream().map(this::convertToVcf).toList(); + String fileContent = Ezvcard.write(vCards).go(); + + Path exportPath = getExportPath(); + FileUtil.createIfMissing(exportPath); + FileUtil.writeToFile(exportPath, fileContent); + } + + private VCard convertToVcf(Person person) { + // Convert attributes to vCard representable properties + // Since we have no indication of which parts of the name is which, set + // the given name as full name + StructuredName structuredName = new StructuredName(); + structuredName.setGiven(person.getName().fullName); + + Address address = new Address(); + address.setStreetAddress(person.getAddress().value); + + Categories categories = new Categories(); + person.getTags().forEach(tag -> categories.getValues().add(tag.tagName)); + + // Create vCard + VCard vCard = new VCard(); + vCard.setFormattedName(person.getName().fullName); + vCard.setStructuredName(structuredName); + vCard.addTelephoneNumber(person.getPhone().value); + vCard.addEmail(person.getEmail().value); + vCard.addAddress(address); + + if (!categories.getValues().isEmpty()) { + vCard.setCategories(categories); + } + + person.getNotes() + .forEach(note -> vCard.addNote(note.getNote())); + + return vCard; + } + + @Override + public Path getExportPath() { + return userPrefs.getExportDirectoryPath().resolve(exportSubPath); + } +} diff --git a/src/main/java/bizbook/logic/commands/exporter/VcfImporter.java b/src/main/java/bizbook/logic/commands/exporter/VcfImporter.java new file mode 100644 index 00000000000..c3d4cdc4a3b --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/VcfImporter.java @@ -0,0 +1,180 @@ +package bizbook.logic.commands.exporter; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import bizbook.commons.util.FileUtil; +import bizbook.logic.commands.exporter.exceptions.InvalidFileException; +import bizbook.model.AddressBook; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; +import ezvcard.Ezvcard; +import ezvcard.VCard; +import ezvcard.property.Categories; +import ezvcard.property.FormattedName; +import ezvcard.property.Telephone; + +/** + * Represents a class that can import an address book from a VCF file + */ +public class VcfImporter implements Importer { + public static final String MESSAGE_MISSING_INFORMATION = "A vCard inside the file is missing information needed to " + + "make a person."; + public static final String MESSAGE_INVALID_INFORMATION = "A vCard inside the file contains information that " + + "cannot be converted to make a person."; + + // ================ Validation methods ============================== + private void validateNotEmpty(List vCards) throws InvalidFileException { + requireNonNull(vCards); + + if (vCards.isEmpty()) { + throw new InvalidFileException(MESSAGE_EMPTY_FILE); + } + } + + private void validateVCard(VCard vCard) throws InvalidFileException { + requireNonNull(vCard); + + if (!vCard.validate(vCard.getVersion()).isEmpty()) { + throw new InvalidFileException(MESSAGE_INVALID_FORMAT); + } + } + + // ================ Import methods ============================== + @Override + public AddressBook importAddressBook(Path filePath) throws IOException, InvalidFileException { + requireNonNull(filePath); + + List vCards = parseVCardFromFile(filePath); + return convertToAddressBook(vCards); + } + + /** + * Loads and parses the vCards from a file path. + * + * @param filePath path to the VCF file + * @return a list of vCards + * @throws IOException when the file could not be loaded + * @throws InvalidFileException when the file is empty + */ + private List parseVCardFromFile(Path filePath) throws IOException, InvalidFileException { + String contents = FileUtil.readFromFile(filePath); + List vCards = Ezvcard.parse(contents).all(); + + validateNotEmpty(vCards); + return vCards; + } + + /** + * Converts a list of vCards to an AddressBook + * + * @param vCards list of vCards + * @return address book with the vCards + * @throws InvalidFileException when any vCard cannot be converted + */ + private AddressBook convertToAddressBook(List vCards) throws InvalidFileException { + requireNonNull(vCards); + + AddressBook addressBook = new AddressBook(); + for (VCard vCard : vCards) { + addressBook.addPerson(convertToPerson(vCard)); + } + + return addressBook; + } + + /** + * Converts a {@link VCard} to a {@link Person} + * + * @throws InvalidFileException when the vCard cannot be converted + */ + private Person convertToPerson(VCard vCard) throws InvalidFileException { + validateVCard(vCard); + + PersonVCard personVCard = new PersonVCard(vCard); + + try { + return new Person( + personVCard.getName(), + personVCard.getPhone(), + personVCard.getEmail(), + personVCard.getAddress(), + personVCard.getTags(), + personVCard.getNotes() + ); + } catch (IllegalArgumentException iae) { + throw new InvalidFileException(MESSAGE_INVALID_INFORMATION); + } + } + + private static class PersonVCard { + private final VCard vCard; + PersonVCard(VCard vCard) { + this.vCard = vCard; + } + + private void validateInfoNotNull(Object object) throws InvalidFileException { + if (object == null) { + throw new InvalidFileException(MESSAGE_MISSING_INFORMATION); + } + } + + private void validateNotEmpty(List objects) throws InvalidFileException { + if (objects.isEmpty()) { + throw new InvalidFileException(MESSAGE_MISSING_INFORMATION); + } + } + + Name getName() throws InvalidFileException { + FormattedName name = vCard.getFormattedName(); + validateInfoNotNull(name); + return new Name(name.getValue()); + } + + Phone getPhone() throws InvalidFileException { + List numbers = vCard.getTelephoneNumbers(); + validateNotEmpty(numbers); + return new Phone(numbers.get(0).getText()); + } + + Email getEmail() throws InvalidFileException { + List emails = vCard.getEmails(); + validateNotEmpty(emails); + return new Email(emails.get(0).getValue()); + } + + Address getAddress() throws InvalidFileException { + List addresses = vCard.getAddresses(); + validateNotEmpty(addresses); + return new Address(addresses.get(0).getStreetAddressFull()); + } + + Set getTags() { + Categories categories = vCard.getCategories(); + if (categories == null) { + return new LinkedHashSet<>(); + } + + return vCard.getCategories().getValues().parallelStream() + .map(Tag::new) + .collect(Collectors.toSet()); + } + + ArrayList getNotes() { + return vCard.getNotes().parallelStream() + .map(note -> new Note(note.getValue())).collect(Collectors.toCollection(ArrayList::new)); + } + } +} diff --git a/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidAddressBookException.java b/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidAddressBookException.java new file mode 100644 index 00000000000..4047c451bdc --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidAddressBookException.java @@ -0,0 +1,13 @@ +package bizbook.logic.commands.exporter.exceptions; + +/** + * Represents an error that is caused because something is wrong with the {@link bizbook.model.AddressBook}. + */ +public class InvalidAddressBookException extends Exception { + /** + * Constructs a new {@code InvalidAddressBookException} with the specified detail {@code message}. + */ + public InvalidAddressBookException(String message) { + super(message); + } +} diff --git a/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidFileException.java b/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidFileException.java new file mode 100644 index 00000000000..1a357a91f35 --- /dev/null +++ b/src/main/java/bizbook/logic/commands/exporter/exceptions/InvalidFileException.java @@ -0,0 +1,13 @@ +package bizbook.logic.commands.exporter.exceptions; + +/** + * Represents an error that is caused because something is wrong with the loaded file. + */ +public class InvalidFileException extends Exception { + /** + * Constructs a new {@code InvalidFileException} with the specified detail {@code message}. + */ + public InvalidFileException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/bizbook/logic/parser/AddCommandParser.java similarity index 68% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/bizbook/logic/parser/AddCommandParser.java index 4ff1a97ed77..029bbd4ec9f 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/bizbook/logic/parser/AddCommandParser.java @@ -1,23 +1,25 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static bizbook.logic.parser.CliSyntax.PREFIX_EMAIL; +import static bizbook.logic.parser.CliSyntax.PREFIX_NAME; +import static bizbook.logic.parser.CliSyntax.PREFIX_PHONE; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.ArrayList; import java.util.Set; import java.util.stream.Stream; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import bizbook.logic.commands.AddCommand; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; /** * Parses input arguments and creates a new AddCommand object @@ -27,6 +29,7 @@ public class AddCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { @@ -44,8 +47,10 @@ public AddCommand parse(String args) throws ParseException { Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + // Set notes to a new empty arrayList first as it is optional + ArrayList notes = new ArrayList<>(); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, address, tagList, notes); return new AddCommand(person); } diff --git a/src/main/java/bizbook/logic/parser/AddNotesCommandParser.java b/src/main/java/bizbook/logic/parser/AddNotesCommandParser.java new file mode 100644 index 00000000000..48e262cf13f --- /dev/null +++ b/src/main/java/bizbook/logic/parser/AddNotesCommandParser.java @@ -0,0 +1,43 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_NOTES; +import static java.util.Objects.requireNonNull; + +import bizbook.commons.core.index.Index; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.logic.commands.AddNoteCommand; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.person.Note; + +/** + * Parses input arguments and creates a new AddNotesCommand object + */ +public class AddNotesCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddNotesCommand + * and returns a AddNotesCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddNoteCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_NOTES); + + Index index; + Note note; + try { + String noteName = argMultimap.getValue(PREFIX_NOTES).orElse(""); + note = ParserUtil.parseNote(noteName); + + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddNoteCommand.MESSAGE_USAGE), ive); + } + + return new AddNoteCommand(index, note); + } +} diff --git a/src/main/java/bizbook/logic/parser/AddressBookParser.java b/src/main/java/bizbook/logic/parser/AddressBookParser.java new file mode 100644 index 00000000000..2d4aa1e1ad2 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/AddressBookParser.java @@ -0,0 +1,134 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import bizbook.commons.core.LogsCenter; +import bizbook.logic.commands.AddCommand; +import bizbook.logic.commands.AddNoteCommand; +import bizbook.logic.commands.ClearCommand; +import bizbook.logic.commands.Command; +import bizbook.logic.commands.DeleteCommand; +import bizbook.logic.commands.DeleteNoteCommand; +import bizbook.logic.commands.DeleteTagCommand; +import bizbook.logic.commands.EditCommand; +import bizbook.logic.commands.EditNoteCommand; +import bizbook.logic.commands.ExitCommand; +import bizbook.logic.commands.ExportCommand; +import bizbook.logic.commands.FindCommand; +import bizbook.logic.commands.HelpCommand; +import bizbook.logic.commands.ImportCommand; +import bizbook.logic.commands.ListCommand; +import bizbook.logic.commands.PinCommand; +import bizbook.logic.commands.RedoCommand; +import bizbook.logic.commands.ToggleCommand; +import bizbook.logic.commands.UndoCommand; +import bizbook.logic.commands.UnpinCommand; +import bizbook.logic.commands.ViewCommand; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class AddressBookParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord").toLowerCase(); + final String arguments = matcher.group("arguments"); + + // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) + // log messages such as the one below. + // Lower level log messages are used sparingly to minimize noise in the code. + logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); + + switch (commandWord) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case DeleteTagCommand.COMMAND_WORD: + return new DeleteTagCommandParser().parse(arguments); + + case AddNoteCommand.COMMAND_WORD: + return new AddNotesCommandParser().parse(arguments); + + case EditNoteCommand.COMMAND_WORD: + return new EditNotesCommandParser().parse(arguments); + + case DeleteNoteCommand.COMMAND_WORD: + return new DeleteNotesCommandParser().parse(arguments); + + case PinCommand.COMMAND_WORD: + return new PinCommandParser().parse(arguments); + + case UnpinCommand.COMMAND_WORD: + return new UnpinCommandParser().parse(arguments); + + + case ExportCommand.COMMAND_WORD: + return new ExportCommandParser().parse(arguments); + + case ImportCommand.COMMAND_WORD: + return new ImportCommandParser().parse(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(); + + case ToggleCommand.COMMAND_WORD: + return new ToggleCommand(); + + default: + logger.finer("This user input caused a ParseException: " + userInput); + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/bizbook/logic/parser/ArgumentMultimap.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/bizbook/logic/parser/ArgumentMultimap.java index 21e26887a83..de4e3fa01ab 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/bizbook/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; import java.util.ArrayList; import java.util.HashMap; @@ -7,8 +7,8 @@ import java.util.Optional; import java.util.stream.Stream; -import seedu.address.logic.Messages; -import seedu.address.logic.parser.exceptions.ParseException; +import bizbook.logic.Messages; +import bizbook.logic.parser.exceptions.ParseException; /** * Stores mapping of prefixes to their respective arguments. diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/bizbook/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/bizbook/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..fa9238663d3 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/bizbook/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/bizbook/logic/parser/CliSyntax.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/CliSyntax.java rename to src/main/java/bizbook/logic/parser/CliSyntax.java index 75b1a9bf119..0b7fff66636 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/bizbook/logic/parser/CliSyntax.java @@ -1,7 +1,8 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; /** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + * Contains Command Line Interface (CLI) syntax definitions common to multiple + * commands */ public class CliSyntax { @@ -11,5 +12,8 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_NOTES = new Prefix("n/"); + public static final Prefix PREFIX_NOTES_INDEX = new Prefix("i/"); + public static final Prefix PREFIX_FILE = new Prefix("f/"); + public static final Prefix PREFIX_PATH = new Prefix("p/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/bizbook/logic/parser/DeleteCommandParser.java similarity index 74% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/bizbook/logic/parser/DeleteCommandParser.java index 3527fe76a3e..6cc092b4e00 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/bizbook/logic/parser/DeleteCommandParser.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; +import bizbook.commons.core.index.Index; +import bizbook.logic.commands.DeleteCommand; +import bizbook.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object @@ -14,6 +14,7 @@ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns a DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { diff --git a/src/main/java/bizbook/logic/parser/DeleteNotesCommandParser.java b/src/main/java/bizbook/logic/parser/DeleteNotesCommandParser.java new file mode 100644 index 00000000000..3e2343d02e6 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/DeleteNotesCommandParser.java @@ -0,0 +1,43 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_NOTES_INDEX; +import static java.util.Objects.requireNonNull; + +import bizbook.commons.core.index.Index; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.logic.commands.DeleteNoteCommand; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteNotesCommand object + */ +public class DeleteNotesCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteNotesCommand + * and returns a DeleteNotesCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteNoteCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_NOTES_INDEX); + + Index index; + Index noteIndex; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + + String noteIndexName = argMultimap.getValue(PREFIX_NOTES_INDEX).orElse(""); + noteIndex = ParserUtil.parseIndex(noteIndexName); + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteNoteCommand.MESSAGE_USAGE), ive); + } + + return new DeleteNoteCommand(index, noteIndex); + } +} diff --git a/src/main/java/bizbook/logic/parser/DeleteTagCommandParser.java b/src/main/java/bizbook/logic/parser/DeleteTagCommandParser.java new file mode 100644 index 00000000000..3696a12b92d --- /dev/null +++ b/src/main/java/bizbook/logic/parser/DeleteTagCommandParser.java @@ -0,0 +1,45 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; +import static java.util.Objects.requireNonNull; + +import bizbook.commons.core.index.Index; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.logic.commands.DeleteTagCommand; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.tag.Tag; + +/** + * Parses input arguments and returns a new DeleteTagCommand object + */ +public class DeleteTagCommandParser implements Parser { + + /** + * Parses the given {@code userInput} of arguments in the context of the DeleteTagCommand + * and returns a DeleteTagCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTagCommand parse(String userInput) throws ParseException { + + requireNonNull(userInput); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput, PREFIX_TAG); + + Index personIndex; + Tag tagToDelete; + + try { + personIndex = ParserUtil.parseIndex(argMultimap.getPreamble()); + String tagName = argMultimap.getValue(PREFIX_TAG).orElse(""); + tagToDelete = ParserUtil.parseTag(tagName); + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteTagCommand.MESSAGE_USAGE), ive); + } + + return new DeleteTagCommand(personIndex, tagToDelete); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/bizbook/logic/parser/EditCommandParser.java similarity index 80% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/bizbook/logic/parser/EditCommandParser.java index 46b3309a78b..8f8f7f7e6c2 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/bizbook/logic/parser/EditCommandParser.java @@ -1,23 +1,23 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static bizbook.logic.parser.CliSyntax.PREFIX_EMAIL; +import static bizbook.logic.parser.CliSyntax.PREFIX_NAME; +import static bizbook.logic.parser.CliSyntax.PREFIX_PHONE; +import static bizbook.logic.parser.CliSyntax.PREFIX_TAG; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import bizbook.commons.core.index.Index; +import bizbook.logic.commands.EditCommand; +import bizbook.logic.commands.EditCommand.EditPersonDescriptor; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object @@ -27,6 +27,7 @@ public class EditCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { diff --git a/src/main/java/bizbook/logic/parser/EditNotesCommandParser.java b/src/main/java/bizbook/logic/parser/EditNotesCommandParser.java new file mode 100644 index 00000000000..ef4dac07818 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/EditNotesCommandParser.java @@ -0,0 +1,48 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.parser.CliSyntax.PREFIX_NOTES; +import static bizbook.logic.parser.CliSyntax.PREFIX_NOTES_INDEX; +import static java.util.Objects.requireNonNull; + +import bizbook.commons.core.index.Index; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.logic.commands.EditNoteCommand; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.person.Note; + +/** + * Parses input arguments and returns a new EditNotesCommand object + */ +public class EditNotesCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditNotesCommand + * and returns a EditNotesCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditNoteCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_NOTES_INDEX, PREFIX_NOTES); + + Index personIndex; + Index noteIndex; + Note note; + try { + personIndex = ParserUtil.parseIndex(argMultimap.getPreamble()); + + String noteIndexName = argMultimap.getValue(PREFIX_NOTES_INDEX).orElse(""); + noteIndex = ParserUtil.parseIndex(noteIndexName); + String noteName = argMultimap.getValue(PREFIX_NOTES).orElse(""); + note = ParserUtil.parseNote(noteName); + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditNoteCommand.MESSAGE_USAGE), ive); + } + + return new EditNoteCommand(personIndex, noteIndex, note); + } +} diff --git a/src/main/java/bizbook/logic/parser/ExportCommandParser.java b/src/main/java/bizbook/logic/parser/ExportCommandParser.java new file mode 100644 index 00000000000..2033c80b826 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/ExportCommandParser.java @@ -0,0 +1,41 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.Messages.MESSAGE_UNSUPPORTED_FILE_TYPE; +import static bizbook.logic.parser.CliSyntax.PREFIX_FILE; +import static java.util.Objects.requireNonNull; + +import bizbook.logic.commands.ExportCommand; +import bizbook.logic.commands.exporter.FileType; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ExportCsvCommand object + */ +public class ExportCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ExportCsvCommand + * and returns a ExportCsvCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ExportCommand parse(String args) throws ParseException { + + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_FILE); + + if (argMultimap.getValue(PREFIX_FILE).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExportCommand.MESSAGE_USAGE)); + } + + FileType fileType = ParserUtil.parseFileType(argMultimap.getValue(PREFIX_FILE).get()); + + if (!fileType.hasExporter()) { + throw new ParseException( + String.format(MESSAGE_UNSUPPORTED_FILE_TYPE, ExportCommand.MESSAGE_USAGE)); + } + + return new ExportCommand(fileType); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/bizbook/logic/parser/FindCommandParser.java similarity index 75% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/bizbook/logic/parser/FindCommandParser.java index 2867bde857b..ea8ae2430e6 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/bizbook/logic/parser/FindCommandParser.java @@ -1,12 +1,12 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.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; +import bizbook.logic.commands.FindCommand; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.person.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object @@ -16,6 +16,7 @@ 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 { diff --git a/src/main/java/bizbook/logic/parser/ImportCommandParser.java b/src/main/java/bizbook/logic/parser/ImportCommandParser.java new file mode 100644 index 00000000000..36845215d70 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/ImportCommandParser.java @@ -0,0 +1,53 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static bizbook.logic.Messages.MESSAGE_INVALID_FILE_PATH; +import static bizbook.logic.Messages.MESSAGE_UNSUPPORTED_FILE_TYPE; +import static bizbook.logic.parser.CliSyntax.PREFIX_FILE; +import static bizbook.logic.parser.CliSyntax.PREFIX_PATH; +import static java.util.Objects.requireNonNull; + +import java.nio.file.InvalidPathException; +import java.nio.file.Path; + +import bizbook.logic.commands.ImportCommand; +import bizbook.logic.commands.exporter.FileType; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ImportCommand object + */ +public class ImportCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ImportCommand + * and returns a ImportCsvCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ImportCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_FILE, PREFIX_PATH); + + if (argMultimap.getValue(PREFIX_FILE).isEmpty() + || argMultimap.getValue(PREFIX_PATH).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ImportCommand.MESSAGE_USAGE)); + } + + FileType fileType = ParserUtil.parseFileType(argMultimap.getValue(PREFIX_FILE).get()); + String rawPath = argMultimap.getValue(PREFIX_PATH).get(); + Path path; + try { + path = Path.of(rawPath); + } catch (InvalidPathException ipe) { + throw new ParseException(String.format(MESSAGE_INVALID_FILE_PATH, rawPath)); + } + + if (!fileType.hasImporter()) { + throw new ParseException( + String.format(MESSAGE_UNSUPPORTED_FILE_TYPE, ImportCommand.MESSAGE_USAGE)); + } + + return new ImportCommand(fileType, path); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/bizbook/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/bizbook/logic/parser/Parser.java index d6551ad8e3f..921520db709 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/bizbook/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import bizbook.logic.commands.Command; +import bizbook.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. @@ -10,6 +10,7 @@ public interface Parser { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/bizbook/logic/parser/ParserUtil.java similarity index 70% rename from src/main/java/seedu/address/logic/parser/ParserUtil.java rename to src/main/java/bizbook/logic/parser/ParserUtil.java index b117acb9c55..cc1ec121a1e 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/bizbook/logic/parser/ParserUtil.java @@ -1,19 +1,21 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; import static java.util.Objects.requireNonNull; import java.util.Collection; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import bizbook.commons.core.index.Index; +import bizbook.commons.util.StringUtil; +import bizbook.logic.commands.exporter.FileType; +import bizbook.logic.parser.exceptions.ParseException; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -25,6 +27,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -115,10 +118,42 @@ public static Tag parseTag(String tag) throws ParseException { */ public static Set parseTags(Collection tags) throws ParseException { requireNonNull(tags); - final Set tagSet = new HashSet<>(); + final Set tagSet = new LinkedHashSet<>(); for (String tagName : tags) { tagSet.add(parseTag(tagName)); } return tagSet; } + + /** + * Parses a {@code String note} into a {@code Note}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code note} is invalid. + */ + public static Note parseNote(String note) throws ParseException { + requireNonNull(note); + String trimmedNote = note.trim(); + if (!Note.isValidNoteName(trimmedNote)) { + throw new ParseException(Note.MESSAGE_CONSTRAINTS); + } + return new Note(trimmedNote); + } + + /** + * Parses a {@code String fileType} into a valid {@code fileType}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code fileType} is invalid. + */ + public static FileType parseFileType(String fileType) throws ParseException { + requireNonNull(fileType); + String trimmedFileType = fileType.trim().toUpperCase(); + + try { + return FileType.valueOf(trimmedFileType); + } catch (IllegalArgumentException argex) { + throw new ParseException(FileType.MESSAGE_CONSTRAINTS); + } + } } diff --git a/src/main/java/bizbook/logic/parser/PinCommandParser.java b/src/main/java/bizbook/logic/parser/PinCommandParser.java new file mode 100644 index 00000000000..010dc97b316 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/PinCommandParser.java @@ -0,0 +1,29 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import bizbook.commons.core.index.Index; +import bizbook.logic.commands.PinCommand; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new PinCommand object + */ +public class PinCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PinCommand + * and return a PinCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public PinCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new PinCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PinCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/bizbook/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/bizbook/logic/parser/Prefix.java index 348b7686c8a..f2be0a0495c 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/bizbook/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package bizbook.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/bizbook/logic/parser/UnpinCommandParser.java b/src/main/java/bizbook/logic/parser/UnpinCommandParser.java new file mode 100644 index 00000000000..feb3096503a --- /dev/null +++ b/src/main/java/bizbook/logic/parser/UnpinCommandParser.java @@ -0,0 +1,29 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import bizbook.commons.core.index.Index; +import bizbook.logic.commands.UnpinCommand; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnpinCommand object + */ +public class UnpinCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnpinCommand + * and return a UnpinCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnpinCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnpinCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnpinCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/bizbook/logic/parser/ViewCommandParser.java b/src/main/java/bizbook/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..0a418dfd230 --- /dev/null +++ b/src/main/java/bizbook/logic/parser/ViewCommandParser.java @@ -0,0 +1,29 @@ +package bizbook.logic.parser; + +import static bizbook.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import bizbook.commons.core.index.Index; +import bizbook.logic.commands.ViewCommand; +import bizbook.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ViewCommand object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns a ViewCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ViewCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/bizbook/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/bizbook/logic/parser/exceptions/ParseException.java index 158a1a54c1c..cfaeb79a415 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/bizbook/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package bizbook.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import bizbook.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/bizbook/model/AddressBook.java similarity index 62% rename from src/main/java/seedu/address/model/AddressBook.java rename to src/main/java/bizbook/model/AddressBook.java index 73397161e84..6b6a3558b2d 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/bizbook/model/AddressBook.java @@ -1,13 +1,13 @@ -package seedu.address.model; +package bizbook.model; import static java.util.Objects.requireNonNull; import java.util.List; +import bizbook.commons.util.ToStringBuilder; +import bizbook.model.person.Person; +import bizbook.model.person.UniquePersonList; import javafx.collections.ObservableList; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; /** * Wraps all data at the address-book level @@ -16,6 +16,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniquePersonList pinnedPersons; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,6 +27,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + pinnedPersons = new UniquePersonList(); } public AddressBook() {} @@ -54,6 +56,8 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); + // Empty the pinned person list since we are reseting the addressbook + setPinnedPersons(newData.getPinnedPersonList()); setPersons(newData.getPersonList()); } @@ -84,6 +88,9 @@ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); persons.setPerson(target, editedPerson); + if (isPinned(target)) { + pinnedPersons.setPerson(target, editedPerson); + } } /** @@ -92,6 +99,52 @@ public void setPerson(Person target, Person editedPerson) { */ public void removePerson(Person key) { persons.remove(key); + if (isPinned(key)) { + removePinnedPerson(key); + } + } + + //=========== Pinned Person List Accessors =============================================================== + + /** + * Returns true if a person with the same identity as {@code person} exists in the pinned list. + */ + public boolean isPinned(Person person) { + requireNonNull(person); + return pinnedPersons.contains(person); + } + + /** + * Returns an unmodifiable view of the pinned person list. + */ + public ObservableList getPinnedPersonList() { + return pinnedPersons.asUnmodifiableObservableList(); + } + + /** + * Replaces the contents of the pinned list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setPinnedPersons(List persons) { + this.pinnedPersons.setPersons(persons); + } + + /** + * Adds the {@code person} in the pinned contact list. + * + * @param person The {@code person} in the contact list to be pinned. + */ + public void addPinnedPerson(Person person) { + this.pinnedPersons.add(person); + } + + /** + * Remove the {@code person} in the pinned contact list. + * + * @param person The {@code person} in the contact list to be unpinned. + */ + public void removePinnedPerson(Person person) { + this.pinnedPersons.remove(person); } //// util methods @@ -120,7 +173,8 @@ public boolean equals(Object other) { } AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); + return persons.equals(otherAddressBook.persons) + && pinnedPersons.equals(otherAddressBook.pinnedPersons); } @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/bizbook/model/Model.java similarity index 55% rename from src/main/java/seedu/address/model/Model.java rename to src/main/java/bizbook/model/Model.java index d54df471c1f..3defcb29501 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/bizbook/model/Model.java @@ -1,11 +1,12 @@ -package seedu.address.model; +package bizbook.model; import java.nio.file.Path; import java.util.function.Predicate; +import bizbook.commons.core.GuiSettings; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; /** * The API of the Model component. @@ -39,6 +40,11 @@ public interface Model { */ Path getAddressBookFilePath(); + /** + * Returns the person who will be focused on. + */ + ObjectProperty getFocusedPerson(); + /** * Sets the user prefs' address book file path. */ @@ -81,7 +87,67 @@ public interface Model { /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** Returns an unmodifiable view of the pinned person list */ + ObservableList getPinnedPersonList(); + + /** + * Returns true if a person with the same identity as {@code person} exists in the pinned list. + */ + boolean isPinned(Person person); + + /** + * Adds the {@code person} in the pinned contact list. + * + * @param person The {@code person} in the contact list to be pinned. + */ + void pinPerson(Person person); + + /** + * Remove the {@code person} in the pinned contact list. + * + * @param person The {@code person} in the contact list to be unpinned. + */ + void unpinPerson(Person person); + + /** + * Checks if there is a newer version that can be reverted to or not. + */ + boolean canRedo(); + + /** + * Checks if there is a version that can be reverted to or not. + */ + boolean canUndo(); + + /** + * Saves the current state of the {@code AddressBook} into a version history list. + */ + void saveAddressBookVersion(); + + /** + * Reverts the {@code AddressBook} to the most recent saved version. + */ + void revertAddressBookVersion(); + + /** + * Reverts the {@code AddressBook} to the most recent saved version. + */ + void redoAddressBookVersion(); + + /** + * Sets the focus person to {@code person}. + */ + void setFocusPerson(Person person); + + /** + * Cheks if the focus person needs to be updated. + * If {@code previousPerson} is the focused person then update it with {@code currentPerson}. + */ + void updateFocusPerson(Person previousPerson, Person currentPerson); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/bizbook/model/ModelManager.java similarity index 66% rename from src/main/java/seedu/address/model/ModelManager.java rename to src/main/java/bizbook/model/ModelManager.java index 57bc563fde6..f653c1afbc8 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/bizbook/model/ModelManager.java @@ -1,17 +1,19 @@ -package seedu.address.model; +package bizbook.model; +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Objects; import java.util.function.Predicate; import java.util.logging.Logger; +import bizbook.commons.core.GuiSettings; +import bizbook.commons.core.LogsCenter; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; -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. @@ -19,9 +21,10 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final VersionedAddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final ObjectProperty focusedPerson; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -31,9 +34,10 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.addressBook = new VersionedAddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + this.focusedPerson = Person.personProperty(null); } public ModelManager() { @@ -87,6 +91,11 @@ public ReadOnlyAddressBook getAddressBook() { return addressBook; } + @Override + public ObjectProperty getFocusedPerson() { + return this.focusedPerson; + } + @Override public boolean hasPerson(Person person) { requireNonNull(person); @@ -107,10 +116,69 @@ public void addPerson(Person person) { @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); } + @Override + public ObservableList getPinnedPersonList() { + return addressBook.getPinnedPersonList(); + } + + @Override + public boolean isPinned(Person person) { + requireNonNull(person); + return addressBook.isPinned(person); + } + + @Override + public void pinPerson(Person person) { + requireNonNull(person); + this.addressBook.addPinnedPerson(person); + } + + @Override + public void unpinPerson(Person person) { + requireNonNull(person); + this.addressBook.removePinnedPerson(person); + } + + @Override + public boolean canRedo() { + return addressBook.canRedo(); + } + + @Override + public boolean canUndo() { + return addressBook.canUndo(); + } + + @Override + public void saveAddressBookVersion() { + addressBook.commit(); + } + + @Override + public void revertAddressBookVersion() { + addressBook.undo(); + } + + @Override + public void redoAddressBookVersion() { + addressBook.redo(); + } + + @Override + public void setFocusPerson(Person person) { + focusedPerson.set(person); + } + + @Override + public void updateFocusPerson(Person previousPerson, Person currentPerson) { + if (previousPerson.equals(focusedPerson.get())) { + setFocusPerson(currentPerson); + } + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -142,7 +210,8 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredPersons.equals(otherModelManager.filteredPersons) + && Objects.equals(focusedPerson.get(), otherModelManager.focusedPerson.get()); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/bizbook/model/ReadOnlyAddressBook.java similarity index 55% rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java rename to src/main/java/bizbook/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..a4ecc8f412c 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/bizbook/model/ReadOnlyAddressBook.java @@ -1,7 +1,7 @@ -package seedu.address.model; +package bizbook.model; +import bizbook.model.person.Person; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; /** * Unmodifiable view of an address book @@ -14,4 +14,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the pinned person list. + * This list will not contain any duplicate persons. + */ + ObservableList getPinnedPersonList(); + } diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/bizbook/model/ReadOnlyUserPrefs.java similarity index 65% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/bizbook/model/ReadOnlyUserPrefs.java index befd58a4c73..fb5083cc3fb 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/bizbook/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package bizbook.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import bizbook.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -13,4 +13,5 @@ public interface ReadOnlyUserPrefs { Path getAddressBookFilePath(); + Path getExportDirectoryPath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/bizbook/model/UserPrefs.java similarity index 71% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/bizbook/model/UserPrefs.java index 6be655fb4c7..6a3865daf9d 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/bizbook/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package bizbook.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import bizbook.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,8 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "bizbook.json"); + private Path exportDirectoryPath = Paths.get("exports"); /** * Creates a {@code UserPrefs} with default values. @@ -36,8 +37,10 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setExportDirectoryPath(newUserPrefs.getExportDirectoryPath()); } + @Override public GuiSettings getGuiSettings() { return guiSettings; } @@ -47,6 +50,7 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } + @Override public Path getAddressBookFilePath() { return addressBookFilePath; } @@ -56,6 +60,16 @@ public void setAddressBookFilePath(Path addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } + @Override + public Path getExportDirectoryPath() { + return exportDirectoryPath; + } + + public void setExportDirectoryPath(Path exportDirectoryPath) { + requireNonNull(exportDirectoryPath); + this.exportDirectoryPath = exportDirectoryPath; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -69,12 +83,13 @@ public boolean equals(Object other) { UserPrefs otherUserPrefs = (UserPrefs) other; return guiSettings.equals(otherUserPrefs.guiSettings) - && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath); + && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath) + && exportDirectoryPath.equals(otherUserPrefs.exportDirectoryPath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, addressBookFilePath, exportDirectoryPath); } @Override @@ -82,6 +97,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nExport data directory location : " + exportDirectoryPath); return sb.toString(); } diff --git a/src/main/java/bizbook/model/VersionedAddressBook.java b/src/main/java/bizbook/model/VersionedAddressBook.java new file mode 100644 index 00000000000..e82062f44e3 --- /dev/null +++ b/src/main/java/bizbook/model/VersionedAddressBook.java @@ -0,0 +1,92 @@ +package bizbook.model; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * This class represents the address book with versioning. + */ +public class VersionedAddressBook extends AddressBook { + + /* Stores the copy of the previous list of contacts */ + public final Deque addressBookOlderVersionList = new ArrayDeque<>(); + /* Stores the copy of the newer versions of the address book */ + public final Deque addressBookNewerVersionList = new ArrayDeque<>(); + + /** + * Creates an AddressBook using the Persons in the {@code toBeCopied} + */ + public VersionedAddressBook(ReadOnlyAddressBook toBeCopied) { + super(toBeCopied); + this.commit(); + } + + /** + * Retrieves the list of the old address book versions. + */ + public Deque getAddressBookHistoryList() { + return addressBookOlderVersionList; + } + + /** + * Checks if there is a newer version that can be reverted to or not. + */ + public boolean canRedo() { + return addressBookNewerVersionList.size() > 0; + } + + /** + * Checks if there is a version that can be reverted to or not. + */ + public boolean canUndo() { + return addressBookOlderVersionList.size() > 1; + } + + /** + * Adds the current version of the {@code AddressBook} into the list. + */ + public void commit() { + AddressBook addressBookCopy = new AddressBook(); + addressBookCopy.setPersons(getPersonList()); + addressBookCopy.setPinnedPersons(getPinnedPersonList()); + // If equal to the previous version in the list then do not save + if (!addressBookOlderVersionList.isEmpty() && addressBookCopy.equals(addressBookOlderVersionList.getLast())) { + return; + } + // Limit the versions saved to be 5 but 1 slot will be for the base version + if (addressBookOlderVersionList.size() == 6) { + addressBookOlderVersionList.removeFirst(); + } + addressBookOlderVersionList.addLast(addressBookCopy); + + addressBookNewerVersionList.clear(); + } + + /** + * Reverts to the latest version of the {@code AddressBook} and removed it from the list. + */ + public void undo() { + AddressBook currentVersion = addressBookOlderVersionList.removeLast(); + AddressBook previousVersion = addressBookOlderVersionList.getLast(); + + // Limit the versions saved to be 5 + if (addressBookNewerVersionList.size() == 5) { + addressBookNewerVersionList.removeFirst(); + } + + addressBookNewerVersionList.addLast(currentVersion); + + setPersons(previousVersion.getPersonList()); + setPinnedPersons(previousVersion.getPinnedPersonList()); + } + + /** + * Reverts to a newer version of the {@code AddressBook} and removed it from the list. + */ + public void redo() { + AddressBook newerVersion = addressBookNewerVersionList.removeLast(); + addressBookOlderVersionList.addLast(newerVersion); + setPersons(newerVersion.getPersonList()); + setPinnedPersons(newerVersion.getPinnedPersonList()); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/bizbook/model/person/Address.java similarity index 93% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/bizbook/model/person/Address.java index 469a2cc9a1e..3360a3be63f 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/bizbook/model/person/Address.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package bizbook.model.person; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Person's address in the address book. diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/bizbook/model/person/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/bizbook/model/person/Email.java index c62e512bc29..1574b79bbda 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/bizbook/model/person/Email.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package bizbook.model.person; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Person's email in the address book. diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/bizbook/model/person/Name.java similarity index 93% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/bizbook/model/person/Name.java index 173f15b9b00..bb7190c6331 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/bizbook/model/person/Name.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package bizbook.model.person; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Person's name in the address book. diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/bizbook/model/person/NameContainsKeywordsPredicate.java similarity index 81% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/bizbook/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..fb0bbe76ff3 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/bizbook/model/person/NameContainsKeywordsPredicate.java @@ -1,10 +1,9 @@ -package seedu.address.model.person; +package bizbook.model.person; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.util.ToStringBuilder; +import bizbook.commons.util.ToStringBuilder; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. @@ -19,7 +18,8 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> person.getName().fullName.toLowerCase() + .contains(keyword.toLowerCase())); } @Override diff --git a/src/main/java/bizbook/model/person/Note.java b/src/main/java/bizbook/model/person/Note.java new file mode 100644 index 00000000000..913e07613b2 --- /dev/null +++ b/src/main/java/bizbook/model/person/Note.java @@ -0,0 +1,79 @@ +package bizbook.model.person; + +import static bizbook.commons.util.AppUtil.checkArgument; +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +/** + * Represents a Person's notes in BizBook. + */ +public class Note { + + public static final String MESSAGE_CONSTRAINTS = "Notes should be alphanumeric."; + public static final String VALIDATION_REGEX = "(?!^ +$)[\\p{Alnum} ]+"; + + private String note; + + /** + * Constructs {@code Notes}. + * + * @param note A valid note. + */ + public Note(String note) { + requireNonNull(note); + checkArgument(isValidNoteName(note), MESSAGE_CONSTRAINTS); + this.note = note; + } + + public String getNote() { + return this.note; + } + + @Override + public boolean equals(Object other) { + + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Note)) { + return false; + } + + Note otherNotes = (Note) other; + + return note.equalsIgnoreCase(otherNotes.note); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(note.toLowerCase()); + } + + /** + * Returns true if a given string is a valid note name. + */ + public static boolean isValidNoteName(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns all stored notes as a String. + * + * @return Notes string to be displayed in details pane + */ + @Override + public String toString() { + return '[' + note + ']'; + } + + /** + * Returns true if a note is empty and false if not. + */ + public boolean isEmpty() { + return note.isEmpty(); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/bizbook/model/person/Person.java similarity index 66% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/bizbook/model/person/Person.java index abe8c46b535..9e5fe8e8fb2 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/bizbook/model/person/Person.java @@ -1,14 +1,17 @@ -package seedu.address.model.person; +package bizbook.model.person; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import bizbook.commons.util.ToStringBuilder; +import bizbook.model.tag.Tag; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; /** * Represents a Person in the address book. @@ -23,18 +26,21 @@ public class Person { // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Set tags = new LinkedHashSet<>(); + private final ArrayList notes = new ArrayList<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { + public Person(Name name, Phone phone, Email email, Address address, Set tags, ArrayList notes) { requireAllNonNull(name, phone, email, address, tags); this.name = name; this.phone = phone; this.email = email; this.address = address; this.tags.addAll(tags); + this.notes.addAll(notes); + } public Name getName() { @@ -61,6 +67,10 @@ public Set getTags() { return Collections.unmodifiableSet(tags); } + public ArrayList getNotes() { + return notes; + } + /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. @@ -74,6 +84,29 @@ public boolean isSamePerson(Person otherPerson) { && otherPerson.getName().equals(getName()); } + /** + * Wraps the Person so that it becomes an ObjectProperty + * + * @param person the person to wrap around + * @return an ObjectProperty of the person + * @see ObjectProperty + */ + public static ObjectProperty personProperty(Person person) { + ObjectProperty property = new ObjectPropertyBase<>() { + @Override + public Object getBean() { + return null; + } + + @Override + public String getName() { + return ""; + } + }; + property.set(person); + return property; + } + /** * Returns true if both persons have the same identity and data fields. * This defines a stronger notion of equality between two persons. @@ -94,13 +127,14 @@ public boolean equals(Object other) { && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && tags.equals(otherPerson.tags) + && notes.equals(otherPerson.notes); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address, tags, notes); } @Override @@ -111,6 +145,7 @@ public String toString() { .add("email", email) .add("address", address) .add("tags", tags) + .add("notes", notes) .toString(); } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/bizbook/model/person/Phone.java similarity index 82% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/bizbook/model/person/Phone.java index d733f63d739..d23dfd5a503 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/bizbook/model/person/Phone.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package bizbook.model.person; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Person's phone number in the address book. @@ -11,10 +11,9 @@ 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 contain numbers, be 8 digits long and start with 6, 8 or 9"; + public static final String VALIDATION_REGEX = "^[689]\\d{7}$"; public final String value; - /** * Constructs a {@code Phone}. * @@ -57,5 +56,4 @@ public boolean equals(Object other) { public int hashCode() { return value.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/bizbook/model/person/UniquePersonList.java similarity index 94% rename from src/main/java/seedu/address/model/person/UniquePersonList.java rename to src/main/java/bizbook/model/person/UniquePersonList.java index cc0a68d79f9..6eb84c89ffc 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/bizbook/model/person/UniquePersonList.java @@ -1,15 +1,15 @@ -package seedu.address.model.person; +package bizbook.model.person; +import static bizbook.commons.util.CollectionUtil.requireAllNonNull; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; import java.util.List; +import bizbook.model.person.exceptions.DuplicatePersonException; +import bizbook.model.person.exceptions.PersonNotFoundException; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/bizbook/model/person/exceptions/DuplicatePersonException.java similarity index 87% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/bizbook/model/person/exceptions/DuplicatePersonException.java index d7290f59442..9893f760b30 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/bizbook/model/person/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package bizbook.model.person.exceptions; /** * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/bizbook/model/person/exceptions/PersonNotFoundException.java similarity index 75% rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java rename to src/main/java/bizbook/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..8db8d340427 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/bizbook/model/person/exceptions/PersonNotFoundException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package bizbook.model.person.exceptions; /** * Signals that the operation is unable to find the specified person. diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/bizbook/model/tag/Tag.java similarity index 93% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/bizbook/model/tag/Tag.java index f1a0d4e233b..0952dc09b73 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/bizbook/model/tag/Tag.java @@ -1,7 +1,7 @@ -package seedu.address.model.tag; +package bizbook.model.tag; +import static bizbook.commons.util.AppUtil.checkArgument; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/bizbook/model/util/SampleDataUtil.java similarity index 62% rename from src/main/java/seedu/address/model/util/SampleDataUtil.java rename to src/main/java/bizbook/model/util/SampleDataUtil.java index 1806da4facf..3665d8465c6 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/bizbook/model/util/SampleDataUtil.java @@ -1,42 +1,45 @@ -package seedu.address.model.util; +package bizbook.model.util; +import java.util.ArrayList; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import bizbook.model.AddressBook; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { + public static Person[] getSamplePersons() { return new Person[] { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + getTagSet("friends"), getNoteList("High profile client")), 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"), getNoteList("Likes dumplings")), 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"), getNoteList("Potential return client")), 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"), getNoteList("Owns a manufacturing company")), 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"), getNoteList("High profile client")), 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"), getNoteList("Prefers no handshakes")) }; } @@ -57,4 +60,13 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a note set containing the list of strings given. + */ + public static ArrayList getNoteList(String... strings) { + return Arrays.stream(strings) + .map(Note::new) + .collect(Collectors.toCollection(ArrayList::new)); + } + } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/bizbook/storage/AddressBookStorage.java similarity index 84% rename from src/main/java/seedu/address/storage/AddressBookStorage.java rename to src/main/java/bizbook/storage/AddressBookStorage.java index f2e015105ae..a65b356494a 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/bizbook/storage/AddressBookStorage.java @@ -1,14 +1,15 @@ -package seedu.address.storage; +package bizbook.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.model.AddressBook; +import bizbook.model.ReadOnlyAddressBook; /** - * Represents a storage for {@link seedu.address.model.AddressBook}. + * Represents a storage for {@link AddressBook}. */ public interface AddressBookStorage { @@ -32,6 +33,7 @@ public interface AddressBookStorage { /** * Saves the given {@link ReadOnlyAddressBook} to the storage. + * * @param addressBook cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/bizbook/storage/JsonAdaptedNote.java b/src/main/java/bizbook/storage/JsonAdaptedNote.java new file mode 100644 index 00000000000..d4e23c75462 --- /dev/null +++ b/src/main/java/bizbook/storage/JsonAdaptedNote.java @@ -0,0 +1,47 @@ +package bizbook.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.model.person.Note; + +/** + * Jackson-friendly version of {@link Note}. + */ +public class JsonAdaptedNote { + + private final String noteName; + + /** + * Constructs a {@code JsonAdaptedNote} with the given {@code noteName}. + */ + @JsonCreator + public JsonAdaptedNote(String noteName) { + this.noteName = noteName; + } + + /** + * Converts a given {@code Note} into this class for Jackson use. + */ + public JsonAdaptedNote(Note source) { + noteName = source.getNote(); + } + + @JsonValue + public String getNoteName() { + return noteName; + } + + /** + * Converts this Jackson-friendly adapted note object into the model's {@code Note} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted note. + */ + public Note toModelType() throws IllegalValueException { + if (!Note.isValidNoteName(noteName)) { + throw new IllegalValueException(Note.MESSAGE_CONSTRAINTS); + } + return new Note(noteName); + } +} diff --git a/src/main/java/bizbook/storage/JsonAdaptedPerson.java b/src/main/java/bizbook/storage/JsonAdaptedPerson.java new file mode 100644 index 00000000000..f5b060e1bcb --- /dev/null +++ b/src/main/java/bizbook/storage/JsonAdaptedPerson.java @@ -0,0 +1,126 @@ +package bizbook.storage; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.model.person.Address; +import bizbook.model.person.Email; +import bizbook.model.person.Name; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import bizbook.model.person.Phone; +import bizbook.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Person}. + */ +class JsonAdaptedPerson { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + private final List tags = new ArrayList<>(); + private final List notes = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("tags") List tags, @JsonProperty("notes") List notes) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tags != null) { + this.tags.addAll(tags); + } + if (notes != null) { + this.notes.addAll(notes); + } + } + + /** + * Converts a given {@code Person} into this class for Jackson use. + */ + public JsonAdaptedPerson(Person source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tags.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .toList()); + notes.addAll(source.getNotes().stream() + .map(JsonAdaptedNote::new) + .toList()); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Person toModelType() throws IllegalValueException { + final List personTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tags) { + personTags.add(tag.toModelType()); + } + + final List personNotes = new ArrayList<>(); + for (JsonAdaptedNote note : notes) { + personNotes.add(note.toModelType()); + } + + final Name modelName = new Name(validateField(name, Name.class.getSimpleName(), Name.MESSAGE_CONSTRAINTS, + Name::isValidName)); + final Phone modelPhone = new Phone(validateField(phone, Phone.class.getSimpleName(), Phone.MESSAGE_CONSTRAINTS, + Phone::isValidPhone)); + final Email modelEmail = new Email(validateField(email, Email.class.getSimpleName(), Email.MESSAGE_CONSTRAINTS, + Email::isValidEmail)); + final Address modelAddress = new Address(validateField(address, Address.class.getSimpleName(), + Address.MESSAGE_CONSTRAINTS, Address::isValidAddress)); + + + final Set modelTags = new LinkedHashSet<>(personTags); + final ArrayList modelNotes = new ArrayList<>(personNotes); + + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelNotes); + } + + /** + * Validates a field based on its presence and provided constraints. + * + * @param field the field to be validated. + * @param fieldName the name of the field that is used for error messages. + * @param constraintMessage the message to show if validation fails. + * @param isValid a function that checks if the field meets the validation constraints. + * @return the validated field value. + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + private String validateField(String field, String fieldName, String constraintMessage, + Function isValid) throws IllegalValueException { + // Check if the field is missing + if (field == null) { + String errorMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, fieldName); + throw new IllegalValueException(errorMessage); + } + + // Check if the field violates constraints, if not valid, throw IllegalValueException + if (!isValid.apply(field)) { + throw new IllegalValueException(constraintMessage); + } + return field; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/bizbook/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/bizbook/storage/JsonAdaptedTag.java index 0df22bdb754..c4bf0e9d0a8 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/bizbook/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package bizbook.storage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.model.tag.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/bizbook/storage/JsonAddressBookStorage.java similarity index 86% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/bizbook/storage/JsonAddressBookStorage.java index 41e06f264e1..b75a630754e 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/bizbook/storage/JsonAddressBookStorage.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package bizbook.storage; import static java.util.Objects.requireNonNull; @@ -7,12 +7,12 @@ import java.util.Optional; import java.util.logging.Logger; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.commons.util.FileUtil; +import bizbook.commons.util.JsonUtil; +import bizbook.model.ReadOnlyAddressBook; /** * A class to access AddressBook data stored as a json file on the hard disk. diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/bizbook/storage/JsonSerializableAddressBook.java similarity index 61% rename from src/main/java/seedu/address/storage/JsonSerializableAddressBook.java rename to src/main/java/bizbook/storage/JsonSerializableAddressBook.java index 5efd834091d..4c6c5d92654 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/bizbook/storage/JsonSerializableAddressBook.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package bizbook.storage; import java.util.ArrayList; import java.util.List; @@ -8,10 +8,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import bizbook.commons.exceptions.IllegalValueException; +import bizbook.model.AddressBook; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.person.Person; /** * An Immutable AddressBook that is serializable to JSON format. @@ -21,8 +21,14 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + // used to ensure that manually edited data file do not contain unknown person + public static final String MESSAGE_DUPLICATE_PIN_PERSON = "Pinned persons list contains duplicate person(s)."; + public static final String MESSAGE_UNKNOWN_PERSON = "Pinned persons list contains unknown person(s)."; + private final List persons = new ArrayList<>(); + private final List pinnedPersons = new ArrayList<>(); + /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @@ -38,6 +44,8 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List readUserPrefs() throws DataLoadingException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataLoadingException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/bizbook/storage/Storage.java similarity index 73% rename from src/main/java/seedu/address/storage/Storage.java rename to src/main/java/bizbook/storage/Storage.java index 9fba0c7a1d6..f60a4d40bf2 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/bizbook/storage/Storage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package bizbook.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.UserPrefs; /** * API of the Storage component diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/bizbook/storage/StorageManager.java similarity index 89% rename from src/main/java/seedu/address/storage/StorageManager.java rename to src/main/java/bizbook/storage/StorageManager.java index 8b84a9024d5..47ca0db74b9 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/bizbook/storage/StorageManager.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package bizbook.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Logger; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.model.ReadOnlyAddressBook; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.UserPrefs; /** * Manages storage of AddressBook data in local storage. diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/bizbook/storage/UserPrefsStorage.java similarity index 69% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/bizbook/storage/UserPrefsStorage.java index e94ca422ea8..654a9760507 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/bizbook/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package bizbook.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import bizbook.commons.exceptions.DataLoadingException; +import bizbook.model.ReadOnlyUserPrefs; +import bizbook.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,8 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataLoadingException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link ReadOnlyUserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/bizbook/ui/CommandBox.java similarity index 62% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/bizbook/ui/CommandBox.java index 9e75478664b..a8a36005d93 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/bizbook/ui/CommandBox.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package bizbook.ui; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.exceptions.ParseException; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -17,16 +17,19 @@ public class CommandBox extends UiPart { private static final String FXML = "CommandBox.fxml"; private final CommandExecutor commandExecutor; + private final CommandHistory commandHistory; @FXML private TextField commandTextField; /** - * Creates a {@code CommandBox} with the given {@code CommandExecutor}. + * Creates a {@code CommandBox} with the given {@code CommandExecutor} and {@code CommandHistory}. */ - public CommandBox(CommandExecutor commandExecutor) { + public CommandBox(CommandExecutor commandExecutor, CommandHistory commandHistory) { super(FXML); this.commandExecutor = commandExecutor; + this.commandHistory = commandHistory; + // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } @@ -44,11 +47,37 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandText); commandTextField.setText(""); + commandHistory.addCommand(commandText); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); } } + /** + * Populates command box with {@param text}. + */ + private void autoCompleteText(String text) { + commandTextField.setText(text); + commandTextField.positionCaret(commandTextField.getText().length()); // Move cursor to end + } + + /** + * Handles the key press event, {@code KeyEvent}. + */ + @FXML + private void handleKeyPressed(KeyEvent keyEvent) { + switch (keyEvent.getCode()) { + case UP: + autoCompleteText(commandHistory.getPreviousCommand()); + break; + case DOWN: + autoCompleteText(commandHistory.getNextCommand()); + break; + default: + break; + } + } + /** * Sets the command box style to use the default style. */ @@ -68,18 +97,4 @@ private void setStyleToIndicateCommandFailure() { styleClass.add(ERROR_STYLE_CLASS); } - - /** - * Represents a function that can execute commands. - */ - @FunctionalInterface - public interface CommandExecutor { - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - } - } diff --git a/src/main/java/bizbook/ui/CommandExecutor.java b/src/main/java/bizbook/ui/CommandExecutor.java new file mode 100644 index 00000000000..82bb9c09800 --- /dev/null +++ b/src/main/java/bizbook/ui/CommandExecutor.java @@ -0,0 +1,18 @@ +package bizbook.ui; + +import bizbook.logic.Logic; +import bizbook.logic.commands.CommandResult; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.exceptions.ParseException; +/** + * Represents a function that can execute commands. + */ +@FunctionalInterface +public interface CommandExecutor { + /** + * Executes the command and returns the result. + * + * @see Logic#execute(String) + */ + CommandResult execute(String commandText) throws CommandException, ParseException; +} diff --git a/src/main/java/bizbook/ui/CommandHistory.java b/src/main/java/bizbook/ui/CommandHistory.java new file mode 100644 index 00000000000..1153e2bc9bc --- /dev/null +++ b/src/main/java/bizbook/ui/CommandHistory.java @@ -0,0 +1,63 @@ +package bizbook.ui; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * Manages the command history for the CommandBox. + */ +public class CommandHistory { + private ObservableList history; + private int currentIndex; + + /** + * Creates a {@code CommandHistory} to store command history. + */ + public CommandHistory() { + history = FXCollections.observableArrayList(); + currentIndex = -1; + } + + /** + * Adds a new command to the history. + * + * @param command the command to add. + * */ + public void addCommand(String command) { + history.add(command); + currentIndex = history.size(); // Set to after the last command + } + + /** + * Gets the previous command in the history. + * + * @return the previous command, or an empty string if at the beginning of the history. + */ + public String getPreviousCommand() { + if (currentIndex > 0) { + currentIndex--; + return history.get(currentIndex); + } + return ""; + } + + /** + * Gets the next command in the history. + * + * @return the next command, or an empty string if at the end of the history + */ + public String getNextCommand() { + if (currentIndex >= 0 && currentIndex < history.size()) { + return history.get(currentIndex++); + } + return ""; + } + + /** + * Clears the command history. + */ + public void clear() { + history.clear(); + currentIndex = -1; + } +} diff --git a/src/main/java/bizbook/ui/CommandTablePanel.java b/src/main/java/bizbook/ui/CommandTablePanel.java new file mode 100644 index 00000000000..d7c93020f6a --- /dev/null +++ b/src/main/java/bizbook/ui/CommandTablePanel.java @@ -0,0 +1,177 @@ +package bizbook.ui; + +import java.util.logging.Logger; + +import bizbook.commons.core.LogsCenter; +import bizbook.logic.commands.AddCommand; +import bizbook.logic.commands.AddNoteCommand; +import bizbook.logic.commands.ClearCommand; +import bizbook.logic.commands.DeleteCommand; +import bizbook.logic.commands.DeleteNoteCommand; +import bizbook.logic.commands.DeleteTagCommand; +import bizbook.logic.commands.EditCommand; +import bizbook.logic.commands.EditNoteCommand; +import bizbook.logic.commands.ExitCommand; +import bizbook.logic.commands.ExportCommand; +import bizbook.logic.commands.FindCommand; +import bizbook.logic.commands.HelpCommand; +import bizbook.logic.commands.ImportCommand; +import bizbook.logic.commands.ListCommand; +import bizbook.logic.commands.PinCommand; +import bizbook.logic.commands.RedoCommand; +import bizbook.logic.commands.ToggleCommand; +import bizbook.logic.commands.UndoCommand; +import bizbook.logic.commands.UnpinCommand; +import bizbook.logic.commands.ViewCommand; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.Region; +import javafx.scene.text.Text; + +/** + * Panel containing the list of command help. + */ +public class CommandTablePanel extends UiPart { + private static final String FXML = "CommandTablePanel.fxml"; + + private static final double COMMAND_WORD_RATIO = 0.15; + private static final double COMMAND_USAGE_RATIO = 0.85; + + private static final double COMMAND_WORD_COLUMN_SIZE = Integer.MAX_VALUE * COMMAND_WORD_RATIO; + private static final double COMMAND_USAGE_COLUMN_SIZE = Integer.MAX_VALUE * COMMAND_USAGE_RATIO; + + private final Logger logger = LogsCenter.getLogger(CommandTablePanel.class); + + + @FXML + private TableView commandTable; + + @FXML + private TableColumn actionColumn; + + @FXML + private TableColumn formatColumn; + + /** + * Creates a {@code CommandTablePanel} + */ + public CommandTablePanel() { + super(FXML); + initializeTable(); + populateData(); + } + + /** + * Initialize table formatting. + */ + private void initializeTable() { + commandTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + actionColumn.setCellValueFactory(new PropertyValueFactory<>("commandWord")); + actionColumn.setMaxWidth(COMMAND_WORD_COLUMN_SIZE); + actionColumn.setMinWidth(75); + + formatColumn.setCellValueFactory(new PropertyValueFactory<>("commandUsage")); + formatColumn.setCellFactory(column -> new CommandTableCell()); + formatColumn.setMinWidth(100); + formatColumn.setMaxWidth(COMMAND_USAGE_COLUMN_SIZE); + + commandTable.widthProperty().addListener((obs, oldWidth, newWidth) -> { + actionColumn.setPrefWidth(newWidth.doubleValue() * COMMAND_WORD_RATIO); + formatColumn.setPrefWidth(newWidth.doubleValue() * COMMAND_USAGE_COLUMN_SIZE); + }); + } + + /** + * Fills up the table with the commands. + */ + private void populateData() { + logger.fine("Showing a few command format of the application."); + ObservableList commandList = FXCollections.observableArrayList( + // Add more commands here as needed + new CommandEntry(AddCommand.COMMAND_WORD, AddCommand.MESSAGE_USAGE), + new CommandEntry(AddNoteCommand.COMMAND_WORD, AddNoteCommand.MESSAGE_USAGE), + new CommandEntry(ClearCommand.COMMAND_WORD, ClearCommand.MESSAGE_USAGE), + new CommandEntry(DeleteCommand.COMMAND_WORD, DeleteCommand.MESSAGE_USAGE), + new CommandEntry(DeleteNoteCommand.COMMAND_WORD, DeleteNoteCommand.MESSAGE_USAGE), + new CommandEntry(DeleteTagCommand.COMMAND_WORD, DeleteTagCommand.MESSAGE_USAGE), + new CommandEntry(EditCommand.COMMAND_WORD, EditCommand.MESSAGE_USAGE), + new CommandEntry(EditNoteCommand.COMMAND_WORD, EditNoteCommand.MESSAGE_USAGE), + new CommandEntry(ExitCommand.COMMAND_WORD, ExitCommand.MESSAGE_USAGE), + new CommandEntry(ExportCommand.COMMAND_WORD, ExportCommand.MESSAGE_USAGE), + new CommandEntry(FindCommand.COMMAND_WORD, FindCommand.MESSAGE_USAGE), + new CommandEntry(HelpCommand.COMMAND_WORD, HelpCommand.MESSAGE_USAGE), + new CommandEntry(ImportCommand.COMMAND_WORD, ImportCommand.MESSAGE_USAGE), + new CommandEntry(ListCommand.COMMAND_WORD, ListCommand.MESSAGE_USAGE), + new CommandEntry(PinCommand.COMMAND_WORD, PinCommand.MESSAGE_USAGE), + new CommandEntry(RedoCommand.COMMAND_WORD, RedoCommand.MESSAGE_USAGE), + new CommandEntry(ToggleCommand.COMMAND_WORD, ToggleCommand.MESSAGE_USAGE), + new CommandEntry(UndoCommand.COMMAND_WORD, UndoCommand.MESSAGE_USAGE), + new CommandEntry(UnpinCommand.COMMAND_WORD, UnpinCommand.MESSAGE_USAGE), + new CommandEntry(ViewCommand.COMMAND_WORD, ViewCommand.MESSAGE_USAGE) + ); + commandTable.setItems(commandList); + } + + /** + * Custom table record that displays onto table. + */ + public class CommandEntry { + private String commandWord; + private String commandUsage; + + /** + * Creates a {@code CommandEntry} with the given command word and usage string. + */ + public CommandEntry(String commandWord, String commandUsage) { + this.commandWord = commandWord; + this.commandUsage = commandUsage; + } + + /** + * Gets the command word. + * + * @return command word. + */ + public String getCommandWord() { + return this.commandWord; + } + + /** + * Gets the command usage syntax. + * + * @return command usage. + */ + public String getCommandUsage() { + return this.commandUsage; + } + } + + /** + * Custom {@code TableCell} that displays the graphics of a command record. + */ + class CommandTableCell extends TableCell { + private final Text text = new Text(); + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setGraphic(null); + } else { + text.setText(item); + + text.wrappingWidthProperty() + .bind(getTableColumn().widthProperty().subtract(10)); + text.getStyleClass().add("help-textbox"); + text.setId("help-textbox"); + setGraphic(text); + } + } + }; +} diff --git a/src/main/java/bizbook/ui/ContactDetails.java b/src/main/java/bizbook/ui/ContactDetails.java new file mode 100644 index 00000000000..8935b35faaa --- /dev/null +++ b/src/main/java/bizbook/ui/ContactDetails.java @@ -0,0 +1,99 @@ +package bizbook.ui; + +import java.util.logging.Logger; + +import bizbook.commons.core.LogsCenter; +import bizbook.model.person.Note; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/** + * Controller for the contact details panel. + */ +public class ContactDetails extends UiPart { + + private static final String FXML = "ContactDetails.fxml"; + private final Logger logger = LogsCenter.getLogger(ContactDetails.class); + + @FXML + private ScrollPane contactDetailsPanel; + + @FXML + private Label name; + + @FXML + private Label phoneNo; + + @FXML + private Label email; + + @FXML + private Label address; + + @FXML + private Label notes; + + @FXML + private VBox notesList; + + /** + * Creates a {@code ContactDetailsPanel} with the given {@code Person} information. + */ + public ContactDetails(ObjectProperty person) { + super(FXML); + person.addListener((observable, oldValue, newValue) -> displayPerson(newValue)); + } + + /** + * Sets the person object as the contact to be displayed on the panel. + * + * @param person The person object to be updated onto the panel. + */ + private void displayPerson(Person person) { + clearPanel(); + + if (person == null || person.equals(null)) { + return; + } + + logger.info("Displaying info of " + person.toString()); + + name.setText(person.getName().fullName); + phoneNo.setText("Mobile: " + person.getPhone().toString()); + email.setText("Email: " + person.getEmail().toString()); + address.setText("Address: " + person.getAddress().toString()); + + if (!person.getNotes().isEmpty()) { + Label notesHeader = new Label("Notes"); + notesHeader.setId("notes-header"); + notesList.getChildren().add(notesHeader); + } + + int index = 1; + for (Note note : person.getNotes()) { + Label label = new Label(index + ". " + note.getNote()); + label.setId("notes-label"); + notesList.getChildren().add(label); + + index++; + } + } + + /** + * Clears any previous contact details from the panel. + */ + private void clearPanel() { + // Clear existing labels + name.setText(""); + phoneNo.setText(""); + email.setText(""); + address.setText(""); + notes.setText(""); + notesList.getChildren().clear(); + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/bizbook/ui/HelpWindow.java similarity index 51% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/bizbook/ui/HelpWindow.java index 3f16b2fcf26..6554c79c9ac 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/bizbook/ui/HelpWindow.java @@ -1,32 +1,44 @@ -package seedu.address.ui; +package bizbook.ui; +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.logging.Logger; +import bizbook.commons.core.LogsCenter; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; +import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; - + public static final String USERGUIDE_URL = "https://ay2425s1-cs2103-f10-3.github.io/tp/UserGuide.html"; + public static final String HELP_MESSAGE = "For full details, refer to the user guide: " + USERGUIDE_URL; + public static final String URL_FAIL_MESSAGE = "Failed to open the URL. The URL has been copied to the clipboard."; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; + private CommandTablePanel commandTablePanel; @FXML private Button copyButton; + @FXML + private Label errorMessageLabel; + @FXML private Label helpMessage; + @FXML + private StackPane commandTablePlaceholder; + /** * Creates a new HelpWindow. * @@ -35,6 +47,8 @@ public class HelpWindow extends UiPart { public HelpWindow(Stage root) { super(FXML, root); helpMessage.setText(HELP_MESSAGE); + errorMessageLabel.setText(""); + errorMessageLabel.setManaged(false); } /** @@ -46,6 +60,7 @@ public HelpWindow() { /** * Shows the help window. + * * @throws IllegalStateException *
    *
  • @@ -90,13 +105,64 @@ public void focus() { } /** - * Copies the URL to the user guide to the clipboard. + * Handles the URL button pressed event. */ @FXML + private void handleUrl() { + // try to open browser + boolean redirectSuccess = openUrl(); + + // fall back to copying URL to clipboard + if (!redirectSuccess) { + copyUrl(); + showError(); + } + } + + /** + * Makes error message appear. + */ + private void showError() { + errorMessageLabel.setText(URL_FAIL_MESSAGE); + errorMessageLabel.setManaged(true); + this.getRoot().sizeToScene(); + } + + + /** + * Copies the URL to the user guide to the clipboard. + */ private void copyUrl() { final Clipboard clipboard = Clipboard.getSystemClipboard(); final ClipboardContent url = new ClipboardContent(); url.putString(USERGUIDE_URL); clipboard.setContent(url); } + + /** + * Open the URL to the user guide to the clipboard. + * + * @return success status of URL open. + */ + private boolean openUrl() { + try { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(new URI(USERGUIDE_URL)); + return true; + } else { + System.out.println("Desktop browsing is not supported on this system."); + } + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + return false; + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + commandTablePanel = new CommandTablePanel(); + commandTablePlaceholder.getChildren().add(commandTablePanel.getRoot()); + } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/bizbook/ui/MainWindow.java similarity index 60% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/bizbook/ui/MainWindow.java index 79e74ef37c0..e23f5cee2d9 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/bizbook/ui/MainWindow.java @@ -1,21 +1,23 @@ -package seedu.address.ui; +package bizbook.ui; import java.util.logging.Logger; +import bizbook.commons.core.GuiSettings; +import bizbook.commons.core.LogsCenter; +import bizbook.logic.Logic; +import bizbook.logic.commands.CommandResult; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.exceptions.ParseException; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Scene; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; /** * The Main Window. Provides the basic application layout containing @@ -24,32 +26,57 @@ public class MainWindow extends UiPart { private static final String FXML = "MainWindow.fxml"; + private static final String DARK_THEME_URL = MainWindow.class.getResource("/view/DarkTheme.css").toExternalForm(); + private static final String LIGHT_THEME_URL = MainWindow.class.getResource("/view/LightTheme.css").toExternalForm(); + private static final String DARK_THEME_HELP_URL = + MainWindow.class.getResource("/view/HelpWindowDark.css").toExternalForm(); + private static final String LIGHT_THEME_HELP_URL = + MainWindow.class.getResource("/view/HelpWindowLight.css").toExternalForm(); + + private boolean isDarkTheme = true; + private Scene scene; private final Logger logger = LogsCenter.getLogger(getClass()); private Stage primaryStage; private Logic logic; + private CommandHistory commandHistory; // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private PinnedPersonListPanel pinnedPersonListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private ContactDetails contactDetailsPanel; + private SearchBox searchBox; + + @FXML + private MenuItem toggleThemeMenuItem; @FXML private StackPane commandBoxPlaceholder; + @FXML + private StackPane searchBoxPlaceholder; + @FXML private MenuItem helpMenuItem; @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane pinnedListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane contactDetailsPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -59,6 +86,8 @@ public MainWindow(Stage primaryStage, Logic logic) { // Set dependencies this.primaryStage = primaryStage; this.logic = logic; + this.commandHistory = new CommandHistory(); + this.scene = getRoot().getScene(); // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); @@ -78,6 +107,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -110,17 +140,27 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + + contactDetailsPanel = new ContactDetails(logic.getFocusedPerson()); + contactDetailsPanelPlaceholder.getChildren().add(contactDetailsPanel.getRoot()); + + personListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.getFocusedPerson()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + pinnedPersonListPanel = new PinnedPersonListPanel(logic.getPinnedPersonList(), logic.getFocusedPerson()); + pinnedListPanelPlaceholder.getChildren().add(pinnedPersonListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - CommandBox commandBox = new CommandBox(this::executeCommand); + CommandBox commandBox = new CommandBox(this::executeCommand, commandHistory); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + searchBox = new SearchBox(this::executeFindCommand); + searchBoxPlaceholder.getChildren().add(searchBox.getRoot()); } /** @@ -141,6 +181,7 @@ private void setWindowDefaultSize(GuiSettings guiSettings) { @FXML public void handleHelp() { if (!helpWindow.isShowing()) { + helpWindow.fillInnerParts(); helpWindow.show(); } else { helpWindow.focus(); @@ -162,15 +203,37 @@ private void handleExit() { helpWindow.hide(); primaryStage.hide(); } + /** + * Toggles between light and dark themes + */ + @FXML + private void handleThemeChange() { + isDarkTheme = !isDarkTheme; + + // Get the scene's stylesheets + ObservableList stylesheets = scene.getStylesheets(); + ObservableList helpStylesheets = helpWindow.getRoot().getScene().getStylesheets(); + stylesheets.clear(); + helpStylesheets.clear(); + + // Add the appropriate stylesheet based on the theme + if (isDarkTheme) { + stylesheets.add(DARK_THEME_URL); + helpStylesheets.add(DARK_THEME_HELP_URL); + toggleThemeMenuItem.setText("Switch to Light Theme"); + } else { + stylesheets.add(LIGHT_THEME_URL); + helpStylesheets.add(LIGHT_THEME_HELP_URL); + toggleThemeMenuItem.setText("Switch to Dark Theme"); + } - public PersonListPanel getPersonListPanel() { - return personListPanel; + logger.info("Theme switched to " + (isDarkTheme ? "dark" : "light") + " mode"); } /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see Logic#execute(String) */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { @@ -186,6 +249,12 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.isThemeChange()) { + handleThemeChange(); + } + + searchBox.clearSearchBox(); + return commandResult; } catch (CommandException | ParseException e) { logger.info("An error occurred while executing command: " + commandText); @@ -193,4 +262,21 @@ private CommandResult executeCommand(String commandText) throws CommandException throw e; } } + + /** + * Executes the find command and returns the result. + * + * @see Logic#execute(String) + */ + private CommandResult executeFindCommand(String commandText) throws CommandException, ParseException { + try { + CommandResult commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("An error occurred while executing command: " + commandText); + throw e; + } + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/bizbook/ui/PersonCard.java similarity index 82% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/bizbook/ui/PersonCard.java index 094c42cda82..6cd3e2308d2 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/bizbook/ui/PersonCard.java @@ -1,16 +1,16 @@ -package seedu.address.ui; +package bizbook.ui; import java.util.Comparator; +import bizbook.model.person.Person; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; /** - * An UI component that displays information of a {@code Person}. + * A UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart { @@ -35,10 +35,6 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; - @FXML - private Label email; - @FXML private FlowPane tags; /** @@ -50,8 +46,6 @@ public PersonCard(Person person, int displayedIndex) { id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/bizbook/ui/PersonListPanel.java similarity index 55% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/bizbook/ui/PersonListPanel.java index f4c501a897b..c18c2ab6dd3 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/bizbook/ui/PersonListPanel.java @@ -1,14 +1,16 @@ -package seedu.address.ui; +package bizbook.ui; import java.util.logging.Logger; +import bizbook.commons.core.LogsCenter; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; /** * Panel containing the list of persons. @@ -23,10 +25,29 @@ public class PersonListPanel extends UiPart { /** * Creates a {@code PersonListPanel} with the given {@code ObservableList}. */ - public PersonListPanel(ObservableList personList) { + public PersonListPanel(ObservableList personList, ObjectProperty focusedPerson) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new PersonListViewCell()); + + // Add event handler + personListView.setOnMouseClicked(event -> handleListViewClick(event, focusedPerson)); + } + + /** + * When user clicks on a person, the details plane changes. + */ + private void handleListViewClick(MouseEvent event, ObjectProperty focusedPerson) { + // Get the index of the clicked item + int index = personListView.getSelectionModel().getSelectedIndex(); + Person selectedPerson = personListView.getSelectionModel().getSelectedItem(); + + if (index != -1) { + focusedPerson.set(selectedPerson); + logger.info("Clicked on person: " + selectedPerson + " at index " + index); + } else { + logger.info("Clicked on an empty area of the ListView"); + } } /** diff --git a/src/main/java/bizbook/ui/PinnedPersonCard.java b/src/main/java/bizbook/ui/PinnedPersonCard.java new file mode 100644 index 00000000000..79df4a4872d --- /dev/null +++ b/src/main/java/bizbook/ui/PinnedPersonCard.java @@ -0,0 +1,41 @@ +package bizbook.ui; + +import java.util.Comparator; + +import bizbook.model.person.Person; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/** + * A UI component that displays information of a {@code PinnedPerson}. + */ +public class PinnedPersonCard extends UiPart { + private static final String FXML = "PinnedPersonCard.fxml"; + + @FXML + private VBox cardPane; + @FXML + private Label id; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public PinnedPersonCard(Person person, int displayedIndex) { + super(FXML); + id.setText(displayedIndex + ". "); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + person.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } +} diff --git a/src/main/java/bizbook/ui/PinnedPersonListPanel.java b/src/main/java/bizbook/ui/PinnedPersonListPanel.java new file mode 100644 index 00000000000..4afddc8baa3 --- /dev/null +++ b/src/main/java/bizbook/ui/PinnedPersonListPanel.java @@ -0,0 +1,68 @@ +package bizbook.ui; + +import java.util.logging.Logger; + +import bizbook.commons.core.LogsCenter; +import bizbook.model.person.Person; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; + +/** + * Panel containing the list of pinned persons. + */ +public class PinnedPersonListPanel extends UiPart { + private static final String FXML = "PinnedPersonListPanel.fxml"; + private static final Integer INITIAL_INDEX_VALUE = -1; + private final Logger logger = LogsCenter.getLogger(PinnedPersonListPanel.class); + + + @FXML + private ListView pinnedPersonListView; + + /** + * Creates a {@code PinnedPersonListPanel} with the given {@code ObservableList}. + */ + public PinnedPersonListPanel(ObservableList pinnedPersonList, ObjectProperty focusedPerson) { + super(FXML); + + pinnedPersonListView.setItems(pinnedPersonList); + pinnedPersonListView.setCellFactory(listView -> new PinnedPersonListViewCell()); + + // Add event handler + pinnedPersonListView.setOnMouseClicked(event -> handleListViewClick(event, focusedPerson)); + } + + private void handleListViewClick(MouseEvent event, ObjectProperty focusedPerson) { + int index = pinnedPersonListView.getSelectionModel().getSelectedIndex(); + Person selectedPerson = pinnedPersonListView.getSelectionModel().getSelectedItem(); + + if (index != INITIAL_INDEX_VALUE) { + focusedPerson.set(selectedPerson); + logger.info("Clicked on pinned person: " + selectedPerson + " at index " + index); + } else { + logger.info("Clicked on an empty area of the PinnedListView"); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PinnedPersonCard}. + */ + class PinnedPersonListViewCell extends ListCell { + @Override + protected void updateItem(Person person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PinnedPersonCard(person, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/bizbook/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/bizbook/ui/ResultDisplay.java index 7d98e84eedf..f896f5df7c8 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/bizbook/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package bizbook.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/bizbook/ui/SearchBox.java b/src/main/java/bizbook/ui/SearchBox.java new file mode 100644 index 00000000000..f39e6246d81 --- /dev/null +++ b/src/main/java/bizbook/ui/SearchBox.java @@ -0,0 +1,74 @@ +package bizbook.ui; + +import bizbook.logic.commands.FindCommand; +import bizbook.logic.commands.ListCommand; +import bizbook.logic.commands.exceptions.CommandException; +import bizbook.logic.parser.exceptions.ParseException; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import javafx.scene.layout.Region; + +/** + * The UI component that is responsible for receiving find command + * without requiring command word. + */ +public class SearchBox extends UiPart { + + private static final String FXML = "SearchBox.fxml"; + + private final CommandExecutor commandExecutor; + private final ChangeListener searchBoxChangeListener; + + @FXML + private TextField searchBoxField; + + /** + * Creates a {@code SearchBox} with the given {@code CommandExecutor}. + */ + public SearchBox(CommandExecutor commandExecutor) { + super(FXML); + this.commandExecutor = commandExecutor; + searchBoxChangeListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, String oldValue, String newValue) { + handleCommandUpdate(); + } + }; + searchBoxField.textProperty().addListener(searchBoxChangeListener); + } + + /** + * Handles the text field being updated. + */ + @FXML + private void handleCommandUpdate() { + String commandText = searchBoxField.getText(); + + if (commandText.isEmpty()) { + try { + commandExecutor.execute(ListCommand.COMMAND_WORD); + } catch (CommandException | ParseException e) { + return; + } + } + + try { + commandExecutor.execute(String.format("%s %s", + FindCommand.COMMAND_WORD, commandText)); + } catch (CommandException | ParseException e) { + return; + } + } + + /** + * Clears search box input. + */ + public void clearSearchBox() { + // temporarily disable listener before clearing field + searchBoxField.textProperty().removeListener(searchBoxChangeListener); + searchBoxField.setText(""); + searchBoxField.textProperty().addListener(searchBoxChangeListener); + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/bizbook/ui/StatusBarFooter.java similarity index 96% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/bizbook/ui/StatusBarFooter.java index b577f829423..8db428c3df2 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/bizbook/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package bizbook.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/bizbook/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/bizbook/ui/Ui.java index 17aa0b494fe..9c90292f675 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/bizbook/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package bizbook.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/bizbook/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/bizbook/ui/UiManager.java index fdf024138bc..56ab7bd241b 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/bizbook/ui/UiManager.java @@ -1,16 +1,16 @@ -package seedu.address.ui; +package bizbook.ui; import java.util.logging.Logger; +import bizbook.MainApp; +import bizbook.commons.core.LogsCenter; +import bizbook.commons.util.StringUtil; +import bizbook.logic.Logic; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; /** * The manager of the UI component. @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/BizBook.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/bizbook/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/bizbook/ui/UiPart.java index fc820e01a9c..169a237b4de 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/bizbook/ui/UiPart.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package bizbook.ui; import static java.util.Objects.requireNonNull; import java.io.IOException; import java.net.URL; +import bizbook.MainApp; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -29,6 +29,7 @@ public UiPart(URL fxmlFileUrl) { /** * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -45,6 +46,7 @@ public UiPart(URL fxmlFileUrl, T root) { /** * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { @@ -60,6 +62,7 @@ public T getRoot() { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. * @param root Specifies the root of the object hierarchy. */ diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 3149ee07e0b..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,86 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - - // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) - // log messages such as the one below. - // Lower level log messages are used sparingly to minimize noise in the code. - logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); - - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - logger.finer("This user input caused a ParseException: " + userInput); - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index bd1ca0f56c8..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tags = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/resources/images/BizBook.png b/src/main/resources/images/BizBook.png new file mode 100644 index 00000000000..5b1becb38a0 Binary files /dev/null and b/src/main/resources/images/BizBook.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..ae9dde2fbf3 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,29 @@ - - - - - + + + + + + + diff --git a/src/main/resources/view/CommandTablePanel.fxml b/src/main/resources/view/CommandTablePanel.fxml new file mode 100644 index 00000000000..660e7e735c5 --- /dev/null +++ b/src/main/resources/view/CommandTablePanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ContactDetails.fxml b/src/main/resources/view/ContactDetails.fxml new file mode 100644 index 00000000000..97fc58afb5b --- /dev/null +++ b/src/main/resources/view/ContactDetails.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..454b31ba8bf 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -39,6 +39,11 @@ -fx-max-height: 0; } +/* Solution below adapted from https://stackoverflow.com/a/40003066/4428725 */ +.scroll-pane .viewport { + -fx-background-color: transparent; +} + .table-view { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; @@ -57,10 +62,10 @@ -fx-border-width: 0 0 1 0; -fx-background-color: transparent; -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; + transparent + transparent + derive(-fx-base, 80%) + transparent; -fx-border-insets: 0 10 1 0; } @@ -132,20 +137,36 @@ -fx-text-fill: #010504; } +.pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#1d1d1d, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; +} +.pane-with-person-info{ + -fx-background-color: derive(#1d1d1d, 20%); + -fx-text-fill: white; + -fx-padding: 4px; + -fx-pref-height: 30px; } - .status-bar { -fx-background-color: derive(#1d1d1d, 30%); } +.search-bar { + -fx-background-color: derive(#1d1d1d, 50%); + -fx-border-color: derive(#1d1d1d, 50%);; + -fx-border-top-width: 2px; + -fx-text-fill: white; +} + .result-display { -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; @@ -179,11 +200,19 @@ -fx-border-color: derive(#1d1d1d, 30%); -fx-border-width: 1px; } - .grid-pane .stack-pane { -fx-background-color: derive(#1d1d1d, 30%); } +/* ScrollPane styling */ +.scroll-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.scroll-pane > .viewport { + -fx-background-color: derive(#1d1d1d, 20%); +} + .context-menu { -fx-background-color: derive(#1d1d1d, 50%); } @@ -229,8 +258,8 @@ } .button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-background-color: white; + -fx-text-fill: #1d1d1d; } .button:focused { @@ -343,10 +372,53 @@ } #tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-text-fill: black; + -fx-background-color: gold; -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; + -fx-border-radius: 5; + -fx-border-color: black; + -fx-background-radius: 5; -fx-font-size: 11; } +/* Details Panel */ +#notes-label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +#notes-header { + -fx-font-size: 25pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + + +.error { + -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; + -fx-border-radius: 3; + -fx-background-radius: 3; +} + +.tooltip-text { + -fx-text-fill: white; +} + +.pinned-person-item { + -fx-background-color: #444; + -fx-background-radius: 1px; + -fx-max-width: 180; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); +} diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..e69de29bb2d 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,20 +0,0 @@ - -.error { - -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; - -fx-border-radius: 3; - -fx-background-radius: 3; -} - -.tooltip-text { - -fx-text-fill: white; -} diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css deleted file mode 100644 index 17e8a8722cd..00000000000 --- a/src/main/resources/view/HelpWindow.css +++ /dev/null @@ -1,19 +0,0 @@ -#copyButton, #helpMessage { - -fx-text-fill: white; -} - -#copyButton { - -fx-background-color: dimgray; -} - -#copyButton:hover { - -fx-background-color: gray; -} - -#copyButton:armed { - -fx-background-color: darkgray; -} - -#helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); -} diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index e01f330de33..f900adfb1a1 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -6,39 +6,47 @@ + + - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpWindowDark.css b/src/main/resources/view/HelpWindowDark.css new file mode 100644 index 00000000000..fe4a29f791d --- /dev/null +++ b/src/main/resources/view/HelpWindowDark.css @@ -0,0 +1,50 @@ +#openButton, +#helpMessage { + -fx-text-fill: white; +} + +#openButton { + -fx-background-color: dimgray; +} + +#openButton:hover { + -fx-background-color: gray; +} + +#openButton:armed { + -fx-background-color: darkgray; +} + +#helpMessageContainer { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.help-textbox { + -fx-text-fill: white; + -fx-fill: white; +} + +#errorMessageLabel { + -fx-text-fill: red; /* Makes the text red */ + -fx-padding: 0 0 0 10px; /* Adds 10px padding to the left */ +} + +.table-cell { + -fx-text-fill: white; + -fx-font-family: "Segoe UI Semibold"; + -fx-wrap-text: true; +} + +.table-row-cell:filled:even { + -fx-background-color: #3c3e3f; +} + +.table-row-cell:filled:odd { + -fx-background-color: #515658; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: #424d5f; + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} diff --git a/src/main/resources/view/HelpWindowLight.css b/src/main/resources/view/HelpWindowLight.css new file mode 100644 index 00000000000..ff6f63afdd2 --- /dev/null +++ b/src/main/resources/view/HelpWindowLight.css @@ -0,0 +1,51 @@ +#openButton, +#helpMessage { + -fx-text-fill: black; +} + +#openButton { + -fx-background-color: lightgray; +} + +#openButton:hover { + -fx-background-color: gainsboro; +} + +#openButton:armed { + -fx-background-color: silver; +} + +#helpMessageContainer { + -fx-background-color: derive(#f0f0f0, -20%); + +} + +#errorMessageLabel { + -fx-text-fill: red; + -fx-padding: 0 0 0 10px; +} + +#help-textbox { + -fx-text-fill: black; + +} + +.table-cell { + -fx-text-fill: black; + -fx-font-family: "Segoe UI Semibold"; + -fx-wrap-text: true; +} + +.table-row-cell:filled:even { + -fx-background-color: #f9f9f9; +} + +.table-row-cell:filled:odd { + -fx-background-color: #eaeaea; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: #cce7ff; + -fx-border-color: #88b9e0; + -fx-border-width: 1; +} diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css new file mode 100644 index 00000000000..000eb2b4595 --- /dev/null +++ b/src/main/resources/view/LightTheme.css @@ -0,0 +1,407 @@ +.background { + -fx-background-color: #f4f4f4; + background-color: #ffffff; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #333333; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #000000; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; + -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: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: #e0e0e0; + -fx-table-header-border-color: #e0e0e0; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: #f0f0f0; +} + +.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 + #c0c0c0 + 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: #333333; + -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: #e0e0e0; + -fx-border-color: transparent transparent transparent #c0c0c0; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: #ffffff; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: #ffffff; +} + +.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: #f8f8f8; +} + +.list-cell:filled:odd { + -fx-background-color: #ffffff; +} + +.list-cell:filled:selected { + -fx-background-color: #e0e0e0; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: #333333; +} + +.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; +} + +.pane { + -fx-background-color: #ffffff; +} + +.stack-pane { + -fx-background-color: #ffffff; +} + +.pane-with-border { + -fx-background-color: #ffffff; + -fx-border-color: #e0e0e0; + -fx-border-top-width: 1px; +} + +.pane-with-person-info { + -fx-background-color: #ffffff; + -fx-text-fill: #333333; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + + +.search-bar { + -fx-background-color: derive(#ffffff, 80%); + -fx-border-color: derive(#000000, 30%); + -fx-border-top-width: 2px; + -fx-text-fill: black; +} + +.status-bar { + -fx-background-color: #f0f0f0; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #333333; +} + +.result-display .label { + -fx-text-fill: #333333 !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: #f0f0f0; + -fx-border-color: #e0e0e0; + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: #333333; +} + +.grid-pane { + -fx-background-color: #ffffff; + -fx-border-color: #e0e0e0; + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: #ffffff; +} + +.context-menu { + -fx-background-color: #f0f0f0; +} + +.context-menu .label { + -fx-text-fill: #333333; +} + +.menu-bar { + -fx-background-color: #f0f0f0; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: #ffffff; +} + +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #333333; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #e0e0e0; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: #333333; + -fx-text-fill: #ffffff; +} + +.button:focused { + -fx-border-color: #333333, #333333; + -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: #ffffff; + -fx-text-fill: #333333; +} + +.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: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #333333; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: #f0f0f0; +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: #333333; + -fx-text-fill: #333333; +} + +.scroll-bar { + -fx-background-color: #f0f0f0; +} + +.scroll-bar .thumb { + -fx-background-color: #c0c0c0; + -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: #2196F3; +} + +#commandTextField { + -fx-background-color: transparent #ffffff transparent #ffffff; + -fx-background-insets: 0; + -fx-border-color: #ffffff #ffffff #333333 #ffffff; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #333333; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, #e0e0e0, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffffff, transparent, #ffffff; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: #ffffff; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 5; + -fx-border-color: #3e7b91; + -fx-background-radius: 5; + -fx-font-size: 11; +} + +#notes-label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #333333; + -fx-opacity: 1; +} + +#notes-header { + -fx-font-size: 25pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: #333333; + -fx-opacity: 1; +} + +.pinned-person-item { + -fx-background-color: #f0f0f0; + -fx-background-radius: 1px; + -fx-max-width: 180; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 10, 0, 0, 0); +} + +.error { + -fx-text-fill: #d32f2f !important; +} + +.list-cell:empty { + -fx-background: #ffffff; +} + +.tag-selector { + -fx-border-width: 1; + -fx-border-color: #333333; + -fx-border-radius: 3; + -fx-background-radius: 3; +} + +.tooltip-text { + -fx-text-fill: #333333; +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..11b5f22335e 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,21 +6,20 @@ - + + title="BizBook" minWidth="750" minHeight="600" onCloseRequest="#handleExit"> - + - @@ -28,30 +27,47 @@ + + + - + - + + + + + + + + + + + + + + + - + - + - + - - + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index 84e09833a87..9bc35f6c655 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -25,12 +25,13 @@ -