diff --git a/.gitignore b/.gitignore index 71c9194e8bd..fd75c7da3bc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,15 @@ src/main/resources/docs/ /out/ /*.iml +# VSCode files +/.vscode/ + # Storage/log files /data/ /config.json /preferences.json /*.log.* +/bin/ # Test sandbox files src/test/data/sandbox/ diff --git a/README.md b/README.md index 13f5c77403f..423c8ea4c92 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,31 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# Swift+ + +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T12-2/tp/branch/master/graph/badge.svg?token=A2FU6P932B)](https://app.codecov.io/gh/AY2223S1-CS2103T-T12-2/tp) +[![CI Status](https://github.com/AY2223S1-CS2103T-T12-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T12-2/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +### Manage your Team like never before + +#### Team: Swift+ (CS2103-T12-02) + +- Swift+ is a seamless contact management system to keep track of their interactions and meetings with clients and colleagues using swift text-based commands. +- Swift+ allows you to plan your schedules looking at their daily/weekly overview. + +## Getting Started + +1. Download and install `jenv` +2. Install `Java 11` +3. Clone the project + +## Site Map + +- [User Guide](docs/UserGuide.md) +- [Developer Guide](docs/DeveloperGuide.md) +- [About Us](docs/AboutUs.md) + +## Acknowledgements + +- This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). +- Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +- UI color scheme inspired by [TailwindUI](https://tailwindui.com/) diff --git a/build.gradle b/build.gradle index 108397716bd..93513babb8a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'jacoco' } -mainClassName = 'seedu.address.Main' +mainClassName = 'swift.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -25,6 +25,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'swift+.jar' } defaultTasks 'clean', 'test' diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..ab45192b6ad --- /dev/null +++ b/codecov.yml @@ -0,0 +1,10 @@ +coverage: + status: + patch: + default: + target: 0% + threshold: 50% + project: + default: + target: auto + threshold: 75% diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..729bbc99e41 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,55 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `jason.qiu@u.nus.edu` -## Project team +## **Project team** -### John Doe +### Jason Qiu - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/jasonqiu212)] +[[portfolio](team/jasonqiu212.md)] -* Role: Project Advisor +- Role: Team lead +- Responsibilities: UI -### Jane Doe +### Pontakorn Prasertsuk - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/peppapighs)] +[[portfolio](team/peppapighs.md)] -* Role: Team Lead -* Responsibilities: UI +- Role: Code Quality +- Responsibilities: UI -### Johnny Doe +### Shenyi Cui - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/shenyicui)] [[portfolio](team/shenyicui.md)] -* Role: Developer -* Responsibilities: Data +- Role: Deliverables +- Responsibilities: Issues -### Jean Doe +### Muthukrishnan Santosh - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/santosh3007)] +[[portfolio](team/santosh3007.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +- Role: Documentation +- Responsibilities: Manage Non-Functional Requirements -### James Doe +### Chin Yun Ru - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/yunruu)] +[[portfolio](team/yunruu.md)] -* Role: Developer -* Responsibilities: UI +- Role: Integration +- Responsibilities: Glossary diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..22c8b4937fd 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,6 @@ --- layout: page -title: Configuration guide +title: Configuration Guide --- -Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). +Certain properties of the application can be controlled (e.g. user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index 26354050fa4..da7a42ad68e 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,53 +1,54 @@ --- layout: page -title: DevOps guide +title: DevOps Guide --- -* Table of Contents -{:toc} +## Table of Contents +{:.no_toc} --------------------------------------------------------------------------------------------------------------------- +1. Table of Contents +{:toc} -## Build automation +--- -This project uses Gradle for **build automation and dependency management**. **You are recommended to read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**. +## **Build Automation** +This project uses Gradle for **build automation and dependency management**. **You are recommended to read this [Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**. Given below are how to use Gradle for some important project tasks. - -* **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
+- **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
e.g. `./gradlew clean` -* **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
+- **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, _if the current file is outdated_.
e.g. `./gradlew shadowJar`. -* **`run`**: Builds and runs the application.
+- **`run`**: Builds and runs the application.
**`runShadow`**: Builds the application as a fat JAR, and then runs it. -* **`checkstyleMain`**: Runs the code style check for the main code base.
+- **`checkstyleMain`**: Runs the code style check for the main code base.
**`checkstyleTest`**: Runs the code style check for the test code base. -* **`test`**: Runs all tests.< - * `./gradlew test` — Runs all tests - * `./gradlew clean test` — Cleans the project and runs tests +- **`test`**: Runs all tests.< + - `./gradlew test` — Runs all tests + - `./gradlew clean test` — Cleans the project and runs tests --------------------------------------------------------------------------------------------------------------------- +--- -## Continuous integration (CI) +## **Continuous Integration (CI)** This project uses GitHub Actions for CI. The project comes with the necessary GitHub Actions configurations files (in the `.github/workflows` folder). No further setting up required. -### Code coverage +### Code Coverage As part of CI, this project uses Codecov to generate coverage reports. When CI runs, it will generate code coverage data (based on the tests run by CI) and upload that data to the CodeCov website, which in turn can provide you more info about the coverage of your testes. Here are the steps to set up CodeCov for a fork of this repository. 1. Sign up with Codecov using your GitHub account [here](https://codecov.io/signup). -1. Once you are inside Codecov web app, add your org (that contains the fork) to CodeCov. -1. Wait for the next run of CI in your fork (or push a dummy commit to it to trigger CI) to confirm CI is able to upload generated coverage data to CodeCov. If CodeCov is not set up correctly, the CI run will fail with an error message that mentions CodeCov. -1. Get the Markdown code for the Codecov badge provided in `Settings > Badges` and update the `docs/index.md` of your repo with it so that the badge [![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) in that page reflects the coverage of your project. +2. Once you are inside Codecov web app, add your org (that contains the fork) to CodeCov. +3. Wait for the next run of CI in your fork (or push a dummy commit to it to trigger CI) to confirm CI is able to upload generated coverage data to CodeCov. If CodeCov is not set up correctly, the CI run will fail with an error message that mentions CodeCov. +4. Get the Markdown code for the Codecov badge provided in `Settings > Badges` and update the `docs/index.md` of your repo with it so that the badge [![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T12-2/tp/branch/master/graph/badge.svg?token=A2FU6P932B)](https://app.codecov.io/gh/AY2223S1-CS2103T-T12-2/tp) in that page reflects the coverage of your project. -### Repository-wide checks +### Repository-wide Checks In addition to running Gradle checks, CI includes some repository-wide checks. Unlike the Gradle checks which only cover files used in the build process, these repository-wide checks cover all files in the repository. They check for repository rules which are hard to enforce on development machines such as line ending requirements. @@ -59,22 +60,23 @@ Any warnings or errors will be printed out to the console. **If adding new checks:** -* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest. +- Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest. + +- Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE` -* Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE` - * SEVERITY is either ERROR or WARN. - * FILENAME is the path to the file relative to the current directory. - * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. + - SEVERITY is either ERROR or WARN. + - FILENAME is the path to the file relative to the current directory. + - LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error. -* Check scripts must exit with a non-zero exit code if any errors occur. +- Check scripts must exit with a non-zero exit code if any errors occur. --------------------------------------------------------------------------------------------------------------------- +--- -## Making a release +## **Making a release** 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. 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. +1. Update the version number in [`MainApp.java`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/MainApp.java). +2. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`). +3. Tag the repo with the version number. e.g. `v0.1` +4. [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 46eae8ee565..3fe768efcc6 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,107 +2,116 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- +## **Table of Contents** -## **Acknowledgements** +{:.no_toc} -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +1. Table of Contents +{:toc} --------------------------------------------------------------------------------------------------------------------- +--- -## **Setting up, getting started** +## **Setting up and getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +Refer to the guide [Setting up and getting started](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design** -
- -:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/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. +
:bulb: **Tip**
+The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2223S1-CS2103T-T12-2/tp/tree/master/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 Components of the Architecture -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup methods where necessary. +**`Main`** has two classes called [`Main`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/Main.java) and [`MainApp`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/MainApp.java). It is responsible for, -[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +- initializing the components in the correct sequence and connecting them up with each other during the app's launch. +- shutting down the components and invoking cleanup methods where necessary when closing the app. -The rest of the App consists of four components. +The rest of the app consists of 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): Displays the user interface of the app. +- [**`Logic`**](#logic-component): Parses and executes the commands. +- [**`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 components. -**How the architecture components interact with each other** +#### 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_contact 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. -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. +For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class. Other components interact with a given component through its interface, rather than the concrete class. The reasoning is to prevent outside component's from being coupled to the implementation of a component, which is illustrated by the diagram below. The sections below give more details of each component. -### UI component +### UI Component + +The API of this component is specified in [`Ui.java`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/ui/Ui.java). -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) +Here's a partial class diagram of the `UI` component without any of the task/contact management panels. -![Structure of the UI Component](images/UiClassDiagram.png) +![Structure of the UI Component without Panels](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 consists of a `MainWindow` that is made up of parts, e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, and etc. All these parts, 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) +Here's another partial class diagram of the `UI` component with the task and contact management panels. + +![Structure of the UI Component with Panels](images/UiPanelsClassDiagram.png) + +The UI keeps track of which tab the user is currently viewing with the `isContactTabShown` boolean. If the contacts tab is currently in view, `MainWindow` contains `PersonListPanel` and `PersonTaskListPanel`, and it contains `TaskListPanel` and `TaskPersonListPanel` if otherwise. + +The `UI` component uses the [JavaFx](https://openjfx.io/) 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/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-T12-2/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. +- prompts users with command suggestions and allows them to auto-complete them 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` and `Task` objects located in the `Model`. -### Logic component +### Logic Component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +The API of this component is specified in [`Logic.java`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/logic/Logic.java). -Here's a (partial) class diagram of the `Logic` component: +Here's a partial class diagram of the `Logic` component: How the `Logic` component works: + 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddContactCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to add a person). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The sequence diagram below illustrates the interactions within the `Logic` component for the `execute("delete_contact 1")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete_contact 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 reaches the end of diagram. +
:information_source: **Note**
+The lifeline for `DeleteContactCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -110,268 +119,569 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha 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. -### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +- 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., `AddContactCommandParser`) +- The `XYZCommandParser` class then uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddContactCommand`), which the `AddressBookParser` returns back as a `Command` object. +- All `XYZCommandParser` classes (e.g., `AddContactCommandParser`, `DeleteTaskCommandParser`, ...) inherit from the `Parser` interface, so that they can be treated similarly where possible, e.g. during testing. + +### Model Component + +The API of this component is specified in [`Model.java`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/model/Model.java). + +Here's a partial class diagram of the `Model` component: +
:information_source: **Note**
+The `Task` and `PersonTaskBridge` classes are left out of the above diagram for simplicity. Compared to the `Person` class, they follow a similar structure of attribute composition. +
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` object. +- does not depend on any of the other three components, because the `Model` represents data entities of the domain, and 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 -### Storage component +The API of this component is specified in [`Storage.java`](https://github.com/AY2223S1-CS2103T-T12-2/tp/blob/master/src/main/java/swift/storage/Storage.java). -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +Here's a partial class diagram of the `Storage` component: The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* 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.addressbook.commons` package. +Classes used by multiple components are in the `swift.commons` package. --------------------------------------------------------------------------------------------------------------------- +--- ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Many-to-many relationship between `Person` and `Task` -#### Proposed Implementation +The implementation of the contact-task relation is facilitated by `PersonTaskBridge` and `PersonTaskBridgeList`. -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: +`PersonTaskBridge` is a class containing a `Person` UUID and a `Task` UUID, representing a relation between a `Person` and a `Task`. -* `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. +`PersonTaskBridgeList` is a class containing a list of `PersonTaskBridge` objects, representing all the relations between `Person` and `Task` objects in the `AddressBook`. Additionally, it implements the following operations: -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +- `PersonTaskBridgeList#add(PersonTaskBridge)` - Saves a new relation between a `Person` and a `Task` in the list. +- `PersonTaskBridgeList#remove(PersonTaskBridge)` - Removes an existing relation between a `Person` and a `Task` from the list. +- `PersonTaskBridgeList#removePerson(Person)` and `PersonTaskBridgeList#removeTask(Task)` - Removes all existing relations between a `Person` and `Task` objects from the list. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +These operations will be exposed in the `Model` interface. -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. +The following class diagram summarizes the relationship between `PersonTaskBridge` and other classes: -![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. +#### Design Considerations -![UndoRedoState1](images/UndoRedoState1.png) +**Aspect: How `Person` and `Task` are associated with `PersonTaskBridge`** -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`. +- **Alternative 1 (current choice):** Stores `Person` and `Task` UUID in `PersonTaskBridge`. -![UndoRedoState2](images/UndoRedoState2.png) + - Pros: No need to handle the case of changing index when `Person` or `Task` are filtered. Easier to maintain data integrity. + - Cons: Requires changes in `Person` and `Task` schema and storage. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +- **Alternative 2:** Stores `Person` and `Task` index in `PersonTaskBridge`. + - Pros: No change is needed for `Person` and `Task` schema. + - Cons: Requires changes to `PersonTaskBridge` objects every time a command changes `Person` or `Task` object index. -
+### Optional `Description` and `Deadline` Fields -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. +The `Description` and `Deadline` fields for tasks are optional for the users fill in. The implementation of this optionality is +facilitated by wrapping the values using the [`java.util.Optional`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html) class. +By doing so, we took advantage of the provided methods, e.g. `orElse`, `or`, and etc. This `Optional` class thus helps to encapsulate +the logic of methods that depend on the presence or absence of the contained value. -![UndoRedoState3](images/UndoRedoState3.png) +#### Difference from Optional `Tag` -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +It is also optional for a `Person` to have tags. To achieve this, a `Person` stores the tags in a `HashSet`. +If no tags are assigned to a `Person`, the `HashSet` will be empty. By doing so, a `Person` can have any number of tags. -
+This differs from the implementation of optionality for `Description` and `Deadline`. For `Description` and `Deadline`, +a `Task` can either contain the value or no value at all. Thus, due to the differing multiplicities, we could not use the +same implementation as tags. -The following sequence diagram shows how the undo operation works: +### View Task Details -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +The implementation of the task tab UI is facilitated by `TaskCard` and `TaskListPanel`. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +`TaskCard` and `TaskListPanel` extends the superclass `UiPart` and fills the UI container with a panel that displays the list of tasks, along with their assigned contacts and deadlines. -
+`TaskListPanel` in is responsible for displaying the graphics of a task using a `TaskCard`. -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. +### Command Suggestions and Command Auto-Completion -
: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. +The implementation of Command Suggestions and Command Auto-Completion is facilitated by `CommandSuggestor` in the `Logic` Component. The `CommandBox` UI component listens for changes in the command box textField and calls methods from `CommandSuggestor` to reflect command suggestions and allow autocompletion. -
+`CommandSuggestor` mainly implements the following operations: -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. +- `CommandSuggestor#suggestCommand` - Suggests a command with the corresponding syntax based on the user's current input +- `CommandSuggestor#autocompleteCommand` - Completes the current user input according to the shown command suggestion -![UndoRedoState4](images/UndoRedoState4.png) +#### Design Considerations -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. +**Aspect: How to provide command suggestions to users** -![UndoRedoState5](images/UndoRedoState5.png) +- **Alternative 1 (current choice):** Provide command suggestion over the command box. -The following activity diagram summarizes what happens when a user executes a new command: + - Pros: Uses less screen real estate + - Cons: Only able to view one possible command - +- **Alternative 2:** Provide command suggestions in a separate display box + itself. + - Pros: Able to display all possible commands + - Cons: Uses more screen real estate -#### Design considerations: +**Aspect: How to autocomplete commands for users** -**Aspect: How undo & redo executes:** +- **Alternative 1:** Autocomplete up to next prefix according displayed command suggestion. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + - Pros: Users can easily autocomplete the command shown with just one tab + - Cons: Users might have to backspace and complete the command again for commands with common prefixes. Eg. `add_contact`, `add_task` -* **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. +- **Alternative 2 (current choice):** Autocomplete up to the longest matching prefix of all possible commands. + - Pros: Easy to autocomplete commands with common prefixes + - Cons: Users might have to type a few characters more -_{more aspects and alternatives to be added}_ +--- -### \[Proposed\] Data archiving +## **Documentation, Logging, Dev-ops, Testing, and Configuration** -_{Explain here how the data archiving feature will be implemented}_ +To understand how to set up and maintain this project website, head over to the [Documentation Guide](Documentation.md). +You can learn how to run tests on Swift+ by going to the [Testing Guide](Testing.md) page. --------------------------------------------------------------------------------------------------------------------- +To learn how to run and release Swift+ using Gradle, please visit the [DevOps Guide](DevOps.md) page. -## **Documentation, logging, testing, configuration, dev-ops** +Please visit the [Logging Guide](Logging.md) to learn how we implement logging. -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +We also have files to configure properties of the app, which are detailed in the [Configuration Guide](Configuration.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Requirements** -### Product scope +This section covers the user requirements we attempt to meet in Swift+. -**Target user profile**: +### Target User Profile -* 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 +Swift+ is designed for **software engineering project leads** who, -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +- need to keep track of many tasks with clients and colleagues. +- can type fast. +- prefer typing to mouse interactions. +- prefer desktop apps over other types. +### Value proposition + +Swift+ allows users to manage tasks with clients and colleagues **faster** than a typical GUI-driven app. ### User stories -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +Priority levels: + +- High (must have) - `* * *` +- Medium (nice to have) - `* *` +- Low (unlikely to have) - `*` + +| Priority | As a …​ | I want to …​ | So that I can…​ | +| -------- | -------------- | ------------------------ | -------------------------------------------------------------------------- | +| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the app | +| `* * *` | user | add a new contact | add a new contact to keep track of | +| `* * *` | user | view all contacts | get an overview of all contacts in my app | +| `* * *` | user | update a contact | update the particulars of a contact | +| `* * *` | user | delete a contact | remove contacts that I no longer need | +| `* * *` | user | find contacts by name | locate details of contacts without having to go through the entire list | +| `* * *` | user | add task for contact | add a task to a contact to keep track of | +| `* * *` | user | view tasks by contact | view tasks belonging to a contact | +| `* * *` | user | delete a task | remove tasks that I no longer need | +| `* *` | user | update a task | update the particulars of a task | +| `* *` | user | list all tasks | get an overview of all tasks in my app | +| `* *` | user | find tasks by name | locate details of tasks without having to go through the entire list | +| `* *` | forgetful user | autocomplete my commands | conveniently type commands without referring to the user guide excessively | -| 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 | +### Use cases -*{More to be added}* +For all use cases below, the system is `Swift+` and the actor is the `user`, unless specified otherwise. -### Use cases +**Use case: UC1 - Create a contact** + +MSS: + +1. User requests to add a contact. +2. Swift+ creates the contact. + +Use case ends. + +Extensions: + +- 1a. Swift+ detects an error in the entered data. + - 1a1. Swift+ requests for the correct data. + - Use case resumes from step 1. + +**Use case: UC2 - Update a contact** + +MSS: + +1. User requests to view all contacts. +2. Swift+ returns a list of all contacts. +3. User requests to edit a specific contact in the list. +4. Swift+ edits the details of the specified contact. + +Use case ends. + +Extensions: + +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. +- 3b. Swift+ detects an error in the entered data. + - 3b1. Swift+ requests for the correct data. + - Use case resumes from step 3. + +**Use case: UC3 - Delete a person** + +MSS: + +1. User requests to view all contacts. +2. Swift+ returns a list of all contacts. +3. User requests to delete a specific contact in the list. +4. Swift+ deletes the specified contact. + +Use case ends. + +Extensions: + +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. + +**Use case: UC4 - Create a task** + +MSS: + +1. User requests to add a task. +2. Swift+ creates the task. + +Use case ends. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +Extensions: -**Use case: Delete a person** +- 1a. Swift+ detects an error in the entered data. + - 1a1. Swift+ requests for the correct data. + - Use case resumes from step 1. -**MSS** +**Use case: UC5 - Update a task** -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 +MSS: - Use case ends. +1. User requests to view all tasks. +2. Swift+ returns a list of all tasks. +3. User requests to edit a specific task in the list. +4. Swift+ edits the details of the specified task. + +Use case ends. -**Extensions** +Extensions: -* 2a. The list is empty. +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. +- 3b. Swift+ detects an error in the entered data. + - 3b1. Swift+ requests for the correct data. + - Use case resumes from step 3. - Use case ends. +**Use case: UC6 - Delete a task** -* 3a. The given index is invalid. +MSS: - * 3a1. AddressBook shows an error message. +1. User requests to view all tasks. +2. Swift+ returns a list of all tasks. +3. User requests to delete a specific task in the list. +4. Swift+ deletes the specified task. - Use case resumes at step 2. +Use case ends. -*{More to be added}* +Extensions: + +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. + +**Use case: UC7 - View tasks associated with a contact** + +MSS: + +1. User requests to view all contacts. +2. Swift+ returns a list of all contacts. +3. User requests to view tasks associated with a specified contact. +4. Swift+ returns the contact and all associated tasks. + +Extensions: + +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. + +**Use case: UC8 - Mark a task as completed** + +MSS: + +1. User requests to view all tasks. +2. Swift+ returns a list of all tasks. +3. User requests to mark a task as completed. +4. Swift+ marks the specified task as completed. + +Extensions: + +- 2a. Swift+ returns an empty list. + - Use case ends. +- 3a. Swift+ detects the given index to be invalid. + - 3a1. Swift+ requests for a valid index. + - Use case resumes from step 3. +- 3b. Swift+ detects that the specified task is already completed. + - Swift+ indicates that task is already completed. + - Use case ends. + +**Use case: UC9 - Assign a task to a contact** + +MSS: + +1. User requests to view all tasks and contacts. +2. Swift+ returns all tasks and contacts. +3. User requests to assign a task to a contact. +4. Swift+ assigns the specified task to the specified contact. + +Extensions: + +- 2a. Swift+ returns an empty list of contacts. + - Use case ends. +- 2b. Swift+ returns an empty list of tasks. + - Use case ends. +- 3a. Swift+ detects given contact or task index to be invalid. + - Swift+ requests for a valid index. + - Use case resumes from step 3. +- 3b. Swift+ detects that the specified task is already assigned to the specified contact. + - Swift+ indicates that task is already assigned to the contact. + - Use case ends. ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 1000 tasks and contacts without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -*{More to be added}* +4. Data should be persistent and stored in the local machine's storage. +5. Product is designed for single user and is not required to handle collaboration between multiple users. ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +- **API**: Stands for application programming interface, which is a set of definitions and protocols for building and integrating application software. +- **Bridge**: Maps a relationship between a contact and a task. +- **GUI**: Stands for graphical user interface, which is a system interface that uses visual icons, menus, and a mouse to manage interactions with the system. +- **Mainstream OS**: Stands for mainstream operating systems, which includes Windows, Linux, Unix, and OS-X. +- **UUID**: Stands for universally unique identifier, which is used for identifying information that needs to be unique within a system. --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Instructions for manual testing** Given below are instructions to test the app manually. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; +
:information_source: **Note**
+These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing. -
### Launch and shutdown 1. Initial launch - 1. Download the jar file and copy into an empty folder - - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Download the jar file and copy into an empty folder. + 2. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -1. Saving window preferences +2. Saving window preferences 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 2. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. + +### Adding a Task + +1. Adding a Task + + 1. Prerequisites: task "Foo" isn't already added. If added, delete it first. + 2. Test case: `add_task n/Foo d/Foo`
+ Expected: Task "Foo" is added to the task list. Details of the added task shown in the status message. + 3. Test case: `add_task n/Foo d/Foo`
+ Expected: No task is added. Error details shown in the status message. + +2. Adding a Task with deadline + 1. Prerequisites: deadline "Bar" isn't already added. If added, delete it first. + 2. Test case: `add_task n/Bar d/Bar dl/02-02-2022 2200`
+ Expected: Deadline "Bar" is added to the task list. Details of the added deadline shown in the status message. + 3. Test case: `add_task n/Bar d/Bar dl/02-02-2022 2200`
+ Expected: No deadline is added. Error details shown in the status message. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +### Mark / Unmark a Task -1. _{ more test cases …​ }_ +1. Marking a Task as completed -### Deleting a person + 1. Prerequisites: task "Foo" is already added. If not added, add it first. Foo should be the first index in the task list. + 2. Test case: `mark 1`
+ Expected: Task "Foo" is marked as completed. Details of the completed task shown in the status message. + 3. Test case: `mark 1`
+ Expected: No task is marked as completed. Error details shown in the status message. + 4. Test case: `mark 0`
+ Expected: No task is marked as completed. Error details shown in the status message. -1. Deleting a person while all persons are being shown +2. Unmarking a Task as incomplete - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: task "Bar" is already added. If not added, add it first. Foo should be the first index in the task list and already marked as complete. + 2. Test case: `unmark 1`
+ Expected: Task "Bar" is marked as uncompleted. Details of the uncompleted task shown in the status message. + 3. Test case: `unmark 1`
+ Expected: No task is marked as uncompleted. Error details shown in the status message. + 4. Test case: `unmark 0`
+ Expected: No task is marked as uncompleted. Error details shown in the status message. - 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. +### Switching Lists - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +1. List all tasks - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Test case: `list_task`
+ Expected: All tasks are listed in the task list and, if not already on the "Task List" view it's swapped to it. Details of the listed tasks shown in the status message. + 2. Test case: `list_contact`
+ Expected: All contacts are listed in the contact list and, if not already on the "Contact List" view it's swapped to it. Details of the listed contacts shown in the status message. + 3. Test case: type `ctrl + tab`
+ Expected: The current list is switched to the other list. + +### Viewing Task and Contact Assocation + +1. Select Task to view Contact Assocation + + 1. Prerequisites: task "Foo" is already added and assigned to contact "Alex". If not added, add it first. Foo should be the first index in the task list. + 2. Test case: `select_task 1`
+ Expected: Task "Foo" is selected, the view is swapped to the "Task List" and the contact list is updated to show all contacts associated with the task. Details of the selected task shown in the status message. + 3. Test case: `select_task 0`
+ Expected: No task is selected. Error details shown in the status message. + +2. Select Contact to view Task Association + + 1. Prerequisites: contact "Alex" is already added and assigned to task "Foo". If not added, add it first. Alex should be the first index in the contact list. + 2. Test case: `select_contact 1`
+ Expected: Contact "Alex" is selected, the view is swapped to the "Contact List" and the task list is updated to show all tasks associated with the contact. Details of the selected contact shown in the status message. + 3. Test case: `select_contact 0`
+ Expected: No contact is selected. Error details shown in the status message. + +### Finding Task or Contact + +1. Find Task + + 1. Prerequisites: task "Foo" is already added and "Bar" is not added. If not added, add it first. + 2. Test case: `find_task Foo`
+ Expected: All tasks containing "Foo" are listed in the task list. Details of the listed tasks shown in the status message. + 3. Test case: `find_task Bar`
+ Expected: No tasks are listed in the task list. Details of the listed tasks shown in the status message. + +2. Find Contact + 1. Prerequisites: contact "Alex" is already added and "Bob is not added". If not added, add it first. + 2. Test case: `find_contact Alex`
+ Expected: All contacts containing "Alex" are listed in the contact list. Details of the listed contacts shown in the status message. + 3. Test case: `find_contact Bob`
+ Expected: No contacts are listed in the contact list. Details of the listed contacts shown in the status message. + +### Deleting a Task or Contact + +1. Deleting a Contact or Task while all Contact or Task are being shown + + 1. Prerequisites: List all Contacts or Tasks using the `list_contact` or `list_task` command. + 2. Test case: `delete_contact 1` or `delete_task 1`
+ Expected: First Contact or Task is deleted from the list. Details of the deleted Contact or Task shown in the status message. + 3. Test case: `delete_contact 0` or `delete_task 0`
+ Expected: No Contact, Task is deleted. Error details shown in the status message. Status bar remains the same. + 4. Other incorrect delete commands to try: `delete_contact` / `delete_task`, `delete_contact x` / `delete_task x`, `...` (where x is larger than the list size)
Expected: Similar to previous. -1. _{ more test cases …​ }_ +### Editing a Task or Contact + +1. Editing a Contact or Task while all Contact or Task are being shown + + 1. Prerequisites: List all Contacts or Tasks using the `list_contact` or `list_task` command. There should be at least one contact or task. + 2. Test case: `edit_contact 1 n/Alex Yeoh p/98765432` + Expected: First Contact is edited to have the name "Alex Yeoh" and phone number "98765432". Details of the edited Contact shown in the status message. + 3. Test case: `edit_contact 0 n/Alex Yeoh p/98765432`
+ Expected: No Contact is edited. Error details shown in the status message. Status bar remains the same. + 4. Test case: `edit_task 1 n/Bar d/Foo dl/02-02-2022 2200`
+ Expected: First Task is edited to have the name "Bar", description "Foo" and deadline "02-02-2022 2200". Details of the edited Task shown in the status message. + 5. Test case: `edit_task 0 n/Bar d/Foo dl/02-02-2022 2200`
+ Expected: No Task is edited. Error details shown in the status message. Status bar remains the same. + 6. Other incorrect edit commands to try: `edit_contact` / `edit_task`, `edit_contact x ...` / `edit_task x ...`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Autocomplete + +1. type `li` then press `tab`
+ Expected: Autocomplete the command based on the current command text, autocompleting it to `list\_`. +2. type `list_t` then press `tab`
+ Expected: Autocomplete the command based on the current command text, autocompleting it to `list_task`. + +### Clearing all entries + +1. Clear all exisiting data in the application + 1. Test case: `clear`
+ Expected: All data is cleared from the application. Status message shows the number of contacts and tasks cleared. ### Saving data -1. Dealing with missing/corrupted data files +1. Shutdown the app by typing `exit` into the command box. +2. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent state is saved. +3. Dealing with missing/corrupted data files + 1. Corrupt the data file in `data/addressbook.json` by adding random characters to make the JSON file unreadable or by simply deleting it. + 2. Re-launch the app by double-clicking the jar file.
+ Expected: The app will start with an empty address book. + 3. After new data is added, the corrupted data file will be overwritten by the app. Any missing file will be replaced by the app. + +### Viewing Help + +1. View Help + 1. Test case: `help`
+ Expected: Help window opens. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +--- + +## **Acknowledgements** -1. _{ more test cases …​ }_ +- This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). +- Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +- UI color scheme inspired by [TailwindUI](https://tailwindui.com/) diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..eec96b729e6 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,29 +1,33 @@ --- layout: page -title: Documentation guide +title: Documentation Guide --- **Setting up and maintaining the project website:** -* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation. -* The `docs/` folder is used for documentation. -* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html). -* Note these points when adapting the documentation to a different project/product: - * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar. - * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format). -* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping) +- We use [**Jekyll**](https://jekyllrb.com/) to manage documentation. +- The `docs/` folder is used for documentation. +- To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html). +- Note these points when adapting the documentation to a different project/product: + - The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar. -**Style guidance:** +
:bulb: **Tip**
+In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format). +
-* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). +- If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping) -* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) +**Style Guidance:** + +- Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). + +- Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) **Diagrams:** -* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html) +- See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html) **Converting a document to the PDF format:** -* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html) +- See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html) diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..73656a8f037 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,11 +1,11 @@ --- layout: page -title: Logging guide +title: Logging Guide --- -* We are using `java.util.logging` package for logging. -* The `LogsCenter` class is used to manage the logging levels and logging destinations. -* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. -* Log messages are output through the console and to a `.log` file. -* The output logging level can be controlled using the `logLevel` setting in the configuration file (See the [Configuration guide](Configuration.md) section). -* **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging conventions_](https://se-education.org/guides/conventions/java/logging.html). +- We are using `java.util.logging` package for logging. +- The `LogsCenter` class is used to manage the logging levels and logging destinations. +- The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. +- Log messages are output through the console and to a `.log` file. +- The output logging level can be controlled using the `logLevel` setting in the configuration file (See the [Configuration guide](Configuration.md) section). +- **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging conventions_](https://se-education.org/guides/conventions/java/logging.html). diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..13b377a3455 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -3,53 +3,55 @@ layout: page title: Setting up and getting started --- -* Table of Contents -{:toc} - +## **Tables of Contents** +{:.no_toc} --------------------------------------------------------------------------------------------------------------------- +1. Table of Contents +{:toc} -## Setting up the project in your computer +--- -
:exclamation: **Caution:** +## **Setting up the project in your computer** +
:warning: **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 to ensure Intellij is configured to use **JDK 11**. -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. -1. **Verify the setup**: - 1. Run the `seedu.address.Main` and try a few commands. - 1. [Run the tests](Testing.md) to ensure they all pass. +2. **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. +3. **Verify the setup**: + 1. Run the `swift.Main` and try a few commands. + 2. [Run the tests](Testing.md) to ensure they all pass. --------------------------------------------------------------------------------------------------------------------- +--- -## Before writing code +## **Before writing code** 1. **Configure the coding style** 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.
-1. **Set up CI** +2. **Set up CI** This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up required. -1. **Learn the design** +3. **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** +4. **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) + - [Tracing code](tutorials/TracingCode.md) + - [Adding a new command](tutorials/AddRemark.md) + - [Removing fields](tutorials/RemovingFields.md) diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..28ea44897b2 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,36 +1,40 @@ --- layout: page -title: Testing guide +title: Testing Guide --- -* Table of Contents +## Table of Contents +{:.no_toc} + +1. Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- -## Running tests +## **Running Tests** There are two ways to run tests. -* **Method 1: Using IntelliJ JUnit test runner** - * To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` - * To run a subset of tests, you can right-click on a test package, +- **Method 1: Using IntelliJ JUnit test runner** + - To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` + - To run a subset of tests, you can right-click on a test package, test class, or a test and choose `Run 'ABC'` -* **Method 2: Using Gradle** - * Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`) +- **Method 2: Using Gradle** + - Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`) -
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle. +
:bulb: **Tip**
+Read this [Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
--------------------------------------------------------------------------------------------------------------------- +--- -## Types of tests +## **Types of Tests** This project has three types of tests: -1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.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` -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` +1. _Unit tests_ targeting the lowest level methods/classes.
+ e.g. `swift.commons.StringUtilTest` +2. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working).
+ e.g. `swift.storage.StorageManagerTest` +3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
+ e.g. `swift.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..90ee9d97d83 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,680 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. + -* Table of Contents +## **About Swift+** + +Swift+ is a **project management app** designed to help software engineering (SWE) project leads in tracking their daily +interactions with contacts. As a project lead, you can easily **record down your clients and teammates** by creating and editing +contacts using Swift+. Using Swift+’s **task management system**, you can assign tasks to contacts and mark tasks as completed. +Finally, you can gain an overview of your project with our intuitive side-by-side user interface. + +Perfect for SWE professionals, Swift+ is built around a **command line interface**, complete with an **autocomplete** feature. +If you have fast fingers, Swift+ can help you manage contacts and tasks more quickly than a traditional point-and-click interface. + +This user guide provides details on how to use Swift+ in your daily workflow. This guide covers how to set up Swift+ and +use its text-based commands. Get started now by heading over to the [How to use this user guide](#how-to-use-this-user-guide) section! + +--- + +## **Table of Contents** +{:.no_toc} + +1. Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- -## Quick start +## **How to use this user guide** -1. Ensure you have Java `11` or above installed in your Computer. +### Icons -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +Throughout this website, you may find colored boxes that contain useful information. The icon at the top of the box represents +the type of information contained. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +| Icon | Meaning | +| --------------------------------------------------------- | ------------------------------------------------------------ | +| ![Tip](images/user-guide/tip.png){:height="32px"} | Tips to help you make the most out of Swift+. | +| ![Note](images/user-guide/note.png){:height="32px"} | Information you should take note of while using Swift+. | +| ![Caution](images/user-guide/caution.png){:height="32px"} | Warnings that may corrupt your app and data if not followed. | -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +### Sections -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +If you have not installed Swift+, head over to the [Installation](#installation) section. - * **`list`** : Lists all contacts. +After installing Swift+, you can refer to our [Getting Started](#getting-started) section on the basics of using Swift+. This includes the app's, - * **`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. +- [Layout](#layout) +- [Command format](#command-format) - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +
:bulb: **Tip**
+If you are using Swift+ for the first time, we **highly recommend** that you read through the [Getting Started](#getting-started) +section before the other sections. +
- * **`clear`** : Deletes all contacts. +To view each command in detail, you can head over to the [Commands](#commands) section. - * **`exit`** : Exits the app. +If you are an experienced user, you can refer to the [Command Summary](#command-summary) for a quick overview of the commands in Swift+. -1. Refer to the [Features](#features) below for details of each command. +If you have any questions while using the app, please refer to our [FAQ](#faq) section. --------------------------------------------------------------------------------------------------------------------- +For any further queries or suggestions, you may reach out to us [here](/AboutUs)! -## Features +--- -
+## **Getting Started** -**:information_source: Notes about the command format:**
+This section covers how to install and start using Swift+. -* 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`. +### Installation -* 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`. +1. Ensure you have [Java 11](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html) installed in your computer. -* 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. +2. Download the latest release of `swift+.jar` from [here](https://github.com/AY2223S1-CS2103T-T12-2/tp/releases). -* 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. +3. Copy the file to an empty folder. This will be your home folder for Swift+. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +4. Double-click on the jar file to launch Swift+. -* 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`. +Congratulations! You have successfully set up Swift+. +
:bulb: **Tip**
+The app comes with sample contacts and tasks by default. To delete the sample data quickly, you can use the [`clear`](#clearing-all-data-clear) command.
-### Viewing help : `help` +### Layout + +After you open Swift+, the app will appear in the form of a graphical user interface, or GUI. In Swift+, you can toggle +between the [Contact View](#contact-view) and the [Task View](#task-view). +Let's take a look at the different components in Swift+ below. + +
:bulb: **Tip**
+To toggle between Contact View and Task View, you can use the [`Ctrl + Tab`](#toggling-between-contacts-and-tasks-tabs-ctrl--tab) command. +
+ +**Swift+'s GUI:** +![Ui](images/Ui.png) + +This table showcases the components shared by both Contact View and Task View. + +| Component Name | Image | +| --------------------- | -------------------------------------------------------------------------- | +| **Menu Bar** | ![Menu Bar](images/user-guide/menu-bar.png) | +| **Command Input Box** | ![Command Input Box](images/user-guide/command-input-box.png) | +| **Command Results** | ![Command Results](images/user-guide/command-results.png){:height="120px"} | + +#### Contact View + +The contact view is primarily for viewing contacts. The main panel on the left shows a list of contacts and all of their contact information. The right sidebar contains tasks with essential details. + +| Component Name | Image | +| ---------------- | -------------------------------------------------------------------- | +| **Contact List** | ![Contact List](images/user-guide/contact-list.png){:height="200px"} | +| **Task Sidebar** | ![Task Sidebar](images/user-guide/task-sidebar.png){:height="150px"} | + +#### Task View -Shows a message explaning how to access the help page. +The task view is mainly for viewing tasks. The main panel on the left displays a list of tasks and all of their details. The right sidebar contains contacts with essential contact information. -![help message](images/helpMessage.png) +| Component Name | Image | +| ------------------- | -------------------------------------------------------------------------- | +| **Task List** | ![Task List](images/user-guide/task-list.png){:height="200px"} | +| **Contact Sidebar** | ![Contact Sidebar](images/user-guide/contact-sidebar.png){:height="150px"} | -Format: `help` +### How to use Swift+ commands +Swift+ is built around text-based commands. Before we dive deeper into the details in the [Commands](#commands) section, +let's learn the basic components and format of a command. -### Adding a person: `add` +#### Flag -Adds a person to the address book. +A flag is a delimiter that allows Swift+ to distinguish different input fields. For each flag, you would put in the +corresponding parameter immediately after. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +| Flag | Corresponding Parameter | +| ----- | ----------------------- | +| `a/` | `ADDRESS` | +| `c/` | `CONTACT_INDEX` | +| `d/` | `DESCRIPTION` | +| `dl/` | `DEADLINE` | +| `e/` | `EMAIL` | +| `n/` | `NAME` | +| `p/` | `PHONE_NUMBER` | +| `t/` | `TAG`, `TASK_INDEX` | -
:bulb: **Tip:** -A person can have any number of tags (including 0) +#### Parameter + +A parameter represents placeholders where you input data. Usually, parameters follow immediately after their corresponding flag. + +Each parameter has unique constraints, which restricts what you can type in for the parameter. Refer to the table below for details. + +
:information_source: **Note**
+Some parameters, such as `CONTACT_INDEX` and `KEYWORD`, may not follow after flags.
-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` +Refer to the [Command Format](#command-format) section on how to use flags and parameters together. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterCorresponding FlagDescription
`ADDRESS``a/`Specifies the address of a contact. An example use for this field include recording a client's home or office address.
`CONTACT_INDEX``c/`Refers to the index number shown in the **displayed contact list**. + +- Must be a **positive whole number**, e.g. 1, 2, 3. + +
`CONTACT_NAME``n/`Specifies the name of a contact. + +- Should only contain alphanumeric characters, spaces, and the following symbols: `,` `-` `'`. + +
`DEADLINE``dl/`Specifies the due date of a task. + +- Must be in the format of **`dd-MM-yyyy HHmm`**. e.g. `12-06-2020 1234` represents `12 June 2020 12:34`. + +
`DESCRIPTION``d/`Specifies the description and details of a task. + +- Should only contain alphanumeric characters, spaces, and the following symbols: `$` `&` `+` `,` `:` `;` `=` `?` `@` `#` `|` `'` `<` `>` `.` `\` `-` `^` `*` `(` `)` `%` `!`. + +
`EMAIL``e/`Specifies the email of a contact. + +- Must be in the format of **`USERNAME@DOMAIN`**. +- `USERNAME` should only contain alphanumeric characters and the following special symbols: `+` `_` `.` `-`. +- `USERNAME` cannot start or end with the above special symbols. +- `DOMAIN` consists of domain labels separated by periods (`.`), e.g. `nus.edu.sg`. +- `DOMAIN` must end with a domain label with at least 2 characters, e.g. `.com`. +- Each domain label must only consist of alphanumeric characters, separated only by hyphens, if any, e.g. `swift-plus.com`. +- Each domain label start and end with alphanumeric characters. + +
`KEYWORD`Not applicableSpecifies the keywords to search for when finding contacts or tasks. + +- Can contain alphanumeric characters, spaces, and any special characters. + +
`PHONE_NUMBER``p/`Specifies the phone number of a contact. + +- Should only contain numbers. +- Must be at least 3 digits long. + +
`TAG``t/`Specifies the tag to categorize a contact under. + +- Should only contain alphanumeric characters. + +
`TASK_INDEX``t/`Refers to the index number shown in the **displayed task list**. + +- Must be a **positive whole number**, e.g. 1, 2, 3. + +
`TASK_NAME``n/`Specifies the name of a task. + +- Should only contain alphanumeric characters, spaces, and the following symbols: `,` `-` `'`. + +
+ +#### Command Format + +To understand how a full command is interpreted, let's look at the following example. -### Listing all persons : `list` +**Example:** `add_contact n/CONTACT_NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` -Shows a list of all persons in the address book. +| | Component Name | Meaning | +| ------------------ | -------------- | ------------------------------------------------------------------ | +| **`add_contact`** | Command Word | Tells Swift+ to execute command to add contact. | +| **`n/`** | Flag | Distinguishes `CONTACT_NAME` from other input fields. | +| **`CONTACT_NAME`** | Parameter | Represents placeholder for name of contact that you wish to input. | -Format: `list` +Notice how `t/TAG` is wrapped in `[ ]`. Items in square brackets are **optional**. -### Editing a person : `edit` +- For example, `n/CONTACT_NAME [t/TAG]` can be used as `n/Mark t/friend` or as `n/Mark`. -Edits an existing person in the address book. +Furthermore, notice how `[t/TAG]` is followed by `…`​. Items followed by `…`​ can be inputted **multiple times**, including +zero times. + +- For example, `n/CONTACT_NAME [t/TAG]…​` can be used as `n/Mark` (i.e. 0 times), `n/Mark t/friend`, `n/Mark t/friend t/family`, and etc. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +
:information_source: **Note**
-* 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. +- Parameters can be in **any order**.
+ e.g. if the command specifies `n/CONTACT_NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/CONTACT_NAME` is also acceptable. +- If a parameter is expected only once in the command but you specified it multiple times, only the **last occurrence** of the parameter will be taken.
+ e.g. if you specify `p/1234 p/5678`, only `p/5678` will be taken. +- Extraneous parameters for commands that do not take in parameters (such as `help`, `list_contact`, `exit` and `clear`) will be **ignored**.
+ e.g. if your specify `help 123`, it will be interpreted as `help`. + +
-Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +--- -### Locating persons by name: `find` +## **Commands** -Finds persons whose names contain any of the given keywords. +This section covers how to use each command in detail. You can refer to the [Parameter](#parameter) section to view the constraints for each parameter. -Format: `find KEYWORD [MORE_KEYWORDS]` +### Contact Commands -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +#### Adding a contact: `add_contact` -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +> Adds a contact. -### Deleting a person : `delete` +**Format:** `add_contact n/CONTACT_NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` -Deletes the specified person from the address book. +
:bulb: **Tip**
+A contact can have any number of tags (including 0). +
-Format: `delete INDEX` +**Examples:** -* 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, …​ +- `add_contact n/Mark Yang p/12345678 e/mark@example.com a/block 123` adds a contact named `Mark Yang` with a phone number of `12345678`, email of `mark@example.com`, and address of `block 123`. +- `add_contact n/Anne Marie t/developer e/anne@example.com a/Newgate office p/87654321 t/client` adds a contact named `Anne Marie` with a `developer` tag, a `friend` tag, email of `anne@example.com`, address of `Newgate office`, and phone number of `87654321`. -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. +#### Listing all contacts: `list_contact` -### Clearing all entries : `clear` +> Shows a list of all contacts in the main panel and a list of all tasks in the sidebar. -Clears all entries from the address book. +**Format:** `list_contact` -Format: `clear` +**Example:** +- `list_contact` displays a list of all contacts, as shown in the diagram below. + ![Result for 'list_contact command'](images/user-guide/list-contact-command.png) -### Exiting the program : `exit` +#### Finding contacts by name: `find_contact` -Exits the program. +> Finds contacts whose names contain any of the given keywords. -Format: `exit` +**Format:** `find_contact KEYWORD [MORE_KEYWORDS]` -### Saving the data +
:information_source: **Note**
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +- The search is **case-insensitive**. e.g. `mark` will match `Mark` +- The order of the keywords does not matter. e.g. `Mark Wilson` will match `Wilson Mark` +- Only the name of contacts is searched. +- Only full words will be matched e.g. `Mark` will not match `Marks` +- Contacts matching at least one keyword will be returned (i.e. `OR` search).
+ e.g. As shown in the diagram below, `find_contact Alex David` will return `Alex Yeoh` and `David Li`, since they contain the keywords `Alex` and `David` respectively. + ![result for 'find alex david'](images/user-guide/findAlexDavidResult.png) -### Editing the data file +
+ +**Examples:** + +- `find_contact Mark` returns `Mark` and `mark`. +- `find_contact mark wilson` returns `Mark Yang` and `Steve Wilson`. + +#### Editing a contact: `edit_contact` + +> Edits an existing contact. + +**Format:** `edit_contact CONTACT_INDEX [n/CONTACT_NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` + +
:information_source: **Note**
+ +- Edits the contact at the specified `CONTACT_INDEX`. +- 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 contact will be removed, i.e. adding of tags is not cumulative. +- You can remove all the contact's tags by typing `t/` without specifying any tags after it. + +
+ +**Examples:** -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +- `edit_contact 1 p/91234567 e/mark@example.com` edits the phone number and email address of the 1st contact to be `91234567` and `mark@example.com` respectively. +- `edit_contact 2 n/Anne t/` edits the name of the 2nd contact to be `Anne` and clears all of `Anne`'s tags. + +#### Deleting a contact: `delete_contact` + +> Deletes the specified contact. + +**Format:** `delete_contact CONTACT_INDEX` + +**Examples:** + +- `list_contact` followed by `delete_contact 2` deletes the 2nd contact in the entire contact list. +- `find_contact Anne` followed by `delete_contact 1` deletes the 1st contact in the results of the `find_contact Anne` command. + +#### Selecting a contact: `select_contact` + +> Displays the selected contact in the main panel and displays the tasks assigned to the contact in the sidebar. + +**Format:** `select_contact CONTACT_INDEX` + +**Examples:** + +- `list_contact` followed by `select_contact 1` selects the 1st contact in the entire contact list and shows all tasks assigned to that contact. +- `find_contact Bernice` followed by `select_contact 1` selects the 1st contact in the results of the `find_contact Bernice` command and shows all task assigned to that person. +
+ e.g. In the screenshot below, we can see the selected contact on the main panel and their assigned tasks on the sidebar. + ![Result for 'select_contact command'](images/user-guide/select-contact.png) + +### Task Commands + +#### Adding a task: `add_task` + +> Adds a task. + +**Format:** `add_task n/TASK_NAME [d/DESCRIPTION] [dl/DEADLINE] [c/CONTACT_INDEX]…​` + +**Examples:** + +- `add_task n/CS2103T iP d/Finish milestones dl/12-12-2022 2359 c/1` adds a task assigned to the 1st displayed contact. The task has a name of `CS2103T`, description of `Finish milestones`, and deadline of `12 December 2022 23:59`. +- `add_task n/CS2101 Assignment dl/12-12-2022 2359 c/2 c/3` adds a task assigned to the 2nd and 3rd displayed contact. The task has a name of `CS2101 Assignment` and deadline of `12 December 2022 23:59`. + +#### Listing all tasks: `list_task` + +> Shows a list of all tasks in the main panel and a list of all contacts in the sidebar. + +**Format:** `list_task` + +
:information_source: **Note**
+ +- Tasks are **sorted chronologically by deadline**. +- Tasks without deadlines are listed below tasks with deadlines and sorted by their names alphabetically. -
: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.
-### Archiving data files `[coming in v2.0]` +**Example:** +- `list_task` displays a list of all tasks, as shown in the diagram below. Note that the first 3 tasks with deadlines are ordered chronologically and placed at the top. The last 2 tasks without deadlines are placed below. + ![Result for 'list_task command'](images/user-guide/list-task-command.png) + +#### Finding tasks by name: `find_task` + +> Finds tasks whose names contain any of the given keywords. + +**Format:** `find_task KEYWORD [MORE_KEYWORDS]` + +
:information_source: **Note**
+ +- The search is **case-insensitive**. e.g. `book` will match `Book` +- The order of the keywords does not matter. e.g. `read book` will match `book read` +- Only the name of the task is searched. +- Only full words will be matched e.g. `Book` will not match `Books` +- Tasks matching at least one keyword will be returned (i.e. `OR` search). + e.g. `Read book` will return `Write book`, `Find book` + +
+ +**Examples:** + +- `find_task Book` returns `book` and `Book`. +- `find_task read book` returns `read novel` and `sell book`. + +#### Editing tasks: `edit_task` + +> Edits an existing task. + +**Format:** `edit_task TASK_INDEX [n/TASK_NAME] [d/DESCRIPTION] [dl/DEADLINE]` + +
:information_source: **Note**
+ +- Edits the task at the specified `TASK_INDEX`. +- At least one of the optional fields must be provided. +- Existing values will be updated to the input values. + +
+ +**Examples:** + +- `edit_task 1 n/Client meeting d/Gather user stories` edits the task name and description of the 1st task to `Client meeting` and `Gather user stories` respectively. +- `edit_task 2 dl/06-12-2022 1200` edits the deadline of the 2nd task to be `06-12-2022 1200`. + +#### Deleting tasks: `delete_task` + +> Deletes an existing task in task list. -_Details coming soon ..._ +**Format:** `delete_task TASK_INDEX` --------------------------------------------------------------------------------------------------------------------- +**Examples:** -## FAQ +- `delete_task 1` deletes the task at index 1. +- `delete_task 3` deletes the task at index 3. -**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. +#### Selecting a task: `select_task` --------------------------------------------------------------------------------------------------------------------- +> Selects the specified task and displays the contacts assigned to the task. -## Command summary +**Format:** `select_task TASK_INDEX` + +**Examples:** + +- `list_task` followed by `select_task 1` selects the 1st task in the entire task list and shows all contacts assigned to that task. +- `find_task discuss` followed by `select_task 1` selects the 1st task in the results of the `find_task discuss` command and shows all contacts assigned to that task. +
+ e.g. In the screenshot below, we can see the selected task on the main panel and its assigned contacts on the sidebar. + ![Result for 'select_task command'](images/user-guide/select-task.png) + +#### Marking a task as complete: `mark` + +> Marks the specified task as completed. + +**Format:** `mark TASK_INDEX` + +
:information_source: **Note**
+ +- Specified task must be currently incomplete for command to succeed. +- As shown in the diagram below, a **ticked checkbox** indicates a task being completed. + ![Task marked as complete](images/mark.png){:height="90px"} + +
+ +**Examples:** + +- `list_task` followed by `mark 1` marks the 1st task in the entire task list as completed. +- `find_task sleep` followed by `mark 1` marks the 1st task in the results of the `find_task sleep` command as completed. + +#### Marking a task as incomplete: `unmark` + +> Marks the specified task as incomplete. + +**Format:** `unmark TASK_INDEX` + +
:information_source: **Note**
+ +- Specified task must be currently completed for command to succeed. +- As shown in the diagram below, an **empty checkbox** indicates a task being incomplete. + ![Task marked as incomplete](images/unmark.png){:height="90px"} + +
+ +**Examples:** + +- `list_task` followed by `unmark 1` marks the 1st task in the entire task list as incomplete. +- `find_task sleep` followed by `unmark 1` marks the 1st task in the results of the `find_task sleep` command as incomplete. + +#### Assigning a task to a contact: `assign` + +> Assigns a task to a contact. + +**Format:** `assign c/CONTACT_INDEX t/TASK_INDEX` + +
:information_source: **Note**
+ +- Assigns the task at the specified `TASK_INDEX` to the contact at the specified `CONTACT_INDEX`. +- Existing assignments are not affected. +- To view a task's assigned contact(s), you can view the labels on the right side of the task list.
e.g. In the diagram below, the 1st task is assigned to `Alex Yeoh`. + ![Assign contact](images/assign-contact.png) + +
+ +**Examples:** + +- `assign c/1 t/1` assigns the task at index 1 to the contact at index 1. +- `assign c/3 t/2` assigns the task at index 2 to the contact at index 3. + +#### Unassign a task from a contact: `unassign` + +> Removes a contact from a task. + +**Format:** `unassign c/CONTACT_INDEX t/TASK_INDEX` + +
:information_source: **Note**
+ +- Removes the contact at the specified `CONTACT_INDEX` from the task at the specified `TASK_INDEX`. + +
+ +**Examples:** + +- `unassign c/1 t/1` removes the contact at index 1 from the task at index 1. +- `unassign c/3 t/2` removes the contact at index 3 from the task at index 2. + +### General Commands + +#### Toggling between contacts and tasks tabs: `Ctrl + Tab` + +> Toggles the view between the contacts and tasks tabs. + +**Format:** `Ctrl + Tab` + +
:bulb: **Tip**
+Alternatively, you can click on the **Contacts and Tasks button** in the top toolbar. +
+ +#### Viewing help: `help` + +> Shows a message explaining how to access the user guide. + +
:bulb: **Tip**
+Alternatively, you can click on the **Help button** in the top toolbar. +
+ +**Format:** `help` + +#### Clearing all data: `clear` + +> Deletes all data in the application. + +**Format:** `clear` + +
:warning: **Caution**
+Executing the `clear` command will cause all of your existing data to be discarded forever and the app will start with an empty data file. There will be no confirmation prompt before clearing data. +
+ +#### Exiting the program : `exit` + +> Exits the program. + +**Format:** `exit` + +
:bulb: **Tip**
+Alternatively, you can click on the **Exit button** in the top toolbar. +
+ +### Command suggestion and autocomplete + +To help you familiarize with the commands, Swift+ prompts you with command suggestions as you type and can autocomplete your commands until the next user-required input. + +1. Type the first few letters of a command you hope to use and Swift+ will display a suggested command. e.g. `lis`.
+ ![autocomplete](images/autocomplete.png) + +2. Press `Tab` to autocomplete your command with the prompted suggestion. + +
+:information_source: **Note**
+- If multiple commands are possible with the current input, autocomplete only completes up to the **longest matching prefix**. e.g. pressing `Tab` after `lis` will autocomplete the command to `list_`, since there are two commands (`list_contact` and `list_task`) that start with `lis`. +- If the current input is invalid, command suggestions will not be shown. The current input will also turn red to alert you.
+- Autocomplete does not guarantee a valid command, unless the given syntax is followed.
+
+ +### Saving the data + +Swift+ data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. + +### Editing the data file + +Swift+ data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. + +
:warning: **Caution**
+If your changes to the data file makes its format invalid, Swift+ will discard all data and start with an empty data file in the next run. +
+ +--- + +## **FAQ** + +**Q**: How do I transfer my data to another computer?
+**A**: Install Swift+ in the other computer. Replace the default data file `addressbook.json` with the file that contains the data of your previous Swift+. + +**Q**: Why does autocomplete not complete the whole suggestion after pressing Tab?
+**A**: If multiple commands are possible with the current input, autocomplete only completes up to the **longest matching prefix**. e.g. pressing `Tab` after `lis` will autocomplete the command to `list_`, since there are two commands (`list_contact` and `list_task`) that start with `lis`. + +--- -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` +## **Command Summary** + +This section contains a summary of all commands in Swift+ listed in alphabetical order. + +| Action | Format | +| ------------------ | ---------------------------------------------------------------------------------------------- | +| **Add Contact** | `add_contact n/CONTACT_NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` | +| **Add Task** | `add_task n/TASK_NAME [d/DESCRIPTION] [dl/DEADLINE] [c/CONTACT_INDEX]…​` | +| **Assign Task** | `assign c/CONTACT_INDEX t/TASK_INDEX` | +| **Clear Data** | `clear` | +| **Delete Contact** | `delete_contact CONTACT_INDEX` | +| **Delete Task** | `delete_task TASK_INDEX` | +| **Edit Contact** | `edit_contact CONTACT_INDEX [n/CONTACT_NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` | +| **Edit Task** | `edit_task TASK_INDEX [n/TASK_NAME] [d/DESCRIPTION] [dl/DEADLINE]` | +| **Find Contacts** | `find_contact KEYWORD [MORE_KEYWORDS]` | +| **Find Tasks** | `find_task KEYWORD [MORE_KEYWORDS]` | +| **Help** | `help` | +| **List Contacts** | `list_contact` | +| **List Tasks** | `list_task` | +| **Mark Task** | `mark TASK_INDEX` | +| **Select Contact** | `select_contact CONTACT_INDEX` | +| **Select Task** | `select_task TASK_INDEX` | +| **Unassign Task** | `unassign c/CONTACT_INDEX t/TASK_INDEX` | +| **Unmark Task** | `unmark TASK_INDEX` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..25141e94842 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Swift+" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-T12-2/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html index 8559a67ffad..16240a02488 100644 --- a/docs/_includes/custom-head.html +++ b/docs/_includes/custom-head.html @@ -1,6 +1,7 @@ -{% comment %} - Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons: - - 1. Head over to https://realfavicongenerator.net/ to add your own favicons. - 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet. -{% endcomment %} + + + + + + + diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..9404d85f449 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -1,32 +1,44 @@ html { font-size: $base-font-size; + scroll-behavior: smooth; } /** * Reset some basic elements */ -body, h1, h2, h3, h4, h5, h6, -p, blockquote, pre, hr, -dl, dd, ol, ul, figure { +body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +hr, +dl, +dd, +ol, +ul, +figure { margin: 0; padding: 0; - } - - /** * Basic styling */ body { - font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; + font: $base-font-weight #{$base-font-size}/#{$base-line-height} + $base-font-family; color: $text-color; background-color: $background-color; -webkit-text-size-adjust: 100%; -webkit-font-feature-settings: "kern" 1; - -moz-font-feature-settings: "kern" 1; - -o-font-feature-settings: "kern" 1; - font-feature-settings: "kern" 1; + -moz-font-feature-settings: "kern" 1; + -o-font-feature-settings: "kern" 1; + font-feature-settings: "kern" 1; font-kerning: normal; display: flex; min-height: 100vh; @@ -34,14 +46,22 @@ body { overflow-wrap: break-word; } - - /** * Set `margin-bottom` to maintain vertical rhythm */ -h1, h2, h3, h4, h5, h6, -p, blockquote, pre, -ul, ol, dl, figure, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +ul, +ol, +dl, +figure, %vertical-rhythm { margin-bottom: $spacing-unit / 2; } @@ -58,8 +78,6 @@ main { display: block; /* Default value of `display` of `main` element is 'inline' in IE 11. */ } - - /** * Images */ @@ -68,8 +86,6 @@ img { vertical-align: middle; } - - /** * Figures */ @@ -81,12 +97,11 @@ figcaption { font-size: $small-font-size; } - - /** * Lists */ -ul, ol { +ul, +ol { margin-left: $spacing-unit; } @@ -97,16 +112,22 @@ li { } } - - /** * Headings */ -h1, h2, h3, h4, h5, h6 { - font-weight: $base-font-weight; +h1, +h2, +h3, +h5, +h6 { + font-weight: $bold-font-weight; + margin-bottom: $spacing-unit; } - +h4 { + font-weight: $base-font-weight; + margin-bottom: $spacing-unit; +} /** * Links @@ -116,11 +137,10 @@ a { text-decoration: none; &:visited { - color: $link-visited-color; + color: $link-base-color; } &:hover { - color: $text-color; text-decoration: underline; } @@ -133,7 +153,6 @@ a { } } - /** * Blockquotes */ @@ -148,13 +167,12 @@ blockquote { margin-bottom: 0; } - i, em { + i, + em { font-style: normal; } } - - /** * Code formatting */ @@ -192,8 +210,6 @@ pre { } } - - /** * Wrapper */ @@ -212,8 +228,6 @@ pre { } } - - /** * Clearfix */ @@ -223,8 +237,6 @@ pre { clear: both; } - - /** * Icons */ @@ -252,7 +264,8 @@ table { background-color: $table-zebra-color; } } - th, td { + th, + td { padding: ($spacing-unit / 3) ($spacing-unit / 2); } th { @@ -267,7 +280,7 @@ table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; + -ms-overflow-style: -ms-autohiding-scrollbar; } } @@ -288,8 +301,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "Swift+"; font-size: 32px; } } - diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss index ca99f981701..56fd0fdd7e8 100644 --- a/docs/_sass/minima/_layout.scss +++ b/docs/_sass/minima/_layout.scss @@ -167,6 +167,7 @@ @include relative-font-size(2.625); letter-spacing: -1px; line-height: 1.15; + font-weight: 500; @media screen and (min-width: $on-large) { @include relative-font-size(2.625); @@ -176,7 +177,7 @@ .post-content { margin-bottom: $spacing-unit; - h1, h2, h3 { margin-top: $spacing-unit * 2 } + h3 { margin-top: $spacing-unit * 1.5 } h4, h5, h6 { margin-top: $spacing-unit } h2 { diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss index a992115a70f..5c3cd517586 100644 --- a/docs/_sass/minima/custom-styles.scss +++ b/docs/_sass/minima/custom-styles.scss @@ -1,7 +1,11 @@ // Placeholder to allow defining custom styles that override everything else. // (Use `_sass/minima/custom-variables.scss` to override variable defaults) -h2, h3, h4, h5, h6 { - color: #e46c0a; +h2, +h3, +h4, +h5, +h6 { + color: $heading-color; } // Bootstrap style alerts @@ -10,7 +14,7 @@ h2, h3, h4, h5, h6 { padding: $alert-padding-y $alert-padding-x; margin-bottom: $alert-margin-bottom; border: $alert-border-width solid transparent; - order-radius : $alert-border-radius; + border-radius: $alert-border-radius; } // Headings for larger alerts @@ -28,7 +32,10 @@ h2, h3, h4, h5, h6 { @each $color, $value in $theme-colors { .alert-#{$color} { - @include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level)); + @include alert-variant( + color-level($value, $alert-bg-level), + color-level($value, $alert-border-level), + color-level($value, $alert-color-level) + ); } } - diff --git a/docs/_sass/minima/custom-variables.scss b/docs/_sass/minima/custom-variables.scss index a128970cbe7..5663b5e9282 100644 --- a/docs/_sass/minima/custom-variables.scss +++ b/docs/_sass/minima/custom-variables.scss @@ -1,7 +1,7 @@ // Placeholder to allow overriding predefined variables smoothly. //Bootstrap's default -$white: #fff !default; +$white: #fff !default; $gray-100: #f8f9fa !default; $gray-200: #e9ecef !default; $gray-300: #dee2e6 !default; @@ -11,61 +11,63 @@ $gray-600: #6c757d !default; $gray-700: #495057 !default; $gray-800: #343a40 !default; $gray-900: #212529 !default; -$black: #000 !default; -$blue: #0d6efd !default; -$indigo: #6610f2 !default; -$purple: #6f42c1 !default; -$pink: #d63384 !default; -$red: #dc3545 !default; -$orange: #fd7e14 !default; -$yellow: #ffc107 !default; -$green: #28a745 !default; -$teal: #20c997 !default; -$cyan: #17a2b8 !default; +$black: #000 !default; +$blue: #0d6efd !default; +$indigo: #6610f2 !default; +$purple: #6d28d9 !default; +$pink: #d63384 !default; +$red: #dc3545 !default; +$orange: #fd7e14 !default; +$yellow: #ffc107 !default; +$green: #28a745 !default; +$teal: #20c997 !default; +$cyan: #17a2b8 !default; -$primary: $blue !default; -$secondary: $gray-600 !default; -$success: $green !default; -$info: $cyan !default; -$warning: $yellow !default; -$danger: $red !default; -$light: $gray-100 !default; -$dark: $gray-800 !default; +$primary: $blue !default; +$secondary: $gray-600 !default; +$success: $green !default; +$info: $cyan !default; +$warning: $yellow !default; +$danger: $red !default; +$light: $gray-100 !default; +$dark: $gray-800 !default; $theme-colors: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark + "primary": $primary, + "secondary": $secondary, + "success": $success, + "info": $info, + "warning": $warning, + "danger": $danger, + "light": $light, + "dark": $dark, ) !default; $theme-color-interval: 8% !default; -$body-bg: $white !default; -$body-color: $gray-900 !default; -$body-text-align: null !default; +$heading-color: $purple !default; + +$body-bg: $white !default; +$body-color: $gray-900 !default; +$body-text-align: null !default; $enable-gradients: true; // Define alert colors, border radius, and padding. -$border-radius: .25rem !default; -$border-width: 1px !default; -$font-weight-bold: 700 !default; +$border-radius: 10px !default; +$border-width: 1px !default; +$font-weight-bold: 700 !default; -$alert-padding-y: .75rem !default; -$alert-padding-x: 1.25rem !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: $border-radius !default; -$alert-link-font-weight: $font-weight-bold !default; -$alert-border-width: $border-width !default; +$alert-padding-y: 0.75rem !default; +$alert-padding-x: 1.25rem !default; +$alert-margin-bottom: 1rem !default; +$alert-border-radius: $border-radius !default; +$alert-link-font-weight: $font-weight-bold !default; +$alert-border-width: $border-width !default; -$alert-bg-level: -10 !default; -$alert-border-level: -9 !default; -$alert-color-level: 6 !default; +$alert-bg-level: -10 !default; +$alert-border-level: -9 !default; +$alert-color-level: 6 !default; // Request a color level // scss-docs-start color-level diff --git a/docs/_sass/minima/initialize.scss b/docs/_sass/minima/initialize.scss index 30288811151..1f7d531c758 100644 --- a/docs/_sass/minima/initialize.scss +++ b/docs/_sass/minima/initialize.scss @@ -6,6 +6,7 @@ $base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Segoe UI Symb $code-font-family: "Menlo", "Inconsolata", "Consolas", "Roboto Mono", "Ubuntu Mono", "Liberation Mono", "Courier New", monospace; $base-font-size: 16px !default; $base-font-weight: 400 !default; +$bold-font-weight: 700 !default; $small-font-size: $base-font-size * 0.875 !default; $base-line-height: 1.5 !default; diff --git a/docs/_sass/toc.scss b/docs/_sass/toc.scss new file mode 100644 index 00000000000..961cac1287f --- /dev/null +++ b/docs/_sass/toc.scss @@ -0,0 +1,54 @@ +//@@author jasonqiu212-reused +// Reused from https://github.com/AY2223S1-CS2103T-W16-2/tp/blob/master/docs/_sass/toc.scss + +#markdown-toc { + counter-reset: item; + + ol { + counter-reset: item; + } + + > li, + ol > li { + counter-increment: item; + } + + ol > li { + display: block; + } + + ol > li:before { + content: counters(item, ".") ". "; + margin-left: -20px; + } +} + +article.post { + counter-reset: section; + + h2:not(.no_toc) { + counter-reset: subsection; + } + + h3:not(.no_toc) { + counter-reset: subsubsection; + } + + h2:not(.no_toc)::before { + counter-increment: section; + content: counter(section) ". "; + font-weight: $base-font-weight; + } + + h3:not(.no_toc)::before { + counter-increment: subsection; + content: counter(section) "." counter(subsection) ". "; + font-weight: $base-font-weight; + } + + h4:not(.no_toc)::before { + counter-increment: subsubsection; + content: counter(section) "." counter(subsection) "." counter(subsubsection) ". "; + font-weight: $base-font-weight; + } +} diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index b5ec6976efa..b1027785533 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -4,7 +4,8 @@ @import "minima/skins/{{ site.minima.skin | default: 'classic' }}", - "minima/initialize"; + "minima/initialize", + "toc"; .icon { height: 21px; diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..2130b424b89 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,10 +7,10 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete_contact 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete_contact 1") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..4523c052862 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,8 +4,8 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":DeleteContactCommandParser" as DeleteContactCommandParser LOGIC_COLOR +participant "d:DeleteContactCommand" as DeleteContactCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,56 +13,56 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete_contact 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete_contact 1") activate AddressBookParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create DeleteContactCommandParser +AddressBookParser -> DeleteContactCommandParser +activate DeleteContactCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +DeleteContactCommandParser --> AddressBookParser +deactivate DeleteContactCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +AddressBookParser -> DeleteContactCommandParser : parse("1") +activate DeleteContactCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create DeleteContactCommand +DeleteContactCommandParser -> DeleteContactCommand +activate DeleteContactCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +DeleteContactCommand --> DeleteContactCommandParser : d +deactivate DeleteContactCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +DeleteContactCommandParser --> AddressBookParser : d +deactivate DeleteContactCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +DeleteContactCommandParser -[hidden]-> AddressBookParser +destroy DeleteContactCommandParser AddressBookParser --> LogicManager : d deactivate AddressBookParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> DeleteContactCommand : execute() +activate DeleteContactCommand -DeleteCommand -> Model : deletePerson(1) +DeleteContactCommand -> Model : deletePerson(1) activate Model -Model --> DeleteCommand +Model --> DeleteContactCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +DeleteContactCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> DeleteContactCommand deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +DeleteContactCommand --> LogicManager : result +deactivate DeleteContactCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..27eda8def64 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -38,7 +38,7 @@ LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of XYZCommand: XYZCommand = AddContactCommand, \nFindContactCommand, etc Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/PersonTaskBridgeDiagram.puml b/docs/diagrams/PersonTaskBridgeDiagram.puml new file mode 100644 index 00000000000..4feed6719ea --- /dev/null +++ b/docs/diagrams/PersonTaskBridgeDiagram.puml @@ -0,0 +1,21 @@ +@startuml +!include style.puml +skinparam linetype polyline +skinparam linetype ortho +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <> { + Class AddressBook + Class Task + Class Person + Class PersonTaskBridge + Class PersonTaskBridgeList + + Task -d- Person : > assigned to + (Person, Task) .. PersonTaskBridge + PersonTaskBridgeList "*" *-up-> PersonTaskBridge + AddressBook "1" *--> PersonTaskBridgeList +} +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..5f30e9a59cb 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -20,6 +20,8 @@ Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson Class JsonAdaptedTag +Class JsonAdaptedTask +Class JsonAdaptedBridge } } @@ -39,5 +41,7 @@ JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedTask +JsonSerializableAddressBook --> "*" JsonAdaptedBridge @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..b778919972e 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,15 +11,10 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard Class StatusBarFooter Class CommandBox } -package Model <> { -Class HiddenModel #FFFFFF -} package Logic <> { Class HiddenLogic #FFFFFF @@ -32,26 +27,19 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard - MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UiPanelsClassDiagram.puml b/docs/diagrams/UiPanelsClassDiagram.puml new file mode 100644 index 00000000000..0da00dd9a21 --- /dev/null +++ b/docs/diagrams/UiPanelsClassDiagram.puml @@ -0,0 +1,62 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class UiManager +show MainWindow members +Class MainWindow +Class PersonListPanel +Class PersonCard +Class TaskListPanel +Class TaskCard +Class PersonTaskCard +Class PersonTaskListPanel +Class TaskPersonCard +Class TaskPersonListPanel +} + +class MainWindow { + isContactTabShown Boolean +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + +UiManager .left.|> Ui +UiManager -down-> "1" MainWindow +MainWindow *-down-> "0..1" PersonListPanel +MainWindow *-down-> "0..1" TaskListPanel +MainWindow *-down-> "0..1" PersonTaskListPanel +MainWindow *-down-> "0..1" TaskPersonListPanel + +PersonListPanel -down-> "*" PersonCard +TaskListPanel -down-> "*" TaskCard +PersonTaskListPanel -down-> "*" PersonTaskCard +TaskPersonListPanel -down-> "*" TaskPersonCard + +MainWindow -left-|> UiPart + +PersonListPanel --|> UiPart +PersonCard --|> UiPart +TaskListPanel --|> UiPart +TaskCard --|> UiPart +PersonTaskListPanel --|> UiPart +PersonTaskCard --|> UiPart +TaskPersonListPanel --|> UiPart +TaskPersonCard --|> UiPart + +PersonCard ..> Model +TaskCard ..> Model +PersonTaskCard ...> Model +TaskPersonCard .right.> Model +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..879cc160992 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..7ea007c940b 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..9ead05e4171 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/PersonTaskBridgeDiagram.png b/docs/images/PersonTaskBridgeDiagram.png new file mode 100644 index 00000000000..9b3acaca9a0 Binary files /dev/null and b/docs/images/PersonTaskBridgeDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..1fed480502a 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..a43a3374e31 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 785e04dbab4..17cbd28adb0 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiPanelsClassDiagram.png b/docs/images/UiPanelsClassDiagram.png new file mode 100644 index 00000000000..2b099967717 Binary files /dev/null and b/docs/images/UiPanelsClassDiagram.png differ diff --git a/docs/images/assign-contact.png b/docs/images/assign-contact.png new file mode 100644 index 00000000000..86970c099ac Binary files /dev/null and b/docs/images/assign-contact.png differ diff --git a/docs/images/autocomplete.png b/docs/images/autocomplete.png new file mode 100644 index 00000000000..c765cff1d96 Binary files /dev/null and b/docs/images/autocomplete.png differ diff --git a/docs/images/favicon/android-chrome-192x192.png b/docs/images/favicon/android-chrome-192x192.png new file mode 100644 index 00000000000..c0aa282267c Binary files /dev/null and b/docs/images/favicon/android-chrome-192x192.png differ diff --git a/docs/images/favicon/android-chrome-512x512.png b/docs/images/favicon/android-chrome-512x512.png new file mode 100644 index 00000000000..d1b020e602d Binary files /dev/null and b/docs/images/favicon/android-chrome-512x512.png differ diff --git a/docs/images/favicon/apple-touch-icon.png b/docs/images/favicon/apple-touch-icon.png new file mode 100644 index 00000000000..ae21c8adbbf Binary files /dev/null and b/docs/images/favicon/apple-touch-icon.png differ diff --git a/docs/images/favicon/browserconfig.xml b/docs/images/favicon/browserconfig.xml new file mode 100644 index 00000000000..b3930d0f047 --- /dev/null +++ b/docs/images/favicon/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/docs/images/favicon/favicon-16x16.png b/docs/images/favicon/favicon-16x16.png new file mode 100644 index 00000000000..eca91ef8586 Binary files /dev/null and b/docs/images/favicon/favicon-16x16.png differ diff --git a/docs/images/favicon/favicon-32x32.png b/docs/images/favicon/favicon-32x32.png new file mode 100644 index 00000000000..ed1ed6c8868 Binary files /dev/null and b/docs/images/favicon/favicon-32x32.png differ diff --git a/docs/images/favicon/favicon.ico b/docs/images/favicon/favicon.ico new file mode 100644 index 00000000000..d61d6dab1ad Binary files /dev/null and b/docs/images/favicon/favicon.ico differ diff --git a/docs/images/favicon/mstile-150x150.png b/docs/images/favicon/mstile-150x150.png new file mode 100644 index 00000000000..2a361b6ebee Binary files /dev/null and b/docs/images/favicon/mstile-150x150.png differ diff --git a/docs/images/favicon/safari-pinned-tab.svg b/docs/images/favicon/safari-pinned-tab.svg new file mode 100644 index 00000000000..dec635b1025 --- /dev/null +++ b/docs/images/favicon/safari-pinned-tab.svg @@ -0,0 +1,36 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + diff --git a/docs/images/favicon/site.webmanifest b/docs/images/favicon/site.webmanifest new file mode 100644 index 00000000000..b20abb7cbb2 --- /dev/null +++ b/docs/images/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png deleted file mode 100644 index 235da1c273e..00000000000 Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..d829c3c9b18 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/jasonqiu212.png b/docs/images/jasonqiu212.png new file mode 100644 index 00000000000..2f9e8d07d56 Binary files /dev/null and b/docs/images/jasonqiu212.png differ diff --git a/docs/images/mark.png b/docs/images/mark.png new file mode 100644 index 00000000000..cbca2411cf8 Binary files /dev/null and b/docs/images/mark.png differ diff --git a/docs/images/peppapighs.png b/docs/images/peppapighs.png new file mode 100644 index 00000000000..3dbab2ea2a2 Binary files /dev/null and b/docs/images/peppapighs.png differ diff --git a/docs/images/santosh3007.png b/docs/images/santosh3007.png new file mode 100644 index 00000000000..0bb82961b47 Binary files /dev/null and b/docs/images/santosh3007.png differ diff --git a/docs/images/shenyicui.png b/docs/images/shenyicui.png new file mode 100644 index 00000000000..867e02c9e1e Binary files /dev/null and b/docs/images/shenyicui.png differ diff --git a/docs/images/unmark.png b/docs/images/unmark.png new file mode 100644 index 00000000000..3e2ee225ac2 Binary files /dev/null and b/docs/images/unmark.png differ diff --git a/docs/images/user-guide/caution.png b/docs/images/user-guide/caution.png new file mode 100644 index 00000000000..bf924f41a01 Binary files /dev/null and b/docs/images/user-guide/caution.png differ diff --git a/docs/images/user-guide/command-input-box.png b/docs/images/user-guide/command-input-box.png new file mode 100644 index 00000000000..05666f32e51 Binary files /dev/null and b/docs/images/user-guide/command-input-box.png differ diff --git a/docs/images/user-guide/command-results.png b/docs/images/user-guide/command-results.png new file mode 100644 index 00000000000..85d5db4ffb5 Binary files /dev/null and b/docs/images/user-guide/command-results.png differ diff --git a/docs/images/user-guide/contact-list.png b/docs/images/user-guide/contact-list.png new file mode 100644 index 00000000000..240619a3289 Binary files /dev/null and b/docs/images/user-guide/contact-list.png differ diff --git a/docs/images/user-guide/contact-sidebar.png b/docs/images/user-guide/contact-sidebar.png new file mode 100644 index 00000000000..8a51d620d52 Binary files /dev/null and b/docs/images/user-guide/contact-sidebar.png differ diff --git a/docs/images/user-guide/findAlexDavidResult.png b/docs/images/user-guide/findAlexDavidResult.png new file mode 100644 index 00000000000..5445e712ce9 Binary files /dev/null and b/docs/images/user-guide/findAlexDavidResult.png differ diff --git a/docs/images/user-guide/list-contact-command.png b/docs/images/user-guide/list-contact-command.png new file mode 100644 index 00000000000..a134584d5cb Binary files /dev/null and b/docs/images/user-guide/list-contact-command.png differ diff --git a/docs/images/user-guide/list-task-command.png b/docs/images/user-guide/list-task-command.png new file mode 100644 index 00000000000..03757d91c95 Binary files /dev/null and b/docs/images/user-guide/list-task-command.png differ diff --git a/docs/images/user-guide/menu-bar.png b/docs/images/user-guide/menu-bar.png new file mode 100644 index 00000000000..d193920bdfe Binary files /dev/null and b/docs/images/user-guide/menu-bar.png differ diff --git a/docs/images/user-guide/note.png b/docs/images/user-guide/note.png new file mode 100644 index 00000000000..1c23f3c7b8d Binary files /dev/null and b/docs/images/user-guide/note.png differ diff --git a/docs/images/user-guide/select-contact.png b/docs/images/user-guide/select-contact.png new file mode 100644 index 00000000000..6edfc9864e7 Binary files /dev/null and b/docs/images/user-guide/select-contact.png differ diff --git a/docs/images/user-guide/select-task.png b/docs/images/user-guide/select-task.png new file mode 100644 index 00000000000..29087283e85 Binary files /dev/null and b/docs/images/user-guide/select-task.png differ diff --git a/docs/images/user-guide/task-list.png b/docs/images/user-guide/task-list.png new file mode 100644 index 00000000000..8a53e74edfa Binary files /dev/null and b/docs/images/user-guide/task-list.png differ diff --git a/docs/images/user-guide/task-sidebar.png b/docs/images/user-guide/task-sidebar.png new file mode 100644 index 00000000000..1b5eb034866 Binary files /dev/null and b/docs/images/user-guide/task-sidebar.png differ diff --git a/docs/images/user-guide/tip.png b/docs/images/user-guide/tip.png new file mode 100644 index 00000000000..63a743f53a1 Binary files /dev/null and b/docs/images/user-guide/tip.png differ diff --git a/docs/images/yunruu.png b/docs/images/yunruu.png new file mode 100644 index 00000000000..0be1ac35dc8 Binary files /dev/null and b/docs/images/yunruu.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..20a7d75c242 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,20 @@ --- layout: page -title: AddressBook Level-3 +title: Swift+ --- -[![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) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T12-2/tp/branch/master/graph/badge.svg?token=A2FU6P932B)](https://app.codecov.io/gh/AY2223S1-CS2103T-T12-2/tp) +[![CI Status](https://github.com/AY2223S1-CS2103T-T12-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T12-2/tp/actions) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). - -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +Swift+ is a **desktop application to track your daily events with contacts.** Combined with an aesthetic GUI, all user interactions happen using swift text-based commands. +- If you are interested in using Swift+, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +- If you are interested about developing Swift+, 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) +- This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). +- Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +- UI color scheme inspired by [TailwindUI](https://tailwindui.com/) diff --git a/docs/team/jasonqiu212.md b/docs/team/jasonqiu212.md new file mode 100644 index 00000000000..915f21bb1f5 --- /dev/null +++ b/docs/team/jasonqiu212.md @@ -0,0 +1,54 @@ +--- +layout: page +title: Jason Qiu's Project Portfolio Page +--- + +### Project: Swift+ + +Swift+ is a **project management application** designed to help software engineering (SWE) project leads in tracking their daily +interactions with contacts. The user interacts with Swift+ mainly through a Command Line Interface (CLI). The application is +written in **Java** and has about **10k LoC**. + +Here are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=jasonqiu212&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) +- **New feature**: Added the ability to find tasks + + - Relevant pull request(s): [#58](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/58) + - What it does: This feature allows users to search for tasks using keywords. + - Justification: This feature eases the process of finding tasks, since users can easily search using keywords. + +- **New feature**: Added `deadline`, `description`, and `isDone` fields for tasks + + - Relevant pull request(s): [#103](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/103), [#132](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/132) + - What it does: This feature give users the option to add deadlines and descriptions to their tasks. It also allows the user to mark their tasks as completed and mark their tasks as incomplete. + - Justification: This enhancement allows users to add more information to their created tasks. + - Highlights: The main challenge while implementing this task was making the `deadline` and `description` optional. Although Java has the `Optional` class to wrap around optional values, it was difficult to decide where to add this layer of abstraction while considering the Single Level of Abstraction Principle (SLAP). + +- **Enhancements to existing features**: + + - Renamed `find` command from AB3 to `find_contact` ([#56](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/56)) + - Added task sorting feature by chronological order of deadline ([#137](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/137)) + +- **Project management**: + + - Managed releases `v1.1` to `v1.3` (4 releases) on GitHub + - Updated Jekyll site-wide settings ([#10](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/10)) + - Removed AB3 traces from user guide ([#34](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/34)) + +- **Documentation**: + + - User Guide: + - Added sections on how to use this user guide and how to get started ([#193](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/193)) + - Added documentation for the `find_task` command + - Made cosmetic changes to header colors and spacing to match color scheme of app + - Developer Guide: + - Added implementation details of optional `Description` and `Deadline` fields ([#249](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/249)) + - Added user stories, target user profile, and value proposition ([#16](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/16)) + - Added use cases 7 to 9 ([#249](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/249)) + +- **Community**: + - PRs reviewed (with non-trivial review comments): [#19](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/19), [#20](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/20), [#48](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/48), [#57](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/57), [#62](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/62), [#83](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/83), [#85](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/85), [#89](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/89), [#99](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/99), [#128](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/128), [#133](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/133), [#187](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/187), [#195](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/195) + - Contributed to forum discussions (examples: [#288](https://github.com/nus-cs2103-AY2223S1/forum/issues/288), [#317](https://github.com/nus-cs2103-AY2223S1/forum/issues/317)) + - Reported bugs and added suggestions for other teams in the class (examples: [#241](https://github.com/AY2223S1-CS2103T-W16-4/tp/issues/241), [#262](https://github.com/AY2223S1-CS2103T-W16-4/tp/issues/262), [#272](https://github.com/AY2223S1-CS2103T-W16-4/tp/issues/272)) + - Found bug in `addressbook-level3` site website: [#149](https://github.com/se-edu/addressbook-level3/issues/149) diff --git a/docs/team/peppapighs.md b/docs/team/peppapighs.md new file mode 100644 index 00000000000..6d0fca6ee2d --- /dev/null +++ b/docs/team/peppapighs.md @@ -0,0 +1,40 @@ +--- +layout: page +title: Pontakorn Prasertsuk's Project Portfolio Page +--- + +### Project: Swift+ + +Swift+ is a **project management application** designed to help software engineering (SWE) project leads in tracking their daily interactions with contacts. The user interacts with Swift+ mainly through a Command Line Interface (CLI). The application is written in **Java** and has about **10k LoC**. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=peppapighs&breakdown=true&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +- **New feature**: Added the task schema and the ability to add tasks. + - What it does: allows the user to add tasks to the contact list. + - Justification: This feature improves the product significantly because a user can track the tasks. + - Highlights: This enhancement serves as the foundation for the other task features. +- **New features** Added the bridge relation between tasks and contacts. + - What it does: allows the user to assign tasks to contacts and vice versa. + - Justification: This feature improves the product significantly because a user can track the associations between contacts and tasks. + - Highlights: This enhancement serves as the foundation for the other contact-task features. +- **New features** Added the ability to assign a contact to a task. + - What it does: allows the user to assign a contact to a task. + - Justification: This feature improves the product significantly because a user can make change to the associations between contacts and tasks. +- **Enhancements to existing features**: + - Updated package name from `seedu.address` to `swift` [#40](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/40), [#42](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/42) + - Rename `list` command to `list_contact` command [#48](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/48) +- **Documentation**: + - User Guide: + - Add documentation for `add_task` command [#45](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/45) + - Add documentation for `assign` command [#99](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/99) + - Add documentation for `clear` command [#133](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/133) + - Add documentation for autocomplete feature [#189](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/189) + - Add autocomplete FAQ [#227](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/227) + - Developer Guide: + - Add implementation details of contact-task bridge relation [#79](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/79) + - Update class and model sequence diagrams [#244](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/244) +- **Reviewing/Mentoring**: + - PRs reviewed: [#8](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/8), [#16](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/16), [#28](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/28), [#58](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/58), [#83](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/83), [#85](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/85), [#89](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/89), [#103](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/103), [#127](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/127), [#137](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/137), [#205](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/205), [#212](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/212), [#230](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/230) + - Bug reports: [#90](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/90), [#100](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/100), [#224](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/224), [#229](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/229) diff --git a/docs/team/santosh3007.md b/docs/team/santosh3007.md new file mode 100644 index 00000000000..0693245a268 --- /dev/null +++ b/docs/team/santosh3007.md @@ -0,0 +1,56 @@ +--- +layout: page +title: Santosh Muthukrishnan's Project Portfolio Page +--- + +## Project: Swift+ + +Swift+ is a **project management application** designed to help software engineering (SWE) project leads in tracking their daily tasks and +interactions with different contacts. The user interacts with Swift+ mainly through a Command Line Interface (CLI). The application is +written in **Java** and has about **10k LoC**. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=santosh3007&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **New feature**: Added the ability to edit tasks + + - Relevant pull request(s): [#66](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/66) + - What it does: This feature allows users to edit existing tasks to update the corresponding fields. + - Justification: This feature aids users in changing the details of their tasks if they created the task with incorrect details or if there is a change in details. + +* **New feature**: Added Command Suggestion and Autocomplete features + + - Relevant pull request(s): [#83](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/83) [#113](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/113) [#134](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/134) + - What it does: This feature prompts users with command suggestions depending on the current user input. It also allows them to autocomplete their commands depending on the command suggestion. + - Justification: This feature make typing commands faster for users and helps new users get familiarised with the commands. It also lets the users know if their commands are valid as they type without running the commands. + +* **New feature**: Added the ability to mark/unmark tasks as completed + + - Relevant pull request(s): [#128](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/128) + - What it does: This feature allows users to mark or unmark existing tasks as complete. + - Justification: This feature aids users in keeping track of the progress/status of their tasks and events so that they can manage their schedule effectively. + +* **Enhancements to existing UI**: + + - Enhanced the current base UI to match project color scheme and changed layout of components ([#57](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/57)) + +* **Documentation**: + - User Guide: + - Added documentation for `edit_task` command ([#27](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/27)) + - Added documentation for Command Suggestion and Autocomplete features + - Added documentation for `mark` and `unmark` commands + - Added Caution note for `clear` command + - Developer Guide: + - Updated and added Non-Functional Requirements ([#28](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/28)) + - Added implementation details of Command Suggestion and Autocomplete ([#82](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/82)) + - Added and updated class diagrams for UI (With and without Task/Contact display panels) + - Update PersonTaskBridge Diagram + + + +- **Reviewing/Mentoring**: + - PRs reviewed (with non-trivial review comments): [#77](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/77), [#83](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/83), [#85](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/85), [#101](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/101), [#111](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/111), [#119](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/119), [#121](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/121), [#127](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/127), [#132](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/132), [#137](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/137), [#138](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/138), [#139](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/139), [#190](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/190), [#191](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/191), [#193](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/193), [#199](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/199), [#217](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/217), [#251](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/251) + - Helped with the logic behind tab switching and panel management [#111](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/111) + - Helped with coming up with the idea of making `CommandResult` include a `CommandType` enum to differentiate which tab each command is associated to [#111](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/111) + diff --git a/docs/team/shenyicui.md b/docs/team/shenyicui.md new file mode 100644 index 00000000000..fd83388452e --- /dev/null +++ b/docs/team/shenyicui.md @@ -0,0 +1,52 @@ +--- +layout: page +title: Shenyi Cui's Project Portfolio Page +--- + +### Project: Swift+ + +Swift+ is a desktop contact management application for SWE project leads working on multiple projects to track their +interactions and meetings with clients and colleagues. The user interacts with it using a CLI, and Swift+ has a GUI created +with JavaFX. It is written in Java and has about 10kLoC. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=shenyicui&breakdown=true&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **New feature**: Added the ability to delete tasks. [#52](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/52) + + - What it does: allows the user to delete tasks from the program. + - Justification: This feature improves the usability of the application, allowing users to keep clear already completed tasks. + +* **New features** Added the ability to select contacts. [#86](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/86) + + - What it does: allows the user to select a contact to view its task association. + - Justification: This feature improves the product significantly because a user can track the associations between contacts and tasks. + +* **New features** Added a new panel in the UI that displays the associated tasks and contacts. [#86](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/86) + + - What it does: allows the user to view the associated contacts and tasks to a selected item. + - Justification: This feature improves the product significantly because it allows the user to view the associations between contacts and tasks. + +* **New features** Added the ability for a user to unassign a contact or task. [#127](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/127) + + - What it does: allows the user to view the associated contacts and tasks to a selected item. + - Justification: This feature improves the product significantly because it allows the user to view the associations between contacts and tasks. + +* **Enhancements to existing features**: + - Rename `add` command to `add_contact` command [#51](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/51) + +- **Documentation**: + - User Guide: + - Add documentation for `select` + - Add documentation for `delete_task` + - Add documentation for `unassign` + - Developer Guide: + - Added documentation for `delete_task` [#89](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/89) + - Added manual testing instructions [#251](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/251) +- **Community**: + - PRs reviewed (with non-trivial review comments): + - [#111](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/111), [#28](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/28), [#27](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/27), + [#91](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/91), [#94](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/94), [#227](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/227), [#230](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/230) + - Reported bugs and added suggestions for other teams in the class + - [#93](https://github.com/AY2223S1-CS2103T-T12-2/tp/issues/93), [#92](https://github.com/AY2223S1-CS2103T-T12-2/tp/issues/92), [#110](https://github.com/AY2223S1-CS2103T-T12-2/tp/issues/110) diff --git a/docs/team/yunruu.md b/docs/team/yunruu.md new file mode 100644 index 00000000000..734318c0f94 --- /dev/null +++ b/docs/team/yunruu.md @@ -0,0 +1,52 @@ +--- +layout: page +title: Chin Yun Ru's Project Portfolio Page +--- + +### Project: Swift+ + +Swift+ is a desktop contact management application for SWE project leads working on multiple projects to track their +interactions and meetings with clients and colleagues. The user interacts with it using a CLI, and Swift+ has a GUI created +with JavaFX. It is written in Java and has about 10kLoC. + +Given below are my contributions to the project. + +- **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=yunruu&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +- **New feature**: Added `list_task` command + + - Relevant pull requests: [#63](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/63), [#64](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/64), [#194](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/194) + - What it does: list all existing tasks. + - Justification: this feature provides an immediate overview of all tasks information at one go. + +- **New feature**: Added main task panel and sub contact panel + + - Relevant pull requests: [#101](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/101), [#85](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/85) + - What it does: allows the user to see the lists of task according to the task commands. + - Justification: this feature allows user to focus on the list of tasks in the main panel, with the relevant contacts in the sub-panel. + +- **New feature**: Added toggling between contacts and tasks tabs + + - Relevant pull requests: [#101](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/101), [#111](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/111) + - What it does: allows the user toggle between contacts and tasks tabs so they can focus on either view. + - Justification: allows user to focus on contacts or tasks view according to the command type (contacts or tasks respectively). + +- **New feature**: Added `select_task` command + + - Relevant pull requests: [#101](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/101), [#117](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/117) + - What it does: allows the user to select a task to view the task information and assigned contacts. + - Justification: allows the user to see the important information of the contacts that are assigned to the task. + +- **Enhancements to existing features**: + - Renamed `Edit` command from AB3 to `EditContact` ([#62](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/62)) + - Updated Contacts Tab UI ([#78](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/78)) + - Updated Tasks Tab UI ([101](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/101)) + - Updated overall UI ([126](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/126), [#138](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/138), [#145](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/145), [#77](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/77), [#78](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/78), [#148](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/148)) +- **Documentation**: + - User Guide: + - Updated `edit_contact` and `list_task` documentation ([#31](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/31), [#194](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/194)) + - Added documentation for `select_task` and `select_contact` commands ([#117](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/117)) + - Developer Guide: + - Updated glossary ([#32](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/32)) + - Added documentation for implementation of tasks main panel ([#85](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/85)) +- **Reviewing**: [#22](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/22), [#84](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/84), [#124](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/124), [#140](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/140), [#147](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/147), [#222](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/222), [#232](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/232), [#233](https://github.com/AY2223S1-CS2103T-T12-2/tp/pull/233) diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 880c701042f..f2d4c533f64 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -23,9 +23,9 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu **`RemarkCommand.java`:** ``` java -package seedu.address.logic.commands; +package swift.logic.commands; -import seedu.address.model.Model; +import swift.model.Model; /** * Changes the remark of an existing person in the address book. @@ -91,7 +91,7 @@ Let’s change `RemarkCommand` to parse input from the user. We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended. ``` java -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { //... @@ -142,7 +142,7 @@ Your code should look something like [this](https://github.com/se-edu/addressboo Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user. -Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface. +Create a `RemarkCommandParser` class in the `swift.logic.parser` package. The class must extend the `Parser` interface. ![The relationship between Parser and RemarkCommandParser](../images/add-remark/ParserInterface.png) @@ -229,7 +229,7 @@ Now that we have all the information that we need, let’s lay the groundwork fo ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `swift.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input validation. @@ -242,7 +242,7 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). +Simply add the following to [`swift.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** @@ -349,7 +349,7 @@ save it with `Model#setPerson()`. personToEdit.getAddress(), remark, personToEdit.getTags()); model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); return new CommandResult(generateSuccessMessage(editedPerson)); } diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..e4d87593404 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. +The `address` field in `Person` is actually an instance of the `swift.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. * :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) @@ -50,9 +50,8 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`. 1. Remove the usages of `address` and select `Do refactor` when you are done. -
- - :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. +
:bulb: **Tip**
+ Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
1. Repeat the steps for the remaining usages of `Address` diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..7892c19261a 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -39,16 +39,15 @@ In our case, we would want to begin the tracing at the very point where the App -According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`. +According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `swift.logic.Logic`. -
- -:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. +
:bulb: **Intellij Tip**
+The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. +A quick look at the `swift.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. ```java public interface Logic { @@ -71,9 +70,8 @@ That should be fine because the [Architecture section of the Developer Guide](.. Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`. -
- -:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. +
:bulb: **Intellij Tip**
+The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used.
![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png) @@ -87,26 +85,25 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`. -
- -:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. +
:bulb: **Tip**
+Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component.
1. To start the debugging session, simply `Run` \> `Debug Main` -1. When the GUI appears, enter `edit 1 n/Alice Yeoh` into the command box and press `Enter`. +2. When the GUI appears, enter `edit 1 n/Alice Yeoh` into the command box and press `Enter`. -1. The Debugger tool window should show up and show something like this:
+3. The Debugger tool window should show up and show something like this:
![DebuggerStep1](../images/tracing/DebuggerStep1.png) -1. Use the _Show execution point_ feature to jump to the line of code that we stopped at:
+4. Use the _Show execution point_ feature to jump to the line of code that we stopped at:
![ShowExecutionPoint](../images/tracing/ShowExecutionPoint.png)
`CommandResult commandResult = logic.execute(commandText);` is the line that you end up at (i.e., the place where we put the breakpoint). -1. We are interested in the `logic.execute(commandText)` portion of that line so let’s _Step in_ into that method call:
+5. We are interested in the `logic.execute(commandText)` portion of that line so let’s _Step in_ into that method call:
![StepInto](../images/tracing/StepInto.png) -1. We end up in `LogicManager#execute()` (not `Logic#execute` -- but this is expected because we know the `execute()` method in the `Logic` interface is actually implemented by the `LogicManager` class). Let’s take a look at the body of the method. Given below is the same code, with additional explanatory comments. +6. We end up in `LogicManager#execute()` (not `Logic#execute` -- but this is expected because we know the `execute()` method in the `Logic` interface is actually implemented by the `LogicManager` class). Let’s take a look at the body of the method. Given below is the same code, with additional explanatory comments. **LogicManager\#execute().** @@ -136,12 +133,12 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ } ``` -1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one. +7. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one. -1. _Step over_ the logging code since it is of no interest to us now. +8. _Step over_ the logging code since it is of no interest to us now. ![StepOver](../images/tracing/StepOver.png) -1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): +9. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): ``` java public Command parseCommand(String userInput) throws ParseException { ... @@ -150,116 +147,118 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ... ``` -1. _Step over_ the statements in that method until you reach the `switch` statement. The 'Variables' window now shows the value of both `commandWord` and `arguments`:
- ![Variables](../images/tracing/Variables.png) - -1. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way. - -1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command). - - ``` java - ... - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - ... - ``` - -1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`. +10. _Step over_ the statements in that method until you reach the `switch` statement. The 'Variables' window now shows the value of both `commandWord` and `arguments`:
+ ![Variables](../images/tracing/Variables.png) -
:bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! -
+11. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way. -1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required. +12. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command). -1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the 'Variables' window.
- ![EditCommand](../images/tracing/EditCommand.png) + ``` java + ... + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + ... + ``` -1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component. - +13. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`. -1. Let’s continue stepping through until we return to `LogicManager#execute()`. +
:bulb: **Intellij Tip**
+ Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! +
- The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) +14. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required. -1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below): - - **`EditCommand#execute()`:** - ``` java - @Override - public CommandResult execute(Model model) throws CommandException { - ... - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - ``` +15. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the 'Variables' window.
+ ![EditCommand](../images/tracing/EditCommand.png) -1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, - * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. - * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. -
- * :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) +16. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component. + -1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
- Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
+17. Let’s continue stepping through until we return to `LogicManager#execute()`. -1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component: - - * :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) + The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
+ ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) -1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component. +18. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below): -
:bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. -
- -1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): - - **`JsonSerializableAddressBook` constructor:** + **`EditCommand#execute()`:** ``` java - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created - * {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll( - source.getPersonList() - .stream() - .map(JsonAdaptedPerson::new) - .collect(Collectors.toList())); - } - ``` - -1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`. - This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format. - -1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
- - * :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) - -1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). - -1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in: - - **`ResultDisplay#setFeedbackToUser()`** - ``` java - public void setFeedbackToUser(String feedbackToUser) { - requireNonNull(feedbackToUser); - resultDisplay.setText(feedbackToUser); + @Override + public CommandResult execute(Model model) throws CommandException { + ... + Person personToEdit = lastShownList.get(index.getZeroBased()); + Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } ``` -1. Finally, you can step through until you reach the end of`MainWindow#executeCommand()`.
- :bulb: This may be a good time to read through the [`UI` component section of the DG](../DeveloperGuide.html#ui-component) +19. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, + * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. + * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
+ FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. +
+ * :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) + +20. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
+ Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
+ +21. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component: + + * :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) + +22. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component. + +
:bulb: **Intellij Tip**
+ When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. +
+ +23. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): + + **`JsonSerializableAddressBook` constructor:** + ``` java + /** + * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. + * + * @param source future changes to this will not affect the created + * {@code JsonSerializableAddressBook}. + */ + public JsonSerializableAddressBook(ReadOnlyAddressBook source) { + persons.addAll( + source.getPersonList() + .stream() + .map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); + } + ``` + +24. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`. + This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format. + +25. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
+ + * :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) + +26. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). + +27. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in: + + **`ResultDisplay#setFeedbackToUser()`** + ``` java + public void setFeedbackToUser(String feedbackToUser) { + requireNonNull(feedbackToUser); + resultDisplay.setText(feedbackToUser); + } + ``` + +28. Finally, you can step through until you reach the end of`MainWindow#executeCommand()`.
+ :bulb: This may be a good time to read through the [`UI` component section of the DG](../DeveloperGuide.html#ui-component) ## Conclusion diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java deleted file mode 100644 index 92f900b7916..00000000000 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.Objects; - -/** - * Represents the result of a command execution. - */ -public class CommandResult { - - private final String feedbackToUser; - - /** Help information should be shown to the user. */ - private final boolean showHelp; - - /** The application should exit. */ - private final boolean exit; - - /** - * Constructs a {@code CommandResult} with the specified fields. - */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { - this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; - } - - /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. - */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); - } - - public String getFeedbackToUser() { - return feedbackToUser; - } - - public boolean isShowHelp() { - return showHelp; - } - - public boolean isExit() { - return exit; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof CommandResult)) { - return false; - } - - CommandResult otherCommandResult = (CommandResult) other; - return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; - } - - @Override - public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.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+)(?.*)"); - - /** - * 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"); - 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: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -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; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java deleted file mode 100644 index 9e75478664b..00000000000 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ /dev/null @@ -1,85 +0,0 @@ -package seedu.address.ui; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.TextField; -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. - */ -public class CommandBox extends UiPart { - - public static final String ERROR_STYLE_CLASS = "error"; - private static final String FXML = "CommandBox.fxml"; - - private final CommandExecutor commandExecutor; - - @FXML - private TextField commandTextField; - - /** - * Creates a {@code CommandBox} with the given {@code CommandExecutor}. - */ - public CommandBox(CommandExecutor commandExecutor) { - super(FXML); - this.commandExecutor = commandExecutor; - // calls #setStyleToDefault() whenever there is a change to the text of the command box. - commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); - } - - /** - * Handles the Enter button pressed event. - */ - @FXML - private void handleCommandEntered() { - String commandText = commandTextField.getText(); - if (commandText.equals("")) { - return; - } - - try { - commandExecutor.execute(commandText); - commandTextField.setText(""); - } catch (CommandException | ParseException e) { - setStyleToIndicateCommandFailure(); - } - } - - /** - * Sets the command box style to use the default style. - */ - private void setStyleToDefault() { - commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); - } - - /** - * Sets the command box style to indicate a failed command. - */ - private void setStyleToIndicateCommandFailure() { - ObservableList styleClass = commandTextField.getStyleClass(); - - if (styleClass.contains(ERROR_STYLE_CLASS)) { - return; - } - - 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/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 9106c3aa6e5..00000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,196 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -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 - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - /** - * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. - */ - public MainWindow(Stage primaryStage, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - - // Configure the UI - setWindowDefaultSize(logic.getGuiSettings()); - - setAccelerators(); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - /** - * Sets the default size based on {@code guiSettings}. - */ - private void setWindowDefaultSize(GuiSettings guiSettings) { - primaryStage.setHeight(guiSettings.getWindowHeight()); - primaryStage.setWidth(guiSettings.getWindowWidth()); - if (guiSettings.getWindowCoordinates() != null) { - primaryStage.setX(guiSettings.getWindowCoordinates().getX()); - primaryStage.setY(guiSettings.getWindowCoordinates().getY()); - } - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { - try { - CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } - - return commandResult; - } catch (CommandException | ParseException e) { - logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); - throw e; - } - } -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/swift/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/swift/AppParameters.java index ab552c398f3..eb94b08d6c4 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/swift/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package swift; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import swift.commons.core.LogsCenter; +import swift.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/swift/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/swift/Main.java index 052a5068631..a78d1ba7e68 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/swift/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package swift; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/swift/MainApp.java similarity index 83% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/swift/MainApp.java index 4133aaa0151..8b1c879192e 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/swift/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package swift; import java.io.IOException; import java.nio.file.Path; @@ -7,36 +7,36 @@ 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.DataConversionException; -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; +import swift.commons.core.Config; +import swift.commons.core.LogsCenter; +import swift.commons.core.Version; +import swift.commons.exceptions.DataConversionException; +import swift.commons.util.ConfigUtil; +import swift.commons.util.StringUtil; +import swift.logic.Logic; +import swift.logic.LogicManager; +import swift.model.AddressBook; +import swift.model.Model; +import swift.model.ModelManager; +import swift.model.ReadOnlyAddressBook; +import swift.model.ReadOnlyUserPrefs; +import swift.model.UserPrefs; +import swift.model.util.SampleDataUtil; +import swift.storage.AddressBookStorage; +import swift.storage.JsonAddressBookStorage; +import swift.storage.JsonUserPrefsStorage; +import swift.storage.Storage; +import swift.storage.StorageManager; +import swift.storage.UserPrefsStorage; +import swift.ui.Ui; +import swift.ui.UiManager; /** * Runs the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -78,7 +78,7 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { ReadOnlyAddressBook initialData; try { addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { + if (addressBookOptional.isEmpty()) { logger.info("Data file not found. Will be starting with a sample AddressBook"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); @@ -90,7 +90,10 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { initialData = new AddressBook(); } - return new ModelManager(initialData, userPrefs); + ModelManager newModelManager = new ModelManager(initialData, userPrefs); + // Show all Tasks by Default + newModelManager.updateFilteredTaskList(Model.PREDICATE_SHOW_ALL_TASKS); + return newModelManager; } private void initLogging(Config config) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/swift/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/swift/commons/core/Config.java index 91145745521..bc115748eea 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/swift/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package swift.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/swift/commons/core/GuiSettings.java similarity index 93% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/swift/commons/core/GuiSettings.java index ba33653be67..c0a5b15cdbd 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/swift/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package swift.commons.core; import java.awt.Point; import java.io.Serializable; @@ -10,8 +10,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 900; + private static final double DEFAULT_WIDTH = 1500; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/swift/commons/core/LogsCenter.java similarity index 99% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/swift/commons/core/LogsCenter.java index 431e7185e76..f905c786f24 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/swift/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package swift.commons.core; import java.io.IOException; import java.util.Arrays; diff --git a/src/main/java/swift/commons/core/Messages.java b/src/main/java/swift/commons/core/Messages.java new file mode 100644 index 00000000000..e8e51e533ec --- /dev/null +++ b/src/main/java/swift/commons/core/Messages.java @@ -0,0 +1,16 @@ +package swift.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The contact index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d contacts listed!"; + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_PERSONS_SELECTED_OVERVIEW = "Contact Selected!"; + public static final String MESSAGE_TASKS_SELECTED_OVERVIEW = "Task Selected!"; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/swift/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/swift/commons/core/Version.java index 12142ec1e32..c9afb64700c 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/swift/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package swift.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/swift/commons/core/index/Index.java similarity index 93% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/swift/commons/core/index/Index.java index 19536439c09..ce1a341c6f8 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/swift/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package swift.commons.core.index; /** * Represents a zero-based or one-based index. @@ -45,6 +45,11 @@ public static Index fromOneBased(int oneBasedIndex) { return new Index(oneBasedIndex - 1); } + @Override + public int hashCode() { + return zeroBasedIndex; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/swift/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/swift/commons/exceptions/DataConversionException.java index 1f689bd8e3f..e59bc667a93 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/swift/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package swift.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/swift/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/swift/commons/exceptions/IllegalValueException.java index 19124db485c..3ad21c640b7 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/swift/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package swift.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/swift/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/swift/commons/util/AppUtil.java index 87aa89c0326..a07a82ff7fb 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/swift/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package swift.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import swift.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/swift/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/swift/commons/util/CollectionUtil.java index eafe4dfd681..202b4476ae3 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/swift/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package swift.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/swift/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/swift/commons/util/ConfigUtil.java index f7f8a2bd44c..8dc5671ee96 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/swift/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package swift.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.DataConversionException; +import swift.commons.core.Config; +import swift.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/swift/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/swift/commons/util/FileUtil.java index b1e2767cdd9..0f6bec8573c 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/swift/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package swift.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/swift/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/swift/commons/util/JsonUtil.java index 8ef609f055d..064cbbf0f11 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/swift/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package swift.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import swift.commons.core.LogsCenter; +import swift.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/swift/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/swift/commons/util/StringUtil.java index 61cc8c9a1cb..f6674385726 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/swift/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package swift.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/swift/logic/Logic.java similarity index 53% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/swift/logic/Logic.java index 92cd8fa605a..7d2bc19561d 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/swift/logic/Logic.java @@ -1,14 +1,16 @@ -package seedu.address.logic; +package swift.logic; import java.nio.file.Path; 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; +import swift.commons.core.GuiSettings; +import swift.logic.commands.CommandResult; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.exceptions.ParseException; +import swift.model.ReadOnlyAddressBook; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; /** * API of the Logic component @@ -26,13 +28,25 @@ public interface Logic { /** * Returns the AddressBook. * - * @see seedu.address.model.Model#getAddressBook() + * @see swift.model.Model#getAddressBook() */ ReadOnlyAddressBook getAddressBook(); /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of persons */ + ObservableList getUnfilteredPersonList(); + + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList getFilteredTaskList(); + + /** Returns an unmodifiable view of the filtered list of bridge */ + ObservableList getFilteredBridgeList(); + + /** Returns an unmodifiable view of the unfiltered list of bridge */ + ObservableList getUnfilteredBridgeList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/swift/logic/LogicManager.java similarity index 64% rename from src/main/java/seedu/address/logic/LogicManager.java rename to src/main/java/swift/logic/LogicManager.java index 9d9c6d15bdc..f9d0c3f48b1 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/swift/logic/LogicManager.java @@ -1,26 +1,29 @@ -package seedu.address.logic; +package swift.logic; import java.io.IOException; import java.nio.file.Path; import java.util.logging.Logger; 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; +import swift.commons.core.GuiSettings; +import swift.commons.core.LogsCenter; +import swift.logic.commands.Command; +import swift.logic.commands.CommandResult; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.AddressBookParser; +import swift.logic.parser.exceptions.ParseException; +import swift.model.Model; +import swift.model.ReadOnlyAddressBook; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; +import swift.storage.Storage; /** * The main LogicManager of the app. */ public class LogicManager implements Logic { + public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; private final Logger logger = LogsCenter.getLogger(LogicManager.class); @@ -64,6 +67,27 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + + @Override + public ObservableList getUnfilteredPersonList() { + return model.getUnfilteredPersonList(); + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + + @Override + public ObservableList getFilteredBridgeList() { + return model.getFilteredBridgeList(); + } + + @Override + public ObservableList getUnfilteredBridgeList() { + return model.getUnfilteredBridgeList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/swift/logic/commands/AddContactCommand.java similarity index 54% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/swift/logic/commands/AddContactCommand.java index 71656d7c5c8..520b1d88739 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/swift/logic/commands/AddContactCommand.java @@ -1,24 +1,30 @@ -package seedu.address.logic.commands; +package swift.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static swift.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static swift.logic.parser.CliSyntax.PREFIX_EMAIL; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; +import static swift.logic.parser.CliSyntax.PREFIX_PHONE; +import static swift.logic.parser.CliSyntax.PREFIX_TAG; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import java.util.ArrayList; +import java.util.List; + +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.person.Person; /** * Adds a person to the address book. */ -public class AddCommand extends Command { +public class AddContactCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "add_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG)); - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a contact to the address book. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " @@ -33,15 +39,15 @@ public class AddCommand extends Command { + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New contact added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in the address book"; private final Person toAdd; /** * Creates an AddCommand to add the specified {@code Person} */ - public AddCommand(Person person) { + public AddContactCommand(Person person) { requireNonNull(person); toAdd = person; } @@ -55,13 +61,13 @@ public CommandResult execute(Model model) throws CommandException { } model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), CommandType.CONTACTS); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + || (other instanceof AddContactCommand // instanceof handles nulls + && toAdd.equals(((AddContactCommand) other).toAdd)); } } diff --git a/src/main/java/swift/logic/commands/AddTaskCommand.java b/src/main/java/swift/logic/commands/AddTaskCommand.java new file mode 100644 index 00000000000..da0d56df03b --- /dev/null +++ b/src/main/java/swift/logic/commands/AddTaskCommand.java @@ -0,0 +1,91 @@ +package swift.logic.commands; + +import static swift.commons.util.CollectionUtil.requireAllNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static swift.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.Deadline; +import swift.model.task.Task; + +/** + * Adds a task to the address book. + */ +public class AddTaskCommand extends Command { + + public static final String COMMAND_WORD = "add_task"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(PREFIX_NAME, PREFIX_DESCRIPTION, PREFIX_DEADLINE, PREFIX_CONTACT)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a task to the task list.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_DEADLINE + "DEADLINE] (must be in dd-MM-yyyy HHmm format) " + + "[" + PREFIX_CONTACT + "CONTACT]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "CS2103T Tutorial " + + PREFIX_DESCRIPTION + "Finish questions " + + PREFIX_DEADLINE + "12-12-2022 1200 " + + PREFIX_CONTACT + "1 " + + PREFIX_CONTACT + "2"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book"; + public static final String MESSAGE_INVALID_CONTACT_INDEX = "The contact index provided is invalid"; + public static final String MESSAGE_INVALID_DATE = "Deadline must valid and in the `dd-MM-yyyy HHmm` format."; + + private final Task toAdd; + private final Collection contactIndices; + + /** + * Creates an AddTaskCommand to add the specified {@code Task} + * + * @param task Task to be added. + */ + public AddTaskCommand(Task task, Collection indices) { + requireAllNonNull(task, indices); + toAdd = task; + contactIndices = indices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireAllNonNull(model); + + if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + if (toAdd.getDeadline().map(x -> !Deadline.isValidDeadline(x.toString())).orElse(false)) { + throw new CommandException(MESSAGE_INVALID_DATE); + } + for (Index index : contactIndices) { + if (index.getZeroBased() >= model.getFilteredPersonList().size()) { + throw new CommandException(MESSAGE_INVALID_CONTACT_INDEX); + } + } + for (Index index : contactIndices) { + model.addBridge(model.getFilteredPersonList().get(index.getZeroBased()), toAdd); + } + + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTaskCommand // instanceof handles nulls + && toAdd.equals(((AddTaskCommand) other).toAdd)); + } +} diff --git a/src/main/java/swift/logic/commands/AssignCommand.java b/src/main/java/swift/logic/commands/AssignCommand.java new file mode 100644 index 00000000000..111cc793e63 --- /dev/null +++ b/src/main/java/swift/logic/commands/AssignCommand.java @@ -0,0 +1,92 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Assigns a task to a contact identified using their displayed index from the + * address book. + */ +public class AssignCommand extends Command { + + public static final String COMMAND_WORD = "assign"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(PREFIX_CONTACT, PREFIX_TASK)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Assigns the task to the contact identified by the index numbers used in the displayed contact list.\n" + + "Parameters: " + + PREFIX_CONTACT + "CONTACT_INDEX " + + PREFIX_TASK + "TASK_INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CONTACT + "1 " + + PREFIX_TASK + "2"; + + public static final String MESSAGE_ASSIGN_SUCCESS = "Task %1$s assigned to contact %2$s!"; + public static final String MESSAGE_DUPLICATE_BRIDGE = "This task is already assigned to this contact"; + + private final Index contactIndex; + private final Index taskIndex; + + /** + * Creates an AssignCommand to add the specified {@code Task} to the specified + * {@code Person} + * + * @param contactIndex Index of the contact to assign the task to. + * @param taskIndex Index of the task to be assigned. + */ + public AssignCommand(Index contactIndex, Index taskIndex) { + this.contactIndex = contactIndex; + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + List lastShownTaskList = model.getFilteredTaskList(); + + if (contactIndex.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + if (taskIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Person selectedPerson = lastShownPersonList.get(contactIndex.getZeroBased()); + Task selectedTask = lastShownTaskList.get(taskIndex.getZeroBased()); + PersonTaskBridge bridge = new PersonTaskBridge(selectedPerson.getId(), selectedTask.getId()); + + if (model.hasBridge(bridge)) { + throw new CommandException(MESSAGE_DUPLICATE_BRIDGE); + } + model.addBridge(selectedPerson, selectedTask); + + model.updateFilteredBridgeList(filteredBridge -> model.getFilteredPersonList().stream() + .anyMatch(person -> person.getId().equals(filteredBridge.getPersonId()))); + + return new CommandResult(String.format(MESSAGE_ASSIGN_SUCCESS, selectedTask, selectedPerson), + CommandType.ASSIGN); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AssignCommand // instanceof handles nulls + && contactIndex.equals(((AssignCommand) other).contactIndex) + && taskIndex.equals(((AssignCommand) other).taskIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/swift/logic/commands/ClearCommand.java similarity index 58% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/swift/logic/commands/ClearCommand.java index 9c86b1fa6e4..2c037e648f3 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/swift/logic/commands/ClearCommand.java @@ -1,9 +1,12 @@ -package seedu.address.logic.commands; +package swift.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; +import java.util.ArrayList; + +import swift.logic.parser.Prefix; +import swift.model.AddressBook; +import swift.model.Model; /** * Clears the address book. @@ -11,6 +14,8 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(); + public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; @@ -18,6 +23,6 @@ public class ClearCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); + return new CommandResult(MESSAGE_SUCCESS, CommandType.CLEAR); } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/swift/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/swift/logic/commands/Command.java index 64f18992160..eb573dbe75a 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/swift/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package swift.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import swift.logic.commands.exceptions.CommandException; +import swift.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/swift/logic/commands/CommandResult.java b/src/main/java/swift/logic/commands/CommandResult.java new file mode 100644 index 00000000000..e2362fc7615 --- /dev/null +++ b/src/main/java/swift/logic/commands/CommandResult.java @@ -0,0 +1,54 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +/** + * Represents the result of a command execution. + */ +public class CommandResult { + + private final String feedbackToUser; + + /** Type of the command. */ + private final CommandType commandType; + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public CommandResult(String feedbackToUser, CommandType commandType) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.commandType = commandType; + } + + public String getFeedbackToUser() { + return feedbackToUser; + } + + public CommandType getCommandType() { + return commandType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CommandResult)) { + return false; + } + + CommandResult otherCommandResult = (CommandResult) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && commandType == otherCommandResult.commandType; + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, commandType); + } + +} diff --git a/src/main/java/swift/logic/commands/CommandSuggestor.java b/src/main/java/swift/logic/commands/CommandSuggestor.java new file mode 100644 index 00000000000..875b6f3ec64 --- /dev/null +++ b/src/main/java/swift/logic/commands/CommandSuggestor.java @@ -0,0 +1,267 @@ +package swift.logic.commands; + +import static swift.logic.parser.CliSyntax.PREFIX_KEYWORD; + +import java.util.ArrayList; +import java.util.Collections; + +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.ArgumentMultimap; +import swift.logic.parser.ArgumentTokenizer; +import swift.logic.parser.Prefix; + +/** + * Suggests a command based on the user input. + */ +public class CommandSuggestor { + private final ArrayList commandList; + private final ArrayList> argPrefixList; + + /** + * Constructs a {@code CommandSuggestor} with predefined commands and argument prompts. + */ + public CommandSuggestor() { + commandList = new ArrayList<>(); + argPrefixList = new ArrayList<>(); + + commandList.add(AddContactCommand.COMMAND_WORD); + argPrefixList.add(AddContactCommand.ARGUMENT_PREFIXES); + + commandList.add(AddTaskCommand.COMMAND_WORD); + argPrefixList.add(AddTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(ClearCommand.COMMAND_WORD); + argPrefixList.add(ClearCommand.ARGUMENT_PREFIXES); + + commandList.add(DeleteContactCommand.COMMAND_WORD); + argPrefixList.add(DeleteContactCommand.ARGUMENT_PREFIXES); + + commandList.add(DeleteTaskCommand.COMMAND_WORD); + argPrefixList.add(DeleteTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(EditContactCommand.COMMAND_WORD); + argPrefixList.add(EditContactCommand.ARGUMENT_PREFIXES); + + commandList.add(EditTaskCommand.COMMAND_WORD); + argPrefixList.add(EditTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(ExitCommand.COMMAND_WORD); + argPrefixList.add(ExitCommand.ARGUMENT_PREFIXES); + + commandList.add(FindContactCommand.COMMAND_WORD); + argPrefixList.add(FindContactCommand.ARGUMENT_PREFIXES); + + commandList.add(FindTaskCommand.COMMAND_WORD); + argPrefixList.add(FindTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(HelpCommand.COMMAND_WORD); + argPrefixList.add(HelpCommand.ARGUMENT_PREFIXES); + + commandList.add(ListContactCommand.COMMAND_WORD); + argPrefixList.add(ListContactCommand.ARGUMENT_PREFIXES); + + commandList.add(ListTaskCommand.COMMAND_WORD); + argPrefixList.add(ListTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(SelectContactCommand.COMMAND_WORD); + argPrefixList.add(SelectContactCommand.ARGUMENT_PREFIXES); + + commandList.add(SelectTaskCommand.COMMAND_WORD); + argPrefixList.add(SelectTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(MarkTaskCommand.COMMAND_WORD); + argPrefixList.add(MarkTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(UnmarkTaskCommand.COMMAND_WORD); + argPrefixList.add(UnmarkTaskCommand.ARGUMENT_PREFIXES); + + commandList.add(AssignCommand.COMMAND_WORD); + argPrefixList.add(AssignCommand.ARGUMENT_PREFIXES); + + commandList.add(UnassignCommand.COMMAND_WORD); + argPrefixList.add(UnassignCommand.ARGUMENT_PREFIXES); + } + + /** + * Suggests a command based on the user input. + * + * @param userInput User input. + * @return Suggested command. + * @throws CommandException If the user input is invalid. + */ + public String suggestCommand(String userInput) throws CommandException { + assert userInput != null && !userInput.isEmpty(); + String[] userInputArray = userInput.split(" ", 2); + String commandWord = userInputArray[0]; + String suggestedCommand = ""; + boolean isCommandComplete = userInput.contains(" "); + + for (String command : commandList) { + if (command.startsWith(commandWord)) { + if (isCommandComplete && !command.equals(commandWord)) { + continue; + } + suggestedCommand = command; + break; + } + } + + if (suggestedCommand.equals("") && !commandWord.equals("")) { + throw new CommandException("Invalid command"); + } + ArrayList argPrefixes = argPrefixList.get(commandList.indexOf(suggestedCommand)); + + if (userInputArray.length > 1) { + if (userInput.charAt(userInput.length() - 1) == ' ') { + userInput = userInput.substring(0, userInput.length() - 1); + } + return userInput + suggestArguments(argPrefixes, userInputArray[1]); + } else { + return suggestedCommand + suggestArguments(argPrefixes, ""); + } + } + + /** + * Returns the new user input when user auto-completes the command. + * + * @param userInput Current User Input. + * @param commandSuggestion Current Command Suggestion + * @return New User Input. + */ + public String autocompleteCommand(String userInput, String commandSuggestion) { + // Command suggested but not yet entered by user + String suggestedCommand = commandSuggestion.substring(userInput.length()); + boolean isCommandComplete = userInput.contains(" "); + int autocompleteUptoIndex; + if (isCommandComplete) { + autocompleteUptoIndex = suggestedCommand.indexOf(isCommandComplete ? "/" : " ") + 1; + } else { + return getLongestMatchingPrefixSuggestion(userInput); + } + + // If command has no prefix arguments + if (autocompleteUptoIndex == 0) { + autocompleteUptoIndex = suggestedCommand.length(); + } + + String autocompletedCommand = suggestedCommand.substring(0, autocompleteUptoIndex); + if (!autocompletedCommand.contains("<")) { + userInput = userInput + autocompletedCommand; + } + return userInput; + } + + /** + * Suggests prompts for arguments based on the user input. + * + * @param argPrefixes Argument prefixes for specified command. + * @param userInput Current user input. + * @return Suggested arguments. + * @throws CommandException If the user input is invalid. + */ + public String suggestArguments( + ArrayList argPrefixes, String userInput) throws CommandException { + ArgumentMultimap argumentMultimap = + ArgumentTokenizer.tokenize(" " + userInput, argPrefixes.toArray(new Prefix[] {})); + String argumentSuggestion = ""; + String[] userInputArray = userInput.trim().split(" "); + Prefix currPrefix = null; + boolean isIndexRequired = argPrefixes.contains(new Prefix("")); + boolean hasKeyword = argPrefixes.contains(PREFIX_KEYWORD); + boolean hasPrefix = (!userInput.isEmpty() && (!isIndexRequired || userInputArray.length > 1)); + + // Check if user input for index is valid (only if required) + if (isIndexRequired) { + if (userInputArray[0].equals("")) { + argumentSuggestion += " " + argPrefixes.get(0).getUserPrompt(); + } else { + if (!userInputArray[0].matches("-?\\d+(\\.\\d+)?")) { + throw new CommandException("Invalid index"); + } + } + } + + if (hasKeyword) { + // Check if user input contains keyword + if (userInput.equals("")) { + argumentSuggestion += " " + argPrefixes.get(0).getUserPrompt(); + } + argumentMultimap.put(PREFIX_KEYWORD, ""); + } else if (hasPrefix && !userInputArray[userInputArray.length - 1].contains("/")) { + // Check if user is trying to autocomplete a prefix + currPrefix = new Prefix(userInputArray[userInputArray.length - 1] + "/"); + argumentMultimap.put(currPrefix, ""); + + if (argPrefixes.contains(currPrefix)) { + argumentSuggestion += "/ "; + } else if (!userInput.contains("/")) { + throw new CommandException("Invalid prefix"); + } + } + + for (Prefix prefix : argPrefixes) { + if (argumentMultimap.getValue(prefix).isEmpty()) { + argumentSuggestion += " " + prefix + prefix.getUserPrompt(); + } + } + return argumentSuggestion; + } + + /** + * Gets the longest matching prefix from all possible command suggestions depending on the user + * input. + * + * @param userInput User input. + * @return Longest matching prefix. + * @throws CommandException If the user input is invalid. + */ + public String getLongestMatchingPrefixSuggestion(String userInput) { + assert userInput != null && !userInput.isEmpty(); + String[] userInputArray = userInput.split(" ", 2); + String commandWord = userInputArray[0]; + boolean isCommandComplete = userInput.contains(" "); + ArrayList matchingCommands = new ArrayList<>(); + + for (String command : commandList) { + if (command.startsWith(commandWord)) { + if (isCommandComplete && !command.equals(commandWord)) { + continue; + } + matchingCommands.add(command + " "); + } + } + return getLongestMatchingPrefix(matchingCommands); + } + + /** + * Gets longest matching prefix from list of strings. + * + * @param matchingCommands List of strings. + * @return Longest matching prefix. + */ + public String getLongestMatchingPrefix(ArrayList matchingCommands) { + Collections.sort(matchingCommands); + int size = matchingCommands.size(); + if (size == 0) { + return ""; + } + + if (size == 1) { + return matchingCommands.get(0); + } + + // find the minimum length from first and last string + int end = + Math.min(matchingCommands.get(0).length(), matchingCommands.get(size - 1).length()); + + // find the common prefix between the first and last string + int i = 0; + while (i < end + && matchingCommands.get(0).charAt(i) == matchingCommands.get(size - 1).charAt(i)) { + i++; + } + + String prefix = matchingCommands.get(0).substring(0, i); + return prefix; + } +} diff --git a/src/main/java/swift/logic/commands/CommandType.java b/src/main/java/swift/logic/commands/CommandType.java new file mode 100644 index 00000000000..2679d211d25 --- /dev/null +++ b/src/main/java/swift/logic/commands/CommandType.java @@ -0,0 +1,16 @@ +package swift.logic.commands; + +/** + * Enum of the type of commands that can be input into the command box. + */ +public enum CommandType { + HELP, + EXIT, + CONTACTS, + TASKS, + CLEAR, + SELECT_TASK, + SELECT_CONTACT, + ASSIGN, + UNASSIGN +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/swift/logic/commands/DeleteContactCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/swift/logic/commands/DeleteContactCommand.java index 02fd256acba..d934c99dd25 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/swift/logic/commands/DeleteContactCommand.java @@ -1,32 +1,35 @@ -package seedu.address.logic.commands; +package swift.logic.commands; import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.List; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.person.Person; /** * Deletes a person identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public class DeleteContactCommand extends Command { - public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_WORD = "delete_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(new Prefix("", "contact_index"))); public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the contact identified by the index number used in the displayed contact list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Contact: %1$s"; private final Index targetIndex; - public DeleteCommand(Index targetIndex) { + public DeleteContactCommand(Index targetIndex) { this.targetIndex = targetIndex; } @@ -41,13 +44,13 @@ 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, personToDelete)); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete), CommandType.CONTACTS); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + || (other instanceof DeleteContactCommand // instanceof handles nulls + && targetIndex.equals(((DeleteContactCommand) other).targetIndex)); // state check } } diff --git a/src/main/java/swift/logic/commands/DeleteTaskCommand.java b/src/main/java/swift/logic/commands/DeleteTaskCommand.java new file mode 100644 index 00000000000..ac293eaf615 --- /dev/null +++ b/src/main/java/swift/logic/commands/DeleteTaskCommand.java @@ -0,0 +1,57 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.Task; + +/** + * Deletes a task identified using it's displayed index from the address book. + */ +public class DeleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "delete_task"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(new Prefix("", "task_index"))); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted task: %1$s"; + + private final Index targetIndex; + + public DeleteTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTask(taskToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, taskToDelete), CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/swift/logic/commands/EditContactCommand.java similarity index 75% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/swift/logic/commands/EditContactCommand.java index 7e36114902f..1fd48b71f0a 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/swift/logic/commands/EditContactCommand.java @@ -1,40 +1,45 @@ -package seedu.address.logic.commands; +package swift.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - +import static swift.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static swift.logic.parser.CliSyntax.PREFIX_EMAIL; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; +import static swift.logic.parser.CliSyntax.PREFIX_PHONE; +import static swift.logic.parser.CliSyntax.PREFIX_TAG; +import static swift.model.Model.PREDICATE_SHOW_ALL_PEOPLE; + +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import java.util.UUID; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.commons.util.CollectionUtil; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.person.Address; +import swift.model.person.Email; +import swift.model.person.Person; +import swift.model.person.PersonName; +import swift.model.person.Phone; +import swift.model.tag.Tag; /** * Edits the details of an existing person in the address book. */ -public class EditCommand extends Command { +public class EditContactCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "edit_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(new Prefix("", "contact_index"), PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_TAG)); - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the contact identified " + + "by the index number used in the displayed contact list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " @@ -46,9 +51,9 @@ public class EditCommand extends Command { + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Contact: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in the address book."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -57,7 +62,7 @@ public class EditCommand extends Command { * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditContactCommand(Index index, EditPersonDescriptor editPersonDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); @@ -82,8 +87,8 @@ public CommandResult execute(Model model) throws CommandException { } model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson), CommandType.CONTACTS); } /** @@ -93,13 +98,14 @@ public CommandResult execute(Model model) throws CommandException { private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { assert personToEdit != null; - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + UUID updatedId = personToEdit.getId(); + PersonName updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedId, updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); } @Override @@ -110,12 +116,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditCommand)) { + if (!(other instanceof EditContactCommand)) { return false; } // state check - EditCommand e = (EditCommand) other; + EditContactCommand e = (EditContactCommand) other; return index.equals(e.index) && editPersonDescriptor.equals(e.editPersonDescriptor); } @@ -125,7 +131,7 @@ public boolean equals(Object other) { * corresponding field value of the person. */ public static class EditPersonDescriptor { - private Name name; + private PersonName name; private Phone phone; private Email email; private Address address; @@ -152,11 +158,11 @@ public boolean isAnyFieldEdited() { return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); } - public void setName(Name name) { + public void setName(PersonName name) { this.name = name; } - public Optional getName() { + public Optional getName() { return Optional.ofNullable(name); } diff --git a/src/main/java/swift/logic/commands/EditTaskCommand.java b/src/main/java/swift/logic/commands/EditTaskCommand.java new file mode 100644 index 00000000000..40fc71890dd --- /dev/null +++ b/src/main/java/swift/logic/commands/EditTaskCommand.java @@ -0,0 +1,213 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static swift.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.commons.util.CollectionUtil; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.Task; +import swift.model.task.TaskName; + +/** + * Edits the details of an existing task in the address book. + */ +public class EditTaskCommand extends Command { + + public static final String COMMAND_WORD = "edit_task"; + public static final ArrayList ARGUMENT_PREFIXES = + new ArrayList<>(List.of(new Prefix("", "task_index"), PREFIX_NAME, PREFIX_DESCRIPTION, PREFIX_DEADLINE)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the task identified " + + "by the index number used in the displayed task list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_DEADLINE + "DEADLINE] (must be in dd-MM-yyyy HHmm format)\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NAME + "Finish Assignment " + + PREFIX_DESCRIPTION + "CS1231 " + + PREFIX_DEADLINE + "12-12-2022 1800"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited Task: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book."; + + private final Index index; + private final EditTaskDescriptor editTaskDescriptor; + + /** + * @param index of the task in the filtered task list to edit + * @param editTaskDescriptor details to edit the task with + */ + public EditTaskCommand(Index index, EditTaskDescriptor editTaskDescriptor) { + requireNonNull(index); + requireNonNull(editTaskDescriptor); + + this.index = index; + this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + + if (!taskToEdit.equals(editedTask) && model.hasTask(editedTask)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask), CommandType.TASKS); + } + + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. + */ + public static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { + assert taskToEdit != null; + + UUID updatedId = taskToEdit.getId(); + TaskName updatedTaskName = editTaskDescriptor.getTaskName().orElse(taskToEdit.getName()); + Optional updatedDescription = editTaskDescriptor.getDescription().or(taskToEdit::getDescription); + Optional updatedDeadline = editTaskDescriptor.getDeadline().or(taskToEdit::getDeadline); + boolean updatedisDone = editTaskDescriptor.isDone().orElse(taskToEdit.isDone()); + + return new Task(updatedId, updatedTaskName, updatedDescription, updatedDeadline, updatedisDone); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskCommand)) { + return false; + } + + // state check + EditTaskCommand e = (EditTaskCommand) other; + return index.equals(e.index) + && editTaskDescriptor.equals(e.editTaskDescriptor); + } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private TaskName taskName; + private Description description; + private Deadline deadline; + private Index contactIndex; + private Boolean isDone; + + public EditTaskDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setTaskName(toCopy.taskName); + setDescription(toCopy.description); + setDeadline(toCopy.deadline); + setContactIndex(toCopy.contactIndex); + setIsDone(toCopy.isDone); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(taskName, description, deadline, contactIndex); + } + + public void setTaskName(TaskName taskName) { + this.taskName = taskName; + } + + public Optional getTaskName() { + return Optional.ofNullable(taskName); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setDeadline(Deadline deadline) { + this.deadline = deadline; + } + + public Optional getDeadline() { + return Optional.ofNullable(deadline); + } + + public void setContactIndex(Index contactIndex) { + this.contactIndex = contactIndex; + } + + public Optional getContactIndex() { + return Optional.ofNullable(contactIndex); + } + + public void setIsDone(Boolean isDone) { + this.isDone = isDone; + } + + public Optional isDone() { + return Optional.ofNullable(isDone); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskDescriptor)) { + return false; + } + + // state check + EditTaskDescriptor e = (EditTaskDescriptor) other; + + return getTaskName().equals(e.getTaskName()) + && getDescription().equals(e.getDescription()) + && getDeadline().equals(e.getDeadline()) + && getContactIndex().equals(e.getContactIndex()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/swift/logic/commands/ExitCommand.java similarity index 52% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/swift/logic/commands/ExitCommand.java index 3dd85a8ba90..48fb9b610f7 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/swift/logic/commands/ExitCommand.java @@ -1,6 +1,9 @@ -package seedu.address.logic.commands; +package swift.logic.commands; -import seedu.address.model.Model; +import java.util.ArrayList; + +import swift.logic.parser.Prefix; +import swift.model.Model; /** * Terminates the program. @@ -8,12 +11,13 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(); public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, CommandType.EXIT); } } diff --git a/src/main/java/swift/logic/commands/FindContactCommand.java b/src/main/java/swift/logic/commands/FindContactCommand.java new file mode 100644 index 00000000000..58fe1ff0d45 --- /dev/null +++ b/src/main/java/swift/logic/commands/FindContactCommand.java @@ -0,0 +1,49 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_KEYWORD; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.person.PersonNameContainsKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindContactCommand extends Command { + + public static final String COMMAND_WORD = "find_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(PREFIX_KEYWORD)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final PersonNameContainsKeywordsPredicate predicate; + + public FindContactCommand(PersonNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()), + CommandType.CONTACTS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindContactCommand // instanceof handles nulls + && predicate.equals(((FindContactCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/swift/logic/commands/FindTaskCommand.java b/src/main/java/swift/logic/commands/FindTaskCommand.java new file mode 100644 index 00000000000..3fa7f46caf7 --- /dev/null +++ b/src/main/java/swift/logic/commands/FindTaskCommand.java @@ -0,0 +1,49 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_KEYWORD; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.TaskNameContainsKeywordsPredicate; + +/** + * Finds and lists all tasks in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindTaskCommand extends Command { + + public static final String COMMAND_WORD = "find_task"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(PREFIX_KEYWORD)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all tasks whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " discuss about UI"; + + private final TaskNameContainsKeywordsPredicate predicate; + + public FindTaskCommand(TaskNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size()), + CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTaskCommand // instanceof handles nulls + && predicate.equals(((FindTaskCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/swift/logic/commands/HelpCommand.java similarity index 62% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/swift/logic/commands/HelpCommand.java index bf824f91bd0..00e0dd9880b 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/swift/logic/commands/HelpCommand.java @@ -1,6 +1,9 @@ -package seedu.address.logic.commands; +package swift.logic.commands; -import seedu.address.model.Model; +import java.util.ArrayList; + +import swift.logic.parser.Prefix; +import swift.model.Model; /** * Format full help instructions for every command for display. @@ -8,6 +11,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(); public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; @@ -16,6 +20,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, CommandType.HELP); } } diff --git a/src/main/java/swift/logic/commands/ListContactCommand.java b/src/main/java/swift/logic/commands/ListContactCommand.java new file mode 100644 index 00000000000..027a0736a91 --- /dev/null +++ b/src/main/java/swift/logic/commands/ListContactCommand.java @@ -0,0 +1,30 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.model.Model.PREDICATE_SHOW_ALL_PEOPLE; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; + +import swift.logic.parser.Prefix; +import swift.model.Model; + +/** + * Lists all persons in the address book to the user. + */ +public class ListContactCommand extends Command { + + public static final String COMMAND_WORD = "list_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(); + + public static final String MESSAGE_SUCCESS = "Listed all contacts"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(MESSAGE_SUCCESS, CommandType.CONTACTS); + } +} diff --git a/src/main/java/swift/logic/commands/ListTaskCommand.java b/src/main/java/swift/logic/commands/ListTaskCommand.java new file mode 100644 index 00000000000..5912bdc7e63 --- /dev/null +++ b/src/main/java/swift/logic/commands/ListTaskCommand.java @@ -0,0 +1,41 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.model.Model.PREDICATE_SHOW_ALL_PEOPLE; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; + +import swift.commons.core.Messages; +import swift.logic.parser.Prefix; +import swift.model.Model; + +/** + * Finds and lists all tasks in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class ListTaskCommand extends Command { + + public static final String COMMAND_WORD = "list_task"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(); + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays all tasks as a list with\n" + + "index numbers.\n" + + "Parameters: KEYWORD\n" + + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); + return new CommandResult( + String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, model.getFilteredTaskList().size()), + CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + return (other instanceof ListTaskCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/swift/logic/commands/MarkTaskCommand.java b/src/main/java/swift/logic/commands/MarkTaskCommand.java new file mode 100644 index 00000000000..73528b33f6c --- /dev/null +++ b/src/main/java/swift/logic/commands/MarkTaskCommand.java @@ -0,0 +1,90 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.commands.EditTaskCommand.EditTaskDescriptor; +import static swift.logic.commands.EditTaskCommand.createEditedTask; +import static swift.logic.parser.CliSyntax.PREFIX_TASK_INDEX; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.Task; + +/** + * Marks an existing task in the address book as completed. + */ +public class MarkTaskCommand extends Command { + + public static final String COMMAND_WORD = "mark"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(PREFIX_TASK_INDEX)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task identified by the index number " + + "used in the displayed task list as completed.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_MARK_TASK_SUCCESS = "Marked Task %1$s as completed."; + public static final String MESSAGE_TASK_ALREADY_COMPLETED = + "This task is already marked as completed."; + + private final Index index; + private final EditTaskDescriptor markTaskDescriptor; + + + /** + * @param index of the task in the filtered task list to mark as complete + */ + public MarkTaskCommand(Index index) { + requireNonNull(index); + this.index = index; + markTaskDescriptor = new EditTaskDescriptor(); + markTaskDescriptor.setIsDone(true); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, markTaskDescriptor); + + if (taskToEdit.isDone()) { + throw new CommandException(MESSAGE_TASK_ALREADY_COMPLETED); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + + return new CommandResult(String.format(MESSAGE_MARK_TASK_SUCCESS, editedTask), + CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MarkTaskCommand)) { + return false; + } + + // state check + MarkTaskCommand e = (MarkTaskCommand) other; + return index.equals(e.index) && markTaskDescriptor.equals(e.markTaskDescriptor); + } +} diff --git a/src/main/java/swift/logic/commands/SelectContactCommand.java b/src/main/java/swift/logic/commands/SelectContactCommand.java new file mode 100644 index 00000000000..d4beca90295 --- /dev/null +++ b/src/main/java/swift/logic/commands/SelectContactCommand.java @@ -0,0 +1,73 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.model.Model.PREDICATE_SHOW_ALL_BRIDGE; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Selects a task identified using it's displayed index from the address book. + */ +public class SelectContactCommand extends Command { + + public static final String COMMAND_WORD = "select_contact"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(new Prefix("", "contact_index"))); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the contact identified by the index number used in the displayed contact list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_CONTACT_SUCCESS = "Contact Selected!"; + + private final Index targetIndex; + + public SelectContactCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person selectedPerson = lastShownPersonList.get(targetIndex.getZeroBased()); + model.updateFilteredPersonList((person) -> person.equals(selectedPerson)); + + model.updateFilteredBridgeList(PREDICATE_SHOW_ALL_BRIDGE); + model.updateFilteredBridgeList((bridge) -> bridge.getPersonId().equals(selectedPerson.getId())); + List bridgeList = model.getFilteredBridgeList(); + + // Predicate to check whether task exists within a bridgeList + Predicate isTaskExist = (task) -> bridgeList.stream() + .anyMatch((bridge) -> bridge.getTaskId().equals(task.getId())); + + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + model.updateFilteredTaskList(isTaskExist); + + return new CommandResult(Messages.MESSAGE_PERSONS_SELECTED_OVERVIEW, CommandType.SELECT_CONTACT); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectContactCommand // instanceof handles nulls + && targetIndex.equals(((SelectContactCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/swift/logic/commands/SelectTaskCommand.java b/src/main/java/swift/logic/commands/SelectTaskCommand.java new file mode 100644 index 00000000000..73448c7505c --- /dev/null +++ b/src/main/java/swift/logic/commands/SelectTaskCommand.java @@ -0,0 +1,75 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.model.Model.PREDICATE_SHOW_ALL_BRIDGE; +import static swift.model.Model.PREDICATE_SHOW_ALL_PEOPLE; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Command to display all tasks using its specified index from address book. + */ +public class SelectTaskCommand extends Command { + public static final String COMMAND_WORD = "select_task"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>(List.of(new Prefix("", "task_index"))); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the task identified by the index number used in the displayed task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_TASK_SUCCESS = "Task Selected!"; + + private final Index targetIndex; + + public SelectTaskCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownTaskList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + // Display only selected task + Task selectedTask = lastShownTaskList.get(targetIndex.getZeroBased()); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + model.updateFilteredTaskList(task -> task.equals(selectedTask)); + + model.updateFilteredBridgeList(PREDICATE_SHOW_ALL_BRIDGE); + model.updateFilteredBridgeList(bridge -> bridge.getTaskId().equals(selectedTask.getId())); + + List bridgeList = model.getFilteredBridgeList(); + + Predicate isAssociatedContact = contact -> bridgeList.stream() + .anyMatch((bridge) -> bridge.getPersonId().equals(contact.getId())); + + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); + model.updateFilteredPersonList(isAssociatedContact); + + return new CommandResult(Messages.MESSAGE_TASKS_SELECTED_OVERVIEW, CommandType.SELECT_TASK); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectTaskCommand // instanceof handles nulls + && targetIndex.equals(((SelectTaskCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/swift/logic/commands/UnassignCommand.java b/src/main/java/swift/logic/commands/UnassignCommand.java new file mode 100644 index 00000000000..418c6556817 --- /dev/null +++ b/src/main/java/swift/logic/commands/UnassignCommand.java @@ -0,0 +1,92 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Unassigns a task to a contact identified using their displayed index from the + * address book. + */ +public class UnassignCommand extends Command { + public static final String COMMAND_WORD = "unassign"; + public static final ArrayList ARGUMENT_PREFIXES = new ArrayList<>( + List.of(PREFIX_CONTACT, PREFIX_TASK)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unassigns the task to the contact identified by the index numbers used in the " + + "displayed contact list.\n" + + "Parameters: " + + PREFIX_CONTACT + "CONTACT_INDEX " + + PREFIX_TASK + "TASK_INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CONTACT + "1 " + + PREFIX_TASK + "2"; + + public static final String MESSAGE_UNASSIGN_SUCCESS = "Task %1$s unassigned to contact %2$s!"; + public static final String MESSAGE_BRIDGE_DOESNT_EXIST = "This task is not assigned to this contact!"; + + private final Index contactIndex; + private final Index taskIndex; + + /** + * Creates an UnassignCommand to add the specified {@code Task} to the specified + * {@code Person} + * + * @param contactIndex Index of the contact to assign the task to. + * @param taskIndex Index of the task to be assigned. + */ + public UnassignCommand(Index contactIndex, Index taskIndex) { + this.contactIndex = contactIndex; + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + List lastShownTaskList = model.getFilteredTaskList(); + + if (contactIndex.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + if (taskIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Person selectedPerson = lastShownPersonList.get(contactIndex.getZeroBased()); + Task selectedTask = lastShownTaskList.get(taskIndex.getZeroBased()); + PersonTaskBridge bridge = new PersonTaskBridge(selectedPerson.getId(), selectedTask.getId()); + + if (!model.hasBridge(bridge)) { + throw new CommandException(MESSAGE_BRIDGE_DOESNT_EXIST); + } + model.deleteBridge(bridge); + + model.updateFilteredBridgeList(filteredBridge -> model.getFilteredPersonList().stream() + .anyMatch(person -> person.getId().equals(filteredBridge.getPersonId()))); + + return new CommandResult(String.format(MESSAGE_UNASSIGN_SUCCESS, selectedTask, selectedPerson), + CommandType.UNASSIGN); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnassignCommand // instanceof handles nulls + && contactIndex.equals(((UnassignCommand) other).contactIndex) + && taskIndex.equals(((UnassignCommand) other).taskIndex)); // state check + } +} diff --git a/src/main/java/swift/logic/commands/UnmarkTaskCommand.java b/src/main/java/swift/logic/commands/UnmarkTaskCommand.java new file mode 100644 index 00000000000..6d18d9cac99 --- /dev/null +++ b/src/main/java/swift/logic/commands/UnmarkTaskCommand.java @@ -0,0 +1,91 @@ +package swift.logic.commands; + +import static java.util.Objects.requireNonNull; +import static swift.logic.commands.EditTaskCommand.EditTaskDescriptor; +import static swift.logic.commands.EditTaskCommand.createEditedTask; +import static swift.logic.parser.CliSyntax.PREFIX_TASK_INDEX; +import static swift.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.List; + +import swift.commons.core.Messages; +import swift.commons.core.index.Index; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.Prefix; +import swift.model.Model; +import swift.model.task.Task; + +/** + * Marks an existing task in the address book as incomplete. + */ +public class UnmarkTaskCommand extends Command { + + public static final String COMMAND_WORD = "unmark"; + public static final ArrayList ARGUMENT_PREFIXES = + new ArrayList<>(List.of(PREFIX_TASK_INDEX)); + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the task identified by the index number " + + "used in the displayed task list as incomplete.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNMARK_TASK_SUCCESS = "Marked Task %1$s as incomplete."; + public static final String MESSAGE_TASK_ALREADY_INCOMPLETE = + "This task is already marked as incomplete."; + + private final Index index; + private final EditTaskDescriptor unmarkTaskDescriptor; + + + /** + * @param index of the task in the filtered task list to mark as incomplete + */ + public UnmarkTaskCommand(Index index) { + requireNonNull(index); + this.index = index; + unmarkTaskDescriptor = new EditTaskDescriptor(); + unmarkTaskDescriptor.setIsDone(false); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, unmarkTaskDescriptor); + + if (!taskToEdit.isDone()) { + throw new CommandException(MESSAGE_TASK_ALREADY_INCOMPLETE); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + + return new CommandResult(String.format(MESSAGE_UNMARK_TASK_SUCCESS, editedTask), + CommandType.TASKS); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnmarkTaskCommand)) { + return false; + } + + // state check + UnmarkTaskCommand e = (UnmarkTaskCommand) other; + return index.equals(e.index) && unmarkTaskDescriptor.equals(e.unmarkTaskDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/swift/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/swift/logic/commands/exceptions/CommandException.java index a16bd14f2cd..db3c84e3d2a 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/swift/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package swift.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/swift/logic/parser/AddContactCommandParser.java similarity index 56% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/swift/logic/parser/AddContactCommandParser.java index 3b8bfa035e8..3ec08e03e7a 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/swift/logic/parser/AddContactCommandParser.java @@ -1,52 +1,54 @@ -package seedu.address.logic.parser; +package swift.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static swift.logic.parser.CliSyntax.PREFIX_EMAIL; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; +import static swift.logic.parser.CliSyntax.PREFIX_PHONE; +import static swift.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; +import java.util.UUID; 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 swift.logic.commands.AddContactCommand; +import swift.logic.parser.exceptions.ParseException; +import swift.model.person.Address; +import swift.model.person.Email; +import swift.model.person.Person; +import swift.model.person.PersonName; +import swift.model.person.Phone; +import swift.model.tag.Tag; /** * Parses input arguments and creates a new AddCommand object */ -public class AddCommandParser implements Parser { +public class AddContactCommandParser 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 { + public AddContactCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddContactCommand.MESSAGE_USAGE)); } - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + UUID id = UUID.randomUUID(); + PersonName name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(id, name, phone, email, address, tagList); - return new AddCommand(person); + return new AddContactCommand(person); } /** diff --git a/src/main/java/swift/logic/parser/AddTaskCommandParser.java b/src/main/java/swift/logic/parser/AddTaskCommandParser.java new file mode 100644 index 00000000000..8fb91281239 --- /dev/null +++ b/src/main/java/swift/logic/parser/AddTaskCommandParser.java @@ -0,0 +1,70 @@ +package swift.logic.parser; + +import static java.util.Objects.requireNonNull; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static swift.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import swift.commons.core.index.Index; +import swift.logic.commands.AddTaskCommand; +import swift.logic.parser.exceptions.ParseException; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.Task; +import swift.model.task.TaskName; + +/** + * Parses input arguments and creates a new AddTaskCommand object + */ +public class AddTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * AddTaskCommand + * and returns an AddTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddTaskCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize( + args, PREFIX_NAME, PREFIX_DESCRIPTION, PREFIX_DEADLINE, PREFIX_CONTACT); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTaskCommand.MESSAGE_USAGE)); + } + + TaskName name = ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_NAME).get()); + + Optional description = Optional.empty(); + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + description = Optional.of(ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + Optional deadline = Optional.empty(); + if (argMultimap.getValue(PREFIX_DEADLINE).isPresent()) { + deadline = Optional.of(ParserUtil.parseDeadline((argMultimap.getValue(PREFIX_DEADLINE).get()))); + } + Set indices = ParserUtil.parseIndices(argMultimap.getAllValues(PREFIX_CONTACT)); + + Task task = new Task(UUID.randomUUID(), name, description, deadline); + + return new AddTaskCommand(task, indices); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/swift/logic/parser/AddressBookParser.java b/src/main/java/swift/logic/parser/AddressBookParser.java new file mode 100644 index 00000000000..83b18178989 --- /dev/null +++ b/src/main/java/swift/logic/parser/AddressBookParser.java @@ -0,0 +1,100 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import swift.logic.commands.AddContactCommand; +import swift.logic.commands.AddTaskCommand; +import swift.logic.commands.AssignCommand; +import swift.logic.commands.ClearCommand; +import swift.logic.commands.Command; +import swift.logic.commands.DeleteContactCommand; +import swift.logic.commands.DeleteTaskCommand; +import swift.logic.commands.EditContactCommand; +import swift.logic.commands.EditTaskCommand; +import swift.logic.commands.ExitCommand; +import swift.logic.commands.FindContactCommand; +import swift.logic.commands.FindTaskCommand; +import swift.logic.commands.HelpCommand; +import swift.logic.commands.ListContactCommand; +import swift.logic.commands.ListTaskCommand; +import swift.logic.commands.MarkTaskCommand; +import swift.logic.commands.SelectContactCommand; +import swift.logic.commands.SelectTaskCommand; +import swift.logic.commands.UnassignCommand; +import swift.logic.commands.UnmarkTaskCommand; +import swift.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+)(?.*)"); + + /** + * 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"); + switch (commandWord) { + case AddContactCommand.COMMAND_WORD: + return new AddContactCommandParser().parse(arguments); + case EditContactCommand.COMMAND_WORD: + return new EditContactCommandParser().parse(arguments); + case DeleteContactCommand.COMMAND_WORD: + return new DeleteContactCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + case FindContactCommand.COMMAND_WORD: + return new FindContactCommandParser().parse(arguments); + case SelectContactCommand.COMMAND_WORD: + return new SelectContactCommandParser().parse(arguments); + case ListContactCommand.COMMAND_WORD: + return new ListContactCommand(); + case ListTaskCommand.COMMAND_WORD: + return new ListTaskCommand(); + case AddTaskCommand.COMMAND_WORD: + return new AddTaskCommandParser().parse(arguments); + case EditTaskCommand.COMMAND_WORD: + return new EditTaskCommandParser().parse(arguments); + case DeleteTaskCommand.COMMAND_WORD: + return new DeleteTaskCommandParser().parse(arguments); + case FindTaskCommand.COMMAND_WORD: + return new FindTaskCommandParser().parse(arguments); + case SelectTaskCommand.COMMAND_WORD: + return new SelectTaskCommandParser().parse(arguments); + case MarkTaskCommand.COMMAND_WORD: + return new MarkTaskCommandParser().parse(arguments); + case UnmarkTaskCommand.COMMAND_WORD: + return new UnmarkTaskCommandParser().parse(arguments); + case AssignCommand.COMMAND_WORD: + return new AssignCommandParser().parse(arguments); + case UnassignCommand.COMMAND_WORD: + return new UnassignCommandParser().parse(arguments); + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/swift/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/swift/logic/parser/ArgumentMultimap.java index 954c8e18f8e..39ecf42bf74 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/swift/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package swift.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/swift/logic/parser/ArgumentTokenizer.java similarity index 94% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/swift/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..61fad9d5345 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/swift/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package swift.logic.parser; import java.util.ArrayList; import java.util.Arrays; @@ -78,7 +78,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from /** * Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the * extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in - * {@code argsString}. + * {@code argsString}. Preamble is optional. * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} @@ -89,9 +89,11 @@ private static ArgumentMultimap extractArguments(String argsString, List prefix1.getStartPosition() - prefix2.getStartPosition()); - // Insert a PrefixPosition to represent the preamble - PrefixPosition preambleMarker = new PrefixPosition(new Prefix(""), 0); - prefixPositions.add(0, preambleMarker); + // Insert a PrefixPosition to represent the preamble if preamble exists + if (prefixPositions.isEmpty() || prefixPositions.get(0).getStartPosition() != 0) { + PrefixPosition preambleMarker = new PrefixPosition(new Prefix(""), 0); + prefixPositions.add(0, preambleMarker); + } // Add a dummy PrefixPosition to represent the end of the string PrefixPosition endPositionMarker = new PrefixPosition(new Prefix(""), argsString.length()); diff --git a/src/main/java/swift/logic/parser/AssignCommandParser.java b/src/main/java/swift/logic/parser/AssignCommandParser.java new file mode 100644 index 00000000000..ba7ad150fb4 --- /dev/null +++ b/src/main/java/swift/logic/parser/AssignCommandParser.java @@ -0,0 +1,55 @@ +package swift.logic.parser; + +import static java.util.Objects.requireNonNull; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.stream.Stream; + +import swift.commons.core.index.Index; +import swift.logic.commands.AssignCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AssignCommand object + */ +public class AssignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * AssignCommand + * and returns a AssignCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AssignCommand parse(String args) throws ParseException { + requireNonNull(args); + System.out.println(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize( + args, PREFIX_CONTACT, PREFIX_TASK); + + if (!arePrefixesPresent(argMultimap, PREFIX_CONTACT, PREFIX_TASK) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE)); + } + + try { + Index contactIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CONTACT).get()); + Index taskIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TASK).get()); + + return new AssignCommand(contactIndex, taskIndex); + } catch (Exception e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE), e); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/swift/logic/parser/CliSyntax.java b/src/main/java/swift/logic/parser/CliSyntax.java new file mode 100644 index 00000000000..77416f5bbf6 --- /dev/null +++ b/src/main/java/swift/logic/parser/CliSyntax.java @@ -0,0 +1,22 @@ +package swift.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_NAME = new Prefix("n/", "name"); + public static final Prefix PREFIX_PHONE = new Prefix("p/", "phone"); + public static final Prefix PREFIX_EMAIL = new Prefix("e/", "email"); + public static final Prefix PREFIX_ADDRESS = new Prefix("a/", "address"); + public static final Prefix PREFIX_TAG = new Prefix("t/", "tag"); + public static final Prefix PREFIX_TASK = new Prefix("t/", "task_index"); + public static final Prefix PREFIX_CONTACT = new Prefix("c/", "contact_index"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/", "description"); + public static final Prefix PREFIX_DEADLINE = new Prefix("dl/", "deadline"); + // PREFIX_KEYWORD is not a prefix, but a placeholder for the keyword argument + public static final Prefix PREFIX_KEYWORD = new Prefix("keyword", "keyword"); + public static final Prefix PREFIX_CONTACT_INDEX = new Prefix("", "contact_index"); + public static final Prefix PREFIX_TASK_INDEX = new Prefix("", "task_index"); +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/swift/logic/parser/DeleteContactCommandParser.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/swift/logic/parser/DeleteContactCommandParser.java index 522b93081cc..1dc0c3fbbe0 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/swift/logic/parser/DeleteContactCommandParser.java @@ -1,28 +1,28 @@ -package seedu.address.logic.parser; +package swift.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.commons.core.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 swift.commons.core.index.Index; +import swift.logic.commands.DeleteContactCommand; +import swift.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object */ -public class DeleteCommandParser implements Parser { +public class DeleteContactCommandParser 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 { + public DeleteContactCommand parse(String args) throws ParseException { try { Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + return new DeleteContactCommand(index); } catch (ParseException pe) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteContactCommand.MESSAGE_USAGE), pe); } } diff --git a/src/main/java/swift/logic/parser/DeleteTaskCommandParser.java b/src/main/java/swift/logic/parser/DeleteTaskCommandParser.java new file mode 100644 index 00000000000..4134f84fd38 --- /dev/null +++ b/src/main/java/swift/logic/parser/DeleteTaskCommandParser.java @@ -0,0 +1,28 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import swift.commons.core.index.Index; +import swift.logic.commands.DeleteTaskCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteTaskCommand object + */ +public class DeleteTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTaskCommand + * and returns a DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTaskCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteTaskCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/swift/logic/parser/EditContactCommandParser.java similarity index 71% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/swift/logic/parser/EditContactCommandParser.java index 845644b7dea..ac4c006b05d 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/swift/logic/parser/EditContactCommandParser.java @@ -1,35 +1,35 @@ -package seedu.address.logic.parser; +package swift.logic.parser; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static swift.logic.parser.CliSyntax.PREFIX_EMAIL; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; +import static swift.logic.parser.CliSyntax.PREFIX_PHONE; +import static swift.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 swift.commons.core.index.Index; +import swift.logic.commands.EditContactCommand; +import swift.logic.commands.EditContactCommand.EditPersonDescriptor; +import swift.logic.parser.exceptions.ParseException; +import swift.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object */ -public class EditCommandParser implements Parser { +public class EditContactCommandParser 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 { + public EditContactCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); @@ -39,7 +39,8 @@ public EditCommand parse(String args) throws ParseException { try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditContactCommand.MESSAGE_USAGE), pe); } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -58,10 +59,10 @@ public EditCommand parse(String args) throws ParseException { parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + throw new ParseException(EditContactCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditContactCommand(index, editPersonDescriptor); } /** diff --git a/src/main/java/swift/logic/parser/EditTaskCommandParser.java b/src/main/java/swift/logic/parser/EditTaskCommandParser.java new file mode 100644 index 00000000000..d06b49f14b6 --- /dev/null +++ b/src/main/java/swift/logic/parser/EditTaskCommandParser.java @@ -0,0 +1,60 @@ +package swift.logic.parser; + +import static java.util.Objects.requireNonNull; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static swift.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static swift.logic.parser.CliSyntax.PREFIX_NAME; + +import swift.commons.core.index.Index; +import swift.logic.commands.EditTaskCommand; +import swift.logic.commands.EditTaskCommand.EditTaskDescriptor; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditTaskCommand object + */ +public class EditTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditTaskCommand + * and returns an EditTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditTaskCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DESCRIPTION, PREFIX_DEADLINE, PREFIX_CONTACT); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE), pe); + } + + EditTaskDescriptor editTaskDescriptor = new EditTaskDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editTaskDescriptor.setTaskName(ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editTaskDescriptor.setDescription(ParserUtil.parseDescription( + argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_DEADLINE).isPresent()) { + editTaskDescriptor.setDeadline(ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_DEADLINE).get())); + } + if (argMultimap.getValue(PREFIX_CONTACT).isPresent()) { + editTaskDescriptor.setContactIndex(ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CONTACT).get())); + } + + if (!editTaskDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditTaskCommand.MESSAGE_NOT_EDITED); + } + + return new EditTaskCommand(index, editTaskDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/swift/logic/parser/FindContactCommandParser.java similarity index 51% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/swift/logic/parser/FindContactCommandParser.java index 4fb71f23103..42068f01da5 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/swift/logic/parser/FindContactCommandParser.java @@ -1,33 +1,33 @@ -package seedu.address.logic.parser; +package swift.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import swift.logic.commands.FindContactCommand; +import swift.logic.parser.exceptions.ParseException; +import swift.model.person.PersonNameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class FindContactCommandParser 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 { + public FindContactCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindContactCommand.MESSAGE_USAGE)); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindContactCommand(new PersonNameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } } diff --git a/src/main/java/swift/logic/parser/FindTaskCommandParser.java b/src/main/java/swift/logic/parser/FindTaskCommandParser.java new file mode 100644 index 00000000000..8c86ae6c83e --- /dev/null +++ b/src/main/java/swift/logic/parser/FindTaskCommandParser.java @@ -0,0 +1,33 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import swift.logic.commands.FindTaskCommand; +import swift.logic.parser.exceptions.ParseException; +import swift.model.task.TaskNameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindTaskCommand object + */ +public class FindTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindTaskCommand + * and returns a FindTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTaskCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTaskCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindTaskCommand(new TaskNameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/swift/logic/parser/MarkTaskCommandParser.java b/src/main/java/swift/logic/parser/MarkTaskCommandParser.java new file mode 100644 index 00000000000..2af3d12220c --- /dev/null +++ b/src/main/java/swift/logic/parser/MarkTaskCommandParser.java @@ -0,0 +1,28 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import swift.commons.core.index.Index; +import swift.logic.commands.MarkTaskCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new MarkTaskCommand object + */ +public class MarkTaskCommandParser { + /** + * Parses the given {@code String} of arguments in the context of the MarkTaskCommand and + * returns a MarkTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public MarkTaskCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new MarkTaskCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MarkTaskCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/swift/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/swift/logic/parser/Parser.java index d6551ad8e3f..fc0720e9d4d 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/swift/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package swift.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import swift.logic.commands.Command; +import swift.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/swift/logic/parser/ParserUtil.java similarity index 56% rename from src/main/java/seedu/address/logic/parser/ParserUtil.java rename to src/main/java/swift/logic/parser/ParserUtil.java index b117acb9c55..aff5090fbdd 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/swift/logic/parser/ParserUtil.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package swift.logic.parser; import static java.util.Objects.requireNonNull; @@ -6,14 +6,17 @@ import java.util.HashSet; 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 swift.commons.core.index.Index; +import swift.commons.util.StringUtil; +import swift.logic.parser.exceptions.ParseException; +import swift.model.person.Address; +import swift.model.person.Email; +import swift.model.person.PersonName; +import swift.model.person.Phone; +import swift.model.tag.Tag; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.TaskName; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -25,6 +28,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -35,19 +39,31 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses {@code Collection oneBasedIndices} into a {@code Set}. + */ + public static Set parseIndices(Collection oneBasedIndices) throws ParseException { + requireNonNull(oneBasedIndices); + final Set indexSet = new HashSet<>(); + for (String oneBasedIndex : oneBasedIndices) { + indexSet.add(parseIndex(oneBasedIndex)); + } + return indexSet; + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. * * @throws ParseException if the given {@code name} is invalid. */ - public static Name parseName(String name) throws ParseException { + public static PersonName parseName(String name) throws ParseException { requireNonNull(name); String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); + if (!PersonName.isValidName(trimmedName)) { + throw new ParseException(PersonName.MESSAGE_CONSTRAINTS); } - return new Name(trimmedName); + return new PersonName(trimmedName); } /** @@ -121,4 +137,50 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String name} into a {@code TaskName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static TaskName parseTaskName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!TaskName.isValidName(trimmedName)) { + throw new ParseException(TaskName.MESSAGE_CONSTRAINTS); + } + return new TaskName(trimmedName); + } + + /** + * Parses a {@code String description} into a {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code description} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String deadline} into a {@code Deadline}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code deadline} is invalid. + */ + public static Deadline parseDeadline(String deadline) throws ParseException { + requireNonNull(deadline); + String trimmedDeadline = deadline.trim(); + if (!Deadline.isValidDeadline(trimmedDeadline)) { + throw new ParseException(Deadline.MESSAGE_CONSTRAINTS); + } + return new Deadline(trimmedDeadline); + } + } diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/swift/logic/parser/Prefix.java similarity index 63% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/swift/logic/parser/Prefix.java index c859d5fa5db..d887907455c 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/swift/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package swift.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. @@ -6,11 +6,22 @@ */ public class Prefix { private final String prefix; + private String userPrompt; public Prefix(String prefix) { this.prefix = prefix; } + /** + * Constructs a {@code Prefix} with given prefix and user prompt. + * @param prefix Prefix + * @param userPrompt User prompt for prefix + */ + public Prefix(String prefix, String userPrompt) { + this.prefix = prefix; + this.userPrompt = userPrompt; + } + public String getPrefix() { return prefix; } @@ -19,6 +30,10 @@ public String toString() { return getPrefix(); } + public String getUserPrompt() { + return userPrompt == null ? "" : "<" + userPrompt + ">"; + } + @Override public int hashCode() { return prefix == null ? 0 : prefix.hashCode(); diff --git a/src/main/java/swift/logic/parser/SelectContactCommandParser.java b/src/main/java/swift/logic/parser/SelectContactCommandParser.java new file mode 100644 index 00000000000..f0385d05874 --- /dev/null +++ b/src/main/java/swift/logic/parser/SelectContactCommandParser.java @@ -0,0 +1,28 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import swift.commons.core.index.Index; +import swift.logic.commands.SelectContactCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectContactCommand object + */ +public class SelectContactCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SelectContactCommand + * and returns a SelectContactCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SelectContactCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SelectContactCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectContactCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/swift/logic/parser/SelectTaskCommandParser.java b/src/main/java/swift/logic/parser/SelectTaskCommandParser.java new file mode 100644 index 00000000000..585ce436066 --- /dev/null +++ b/src/main/java/swift/logic/parser/SelectTaskCommandParser.java @@ -0,0 +1,28 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import swift.commons.core.index.Index; +import swift.logic.commands.SelectContactCommand; +import swift.logic.commands.SelectTaskCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectTaskCommand object + */ +public class SelectTaskCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SelectTaskCommand + * and returns a SelectTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SelectTaskCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SelectTaskCommand(index); + } catch (ParseException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectContactCommand.MESSAGE_USAGE), e); + } + } +} diff --git a/src/main/java/swift/logic/parser/UnassignCommandParser.java b/src/main/java/swift/logic/parser/UnassignCommandParser.java new file mode 100644 index 00000000000..29bd666a53e --- /dev/null +++ b/src/main/java/swift/logic/parser/UnassignCommandParser.java @@ -0,0 +1,55 @@ +package swift.logic.parser; + +import static java.util.Objects.requireNonNull; +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static swift.logic.parser.CliSyntax.PREFIX_CONTACT; +import static swift.logic.parser.CliSyntax.PREFIX_TASK; + +import java.util.stream.Stream; + +import swift.commons.core.index.Index; +import swift.logic.commands.UnassignCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AssignCommand object + */ +public class UnassignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * AssignCommand + * and returns a AssignCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnassignCommand parse(String args) throws ParseException { + requireNonNull(args); + System.out.println(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize( + args, PREFIX_CONTACT, PREFIX_TASK); + + if (!arePrefixesPresent(argMultimap, PREFIX_CONTACT, PREFIX_TASK) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignCommand.MESSAGE_USAGE)); + } + + try { + Index contactIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CONTACT).get()); + Index taskIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TASK).get()); + + return new UnassignCommand(contactIndex, taskIndex); + } catch (Exception e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignCommand.MESSAGE_USAGE), e); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values + * in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/swift/logic/parser/UnmarkTaskCommandParser.java b/src/main/java/swift/logic/parser/UnmarkTaskCommandParser.java new file mode 100644 index 00000000000..040e6fe5b34 --- /dev/null +++ b/src/main/java/swift/logic/parser/UnmarkTaskCommandParser.java @@ -0,0 +1,29 @@ +package swift.logic.parser; + +import static swift.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import swift.commons.core.index.Index; +import swift.logic.commands.UnmarkTaskCommand; +import swift.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UnmarkTaskCommand object + */ +public class UnmarkTaskCommandParser { + /** + * Parses the given {@code String} of arguments in the context of the UnmarkTaskCommand and + * returns a UnmarkTaskCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnmarkTaskCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnmarkTaskCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkTaskCommand.MESSAGE_USAGE), + pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/swift/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/swift/logic/parser/exceptions/ParseException.java index 158a1a54c1c..51759e30d9b 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/swift/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package swift.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import swift.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/swift/model/AddressBook.java b/src/main/java/swift/model/AddressBook.java new file mode 100644 index 00000000000..9f05bd20ae3 --- /dev/null +++ b/src/main/java/swift/model/AddressBook.java @@ -0,0 +1,223 @@ +package swift.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import swift.model.bridge.PersonTaskBridge; +import swift.model.bridge.PersonTaskBridgeList; +import swift.model.person.Person; +import swift.model.person.UniquePersonList; +import swift.model.task.Task; +import swift.model.task.UniqueTaskList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class AddressBook implements ReadOnlyAddressBook { + + private final UniquePersonList persons; + private final UniqueTaskList tasks; + private final PersonTaskBridgeList bridges; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + persons = new UniquePersonList(); + tasks = new UniqueTaskList(); + bridges = new PersonTaskBridgeList(); + } + + public AddressBook() { + } + + /** + * Creates an AddressBook using the Persons in the {@code toBeCopied} + */ + public AddressBook(ReadOnlyAddressBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the person list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setPersons(List persons) { + this.persons.setPersons(persons); + } + + /** + * Replaces the contents of the task list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); + } + + /** + * Replaces the contents of the bridge list with {@code bridges}. + * {@code bridges} must not contain duplicate bridges. + */ + public void setBridges(List bridges) { + this.bridges.setBridges(bridges); + } + + /** + * Resets the existing data of this {@code AddressBook} with {@code newData}. + */ + public void resetData(ReadOnlyAddressBook newData) { + requireNonNull(newData); + + setPersons(newData.getPersonList()); + setTasks(newData.getTaskList()); + setBridges(newData.getBridgeList()); + } + + //// person-level operations + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + public boolean hasPerson(Person person) { + requireNonNull(person); + return persons.contains(person); + } + + /** + * Adds a person to the address book. + * The person must not already exist in the address book. + */ + public void addPerson(Person p) { + persons.add(p); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the address book. + * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + */ + public void setPerson(Person target, Person editedPerson) { + requireNonNull(editedPerson); + + persons.setPerson(target, editedPerson); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removePerson(Person key) { + persons.remove(key); + } + + //// task-level operations + + /** + * Returns true if a task with the same name and owner as {@code task} exists in the address book. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + /** + * Adds a task to the address book. + * The task must not already exist in the address book. + */ + public void addTask(Task t) { + tasks.add(t); + } + + /** + * Replaces the given task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the address book. + * The task name and owner of {@code editedTask} must not be the same as another existing task in the address book. + */ + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + + tasks.setTask(target, editedTask); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + + //// bridge-level operations + + /** + * Returns true if a bridge with the same person and task as {@code bridge} exists in the address book. + */ + public boolean hasBridge(PersonTaskBridge bridge) { + requireNonNull(bridge); + return bridges.contains(bridge); + } + + /** + * Adds a bridge to the address book. + * The bridge must not already exist in the address book. + */ + public void addBridge(PersonTaskBridge b) { + bridges.add(b); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeBridge(PersonTaskBridge key) { + bridges.remove(key); + } + + //// util methods + + @Override + public String toString() { + return persons.asUnmodifiableObservableList().size() + " persons, " + + tasks.asUnmodifiableObservableList().size() + " tasks"; + // TODO: refine later + } + + @Override + public ObservableList getPersonList() { + return persons.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getTaskList() { + return tasks.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getBridgeList() { + return bridges.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressBook // instanceof handles nulls + && persons.equals(((AddressBook) other).persons)) + && tasks.equals(((AddressBook) other).tasks) + && bridges.equals(((AddressBook) other).bridges); + } + + @Override + public int hashCode() { + return persons.hashCode() ^ tasks.hashCode() ^ bridges.hashCode(); + } +} diff --git a/src/main/java/swift/model/Model.java b/src/main/java/swift/model/Model.java new file mode 100644 index 00000000000..503f6de23a5 --- /dev/null +++ b/src/main/java/swift/model/Model.java @@ -0,0 +1,171 @@ +package swift.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import swift.commons.core.GuiSettings; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_PEOPLE = unused -> true; + Predicate PREDICATE_HIDE_ALL_PEOPLE = unused -> false; + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; + Predicate PREDICATE_HIDE_ALL_TASKS = unused -> false; + Predicate PREDICATE_SHOW_ALL_BRIDGE = unused -> true; + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' address book file path. + */ + Path getAddressBookFilePath(); + + /** + * Sets the user prefs' address book file path. + */ + void setAddressBookFilePath(Path addressBookFilePath); + + /** + * Replaces address book data with the data in {@code addressBook}. + */ + void setAddressBook(ReadOnlyAddressBook addressBook); + + /** Returns the AddressBook */ + ReadOnlyAddressBook getAddressBook(); + + /** + * Returns true if a person with the same identity as {@code person} exists in the address book. + */ + boolean hasPerson(Person person); + + /** + * Deletes the given person. + * The person must exist in the address book. + */ + void deletePerson(Person target); + + /** + * Adds the given person. + * {@code person} must not already exist in the address book. + */ + void addPerson(Person person); + + /** + * Replaces the given person {@code target} with {@code editedPerson}. + * {@code target} must exist in the address book. + * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + */ + void setPerson(Person target, Person editedPerson); + + /** Returns an unmodifiable view of the filtered person list */ + ObservableList getFilteredPersonList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredBridgeList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredBridgeList(); + + /** + * Updates the filter of the filtered task list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTaskList(Predicate predicate); + + /** + * Returns true if a task with the same identity as {@code task} exists in the address book. + */ + boolean hasTask(Task task); + + /** + * Deletes the given task. + * The task must exist in the address book. + */ + void deleteTask(Task target); + + /** + * Adds the given task. + * {@code task} must not already exist in the address book. + */ + void addTask(Task task); + + /** + * Returns true if a bridge exists in the address book. + * + * @param bridge the bridge to check + * @return true if the bridge exists + */ + boolean hasBridge(PersonTaskBridge bridge); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the address book. + * The task name and owner of {@code editedTask} must not be the same as another existing task in the address book. + */ + void setTask(Task target, Task editedTask); + + /** + * Adds a relationship between the given task and person. + * + * @param person The person to be added to the task. + * @param task The task to be added to the person. + */ + void addBridge(Person person, Task task); + + /** + * Adds a relationship between the given task and person. + * + * @param bridge The bridge to be added. + */ + void addBridge(PersonTaskBridge bridge); + + /** + * Deletes a relationship between a given task and person. + * + * @param bridge The bridge to be deleted. + */ + void deleteBridge(PersonTaskBridge bridge); + + /** Returns an unmodifiable view of the unfiltered bridge list */ + ObservableList getUnfilteredBridgeList(); + + /** Returns an unmodifiable view of the unfiltered person list */ + ObservableList getUnfilteredPersonList(); +} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/swift/model/ModelManager.java similarity index 55% rename from src/main/java/seedu/address/model/ModelManager.java rename to src/main/java/swift/model/ModelManager.java index 86c1df298d7..ff044f7ff20 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/swift/model/ModelManager.java @@ -1,7 +1,7 @@ -package seedu.address.model; +package swift.model; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; import java.util.function.Predicate; @@ -9,9 +9,11 @@ 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; +import swift.commons.core.GuiSettings; +import swift.commons.core.LogsCenter; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; /** * Represents the in-memory model of the address book data. @@ -22,6 +24,8 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredTasks; + private final FilteredList filteredBridges; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +38,8 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredTasks = new FilteredList<>(this.addressBook.getTaskList()); + filteredBridges = new FilteredList<>(this.addressBook.getBridgeList()); } public ModelManager() { @@ -101,7 +107,7 @@ public void deletePerson(Person target) { @Override public void addPerson(Person person) { addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PEOPLE); } @Override @@ -111,6 +117,90 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + + @Override + public void deleteTask(Task target) { + addressBook.removeTask(target); + } + + @Override + public void addTask(Task task) { + addressBook.addTask(task); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + } + + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + addressBook.setTask(target, editedTask); + } + + @Override + public boolean hasBridge(PersonTaskBridge bridge) { + requireNonNull(bridge); + return addressBook.hasBridge(bridge); + } + + @Override + public void addBridge(Person person, Task task) { + requireAllNonNull(person, task); + addressBook.addBridge(new PersonTaskBridge(person.getId(), task.getId())); + } + + @Override + public void addBridge(PersonTaskBridge bridge) { + requireNonNull(bridge); + addressBook.addBridge(bridge); + } + + @Override + public void deleteBridge(PersonTaskBridge target) { + addressBook.removeBridge(target); + } + + //=========== Filtered Task List Accessors ============================================================= + /** + * Returns an unmodifiable view of the list of {@code Task} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredTaskList() { + return filteredTasks.sorted(Task::compareTo); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + requireNonNull(predicate); + filteredTasks.setPredicate(predicate); + } + + //=========== Filtered Bridge List Accessors ============================================================= + /** + * Returns an unmodifiable view of the list of {@code Task} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredBridgeList() { + return filteredBridges; + } + + @Override + public ObservableList getUnfilteredBridgeList() { + return addressBook.getBridgeList(); + } + + @Override + public void updateFilteredBridgeList(Predicate predicate) { + requireNonNull(predicate); + filteredBridges.setPredicate(predicate); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -122,6 +212,11 @@ public ObservableList getFilteredPersonList() { return filteredPersons; } + @Override + public ObservableList getUnfilteredPersonList() { + return addressBook.getPersonList(); + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); @@ -144,7 +239,8 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && filteredTasks.equals(other.filteredTasks); } } diff --git a/src/main/java/swift/model/ReadOnlyAddressBook.java b/src/main/java/swift/model/ReadOnlyAddressBook.java new file mode 100644 index 00000000000..8232c9defda --- /dev/null +++ b/src/main/java/swift/model/ReadOnlyAddressBook.java @@ -0,0 +1,30 @@ +package swift.model; + +import javafx.collections.ObservableList; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyAddressBook { + + /** + * Returns an unmodifiable view of the persons list. + * This list will not contain any duplicate persons. + */ + ObservableList getPersonList(); + + /** + * Returns an unmodifiable view of the tasks list. + * This list will not contain any duplicate tasks. + */ + ObservableList getTaskList(); + + /** + * Returns an unmodifiable view of the person-task bridge list. + * This list will not contain any duplicate bridges. + */ + ObservableList getBridgeList(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/swift/model/ReadOnlyUserPrefs.java similarity index 70% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/swift/model/ReadOnlyUserPrefs.java index befd58a4c73..e25d29ec6f7 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/swift/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package swift.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import swift.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/swift/model/UserPrefs.java similarity index 96% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/swift/model/UserPrefs.java index 25a5fd6eab9..729bcc26d11 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/swift/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package swift.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 swift.commons.core.GuiSettings; /** * Represents User's preferences. diff --git a/src/main/java/swift/model/bridge/PersonTaskBridge.java b/src/main/java/swift/model/bridge/PersonTaskBridge.java new file mode 100644 index 00000000000..ffc5251a7ec --- /dev/null +++ b/src/main/java/swift/model/bridge/PersonTaskBridge.java @@ -0,0 +1,45 @@ +package swift.model.bridge; + +import static swift.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.UUID; + +/** + * Represents a many-to-many relationship between Person and Task. + */ +public class PersonTaskBridge { + private final UUID personId; + private final UUID taskId; + + /** + * Initializes PersonTaskBridge with the given personId and taskId. + * + * @param personId A unique identifier for the person. + * @param taskId A unique identifier for the task. + */ + public PersonTaskBridge(UUID personId, UUID taskId) { + requireAllNonNull(personId, taskId); + this.personId = personId; + this.taskId = taskId; + } + + public UUID getPersonId() { + return personId; + } + + public UUID getTaskId() { + return taskId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof PersonTaskBridge) { + PersonTaskBridge other = (PersonTaskBridge) obj; + return personId.equals(other.personId) && taskId.equals(other.taskId); + } + return false; + } +} diff --git a/src/main/java/swift/model/bridge/PersonTaskBridgeList.java b/src/main/java/swift/model/bridge/PersonTaskBridgeList.java new file mode 100644 index 00000000000..775b9c6f4ab --- /dev/null +++ b/src/main/java/swift/model/bridge/PersonTaskBridgeList.java @@ -0,0 +1,137 @@ +package swift.model.bridge; + +import static java.util.Objects.requireNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import swift.model.bridge.exceptions.BridgeNotFoundException; +import swift.model.bridge.exceptions.DuplicateBridgeException; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Represents a list of person-task bridges. + */ +public class PersonTaskBridgeList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent person-task bridge as the + * given argument. + * + * @param toCheck The person-task bridge to check for. + */ + public boolean contains(PersonTaskBridge toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a person-task bridge to the list. + * The person-task bridge must not already exist in the list. + * + * @param toAdd The person-task bridge to add. + */ + public void add(PersonTaskBridge toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateBridgeException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent person-task bridge from the list. + * The person-task bridge must exist in the list. + * + * @param toRemove The person-task bridge to be removed. + */ + public void remove(PersonTaskBridge toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new BridgeNotFoundException(); + } + } + + /** + * Removes all person-task bridges associated with a person from the list. + * + * @param person The person whose person-task bridges are to be removed. + */ + public void removePerson(Person person) { + requireNonNull(person); + internalList.removeIf(bridge -> bridge.getPersonId().equals(person.getId())); + } + + /** + * Removes all person-task bridges associated with a task from the list. + * + * @param task The task whose person-task bridges are to be removed. + */ + public void removeTask(Task task) { + requireNonNull(task); + internalList.removeIf(bridge -> bridge.getTaskId().equals(task.getId())); + } + + /** + * Replaces the contents of this list with {@code bridges}. + * {@code bridges} must not contain duplicate bridges. + * + * @param bridges The list of bridges to replace the current list with. + */ + public void setBridges(List bridges) { + requireAllNonNull(bridges); + if (!bridgesAreUnique(bridges)) { + throw new DuplicateBridgeException(); + } + + internalList.setAll(bridges); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PersonTaskBridgeList // instanceof handles nulls + && internalList.equals(((PersonTaskBridgeList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code bridges} contains only unique bridges. + * + * @param bridges A list of bridges. + */ + private boolean bridgesAreUnique(List bridges) { + for (int i = 0; i < bridges.size() - 1; i++) { + for (int j = i + 1; j < bridges.size(); j++) { + if (bridges.get(i).equals(bridges.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/swift/model/bridge/exceptions/BridgeNotFoundException.java b/src/main/java/swift/model/bridge/exceptions/BridgeNotFoundException.java new file mode 100644 index 00000000000..9372473a620 --- /dev/null +++ b/src/main/java/swift/model/bridge/exceptions/BridgeNotFoundException.java @@ -0,0 +1,7 @@ +package swift.model.bridge.exceptions; + +/** + * Signals that the operation is unable to find the specified bridge. + */ +public class BridgeNotFoundException extends RuntimeException { +} diff --git a/src/main/java/swift/model/bridge/exceptions/DuplicateBridgeException.java b/src/main/java/swift/model/bridge/exceptions/DuplicateBridgeException.java new file mode 100644 index 00000000000..0a33735fbf0 --- /dev/null +++ b/src/main/java/swift/model/bridge/exceptions/DuplicateBridgeException.java @@ -0,0 +1,13 @@ +package swift.model.bridge.exceptions; + +/** + * Signals that the operation will result in duplicate PersonTaskBridge + * (PersonTaskBridge are + * considered duplicates if they have the same + * person ID and task ID). + */ +public class DuplicateBridgeException extends RuntimeException { + public DuplicateBridgeException() { + super("Operation would result in duplicate bridges"); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/swift/model/person/Address.java similarity index 93% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/swift/model/person/Address.java index 60472ca22a0..9e625a258c3 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/swift/model/person/Address.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package swift.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.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/swift/model/person/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/swift/model/person/Email.java index f866e7133de..04e7e2f46de 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/swift/model/person/Email.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package swift.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.commons.util.AppUtil.checkArgument; /** * Represents a Person's email in the address book. diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/swift/model/person/Person.java similarity index 78% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/swift/model/person/Person.java index 8ff1d83fe89..c3df01c147c 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/swift/model/person/Person.java @@ -1,22 +1,24 @@ -package seedu.address.model.person; +package swift.model.person; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; -import seedu.address.model.tag.Tag; +import swift.model.tag.Tag; /** * Represents a Person in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ public class Person { + private final UUID id; // Identity fields - private final Name name; + private final PersonName name; private final Phone phone; private final Email email; @@ -27,8 +29,9 @@ public class Person { /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(UUID id, PersonName name, Phone phone, Email email, Address address, Set tags) { + requireAllNonNull(id, name, phone, email, address, tags); + this.id = id; this.name = name; this.phone = phone; this.email = email; @@ -36,7 +39,11 @@ public Person(Name name, Phone phone, Email email, Address address, Set tag this.tags.addAll(tags); } - public Name getName() { + public UUID getId() { + return id; + } + + public PersonName getName() { return name; } @@ -70,7 +77,10 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getName().equals(getName()) + && otherPerson.getPhone().equals(getPhone()) + && otherPerson.getEmail().equals(getEmail()) + && otherPerson.getAddress().equals(getAddress()); } /** @@ -98,7 +108,7 @@ public boolean equals(Object other) { @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(id, name, phone, email, address, tags); } @Override diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/swift/model/person/PersonName.java similarity index 66% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/swift/model/person/PersonName.java index 79244d71cf7..82513c1ac37 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/swift/model/person/PersonName.java @@ -1,22 +1,23 @@ -package seedu.address.model.person; +package swift.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.commons.util.AppUtil.checkArgument; /** * Represents a Person's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ -public class Name { +public class PersonName { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphanumeric characters, commas, hyphen, apostrophes and spaces, " + + "and it should not be blank"; /* * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = "^[\\p{Alnum}][\\p{Alnum}\\s\\-\\.\\']*$"; public final String fullName; @@ -25,7 +26,7 @@ public class Name { * * @param name A valid name. */ - public Name(String name) { + public PersonName(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); fullName = name; @@ -47,8 +48,8 @@ public String toString() { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + || (other instanceof PersonName // instanceof handles nulls + && fullName.equals(((PersonName) other).fullName)); // state check } @Override diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/swift/model/person/PersonNameContainsKeywordsPredicate.java similarity index 56% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/swift/model/person/PersonNameContainsKeywordsPredicate.java index c9b5868427c..b5599ac0a3c 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/swift/model/person/PersonNameContainsKeywordsPredicate.java @@ -1,17 +1,17 @@ -package seedu.address.model.person; +package swift.model.person; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import swift.commons.util.StringUtil; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class PersonNameContainsKeywordsPredicate implements Predicate { private final List keywords; - public NameContainsKeywordsPredicate(List keywords) { + public PersonNameContainsKeywordsPredicate(List keywords) { this.keywords = keywords; } @@ -24,8 +24,8 @@ public boolean test(Person person) { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check + || (other instanceof PersonNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PersonNameContainsKeywordsPredicate) other).keywords)); // state check } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/swift/model/person/Phone.java similarity index 93% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/swift/model/person/Phone.java index 872c76b382f..3071c58ea75 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/swift/model/person/Phone.java @@ -1,7 +1,7 @@ -package seedu.address.model.person; +package swift.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.commons.util.AppUtil.checkArgument; /** * Represents a Person's phone number in the address book. diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/swift/model/person/UniquePersonList.java similarity index 94% rename from src/main/java/seedu/address/model/person/UniquePersonList.java rename to src/main/java/swift/model/person/UniquePersonList.java index 0fee4fe57e6..8e92e569f6d 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/swift/model/person/UniquePersonList.java @@ -1,15 +1,15 @@ -package seedu.address.model.person; +package swift.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import swift.model.person.exceptions.DuplicatePersonException; +import swift.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/swift/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/swift/model/person/exceptions/DuplicatePersonException.java index d7290f59442..bc69fcc4f68 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/swift/model/person/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package swift.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/swift/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/swift/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..384d94b74f8 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/swift/model/person/exceptions/PersonNotFoundException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package swift.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/swift/model/tag/Tag.java similarity index 93% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/swift/model/tag/Tag.java index b0ea7e7dad7..38a856fa204 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/swift/model/tag/Tag.java @@ -1,7 +1,7 @@ -package seedu.address.model.tag; +package swift.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static swift.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. diff --git a/src/main/java/swift/model/task/Deadline.java b/src/main/java/swift/model/task/Deadline.java new file mode 100644 index 00000000000..1941fee13ff --- /dev/null +++ b/src/main/java/swift/model/task/Deadline.java @@ -0,0 +1,69 @@ +package swift.model.task; + +import static java.util.Objects.requireNonNull; +import static swift.commons.util.AppUtil.checkArgument; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; + +/** + * Represents a Task's deadline in the address book. + * Guarantees: immutable; valid as declared in {@link #isValidDeadline(String)} + */ +public class Deadline implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Deadline must be in `dd-MM-yyyy HHmm` format."; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("dd-MM-uuuu HHmm").withResolverStyle(ResolverStyle.STRICT); + + public final LocalDateTime deadline; + + /** + * Constructs a {@code Deadline}. + * + * @param deadline A valid deadline. + */ + public Deadline(String deadline) { + requireNonNull(deadline); + checkArgument(isValidDeadline(deadline), MESSAGE_CONSTRAINTS); + this.deadline = LocalDateTime.parse(deadline, DATE_TIME_FORMATTER); + } + + /** + * Returns true if a given string is a valid deadline. + */ + public static boolean isValidDeadline(String test) { + try { + LocalDateTime.parse(test, DATE_TIME_FORMATTER); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + @Override + public int compareTo(Deadline other) { + return deadline.compareTo(other.deadline); + } + + @Override + public String toString() { + return DATE_TIME_FORMATTER.format(deadline); + } + + @Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof Deadline + && deadline.equals(((Deadline) other).deadline)); + } + + @Override + public int hashCode() { + return deadline.hashCode(); + } +} diff --git a/src/main/java/swift/model/task/Description.java b/src/main/java/swift/model/task/Description.java new file mode 100644 index 00000000000..7f829aa844a --- /dev/null +++ b/src/main/java/swift/model/task/Description.java @@ -0,0 +1,58 @@ +package swift.model.task; + +import static java.util.Objects.requireNonNull; +import static swift.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task's description in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = + "Descriptions should only contain alphanumeric characters, spaces, and special " + + "characters ($&+,:;=?@#|'<>.\\-^*()%!/)"; + + /* + * Only alphanumeric characters, spaces, and special characters ($&+,:;=?@#|'<>.\-^*()%!) + * are allowed. + */ + public static final String VALIDATION_REGEX = "^[\\p{Alnum}/$&+,:;=?@#|'<>.\\\\-^*()%! ]*$"; + + public final String description; + + /** + * Constructs a {@code Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + this.description = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Description + && description.equals(((Description) other).description)); + } + + @Override + public int hashCode() { + return description.hashCode(); + } +} diff --git a/src/main/java/swift/model/task/Task.java b/src/main/java/swift/model/task/Task.java new file mode 100644 index 00000000000..46010911284 --- /dev/null +++ b/src/main/java/swift/model/task/Task.java @@ -0,0 +1,123 @@ +package swift.model.task; + +import static swift.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * Represents a Task in the address book. + */ +public class Task implements Comparable { + private final UUID id; + + private final TaskName name; + + private final Optional description; + + private final Optional deadline; + + private final boolean isDone; + + /** + * Constructs a {@code Task}. + * + * @param id A unique identifier for the task. + * @param name A valid task name. + * @param description A valid and optional description. + * @param deadline A valid and optional deadline. + */ + public Task(UUID id, TaskName name, Optional description, Optional deadline) { + requireAllNonNull(id, name, description, deadline); + this.id = id; + this.name = name; + this.description = description; + this.deadline = deadline; + this.isDone = false; + } + + /** + * Constructs a {@code Task}. + * + * @param id A unique identifier for the task. + * @param name A valid task name. + * @param description A valid and optional description. + * @param deadline A valid and optional deadline. + * @param isDone A boolean value indicating whether the task is done. + */ + public Task(UUID id, TaskName name, Optional description, + Optional deadline, boolean isDone) { + requireAllNonNull(id, name, description, deadline); + this.id = id; + this.name = name; + this.description = description; + this.deadline = deadline; + this.isDone = isDone; + } + + public UUID getId() { + return id; + } + + public TaskName getName() { + return name; + } + + public Optional getDescription() { + return description; + } + + public Optional getDeadline() { + return deadline; + } + + public boolean isDone() { + return isDone; + } + + @Override + public int compareTo(Task other) { + // Compare by name lexicographically if both tasks do not have deadlines + if (deadline.isEmpty() && other.deadline.isEmpty()) { + return name.compareTo(other.name); + } + + if (deadline.isEmpty()) { + return 1; + } + if (other.deadline.isEmpty()) { + return -1; + } + + // Compare by deadline if both tasks have deadlines + return deadline.get().compareTo(other.deadline.get()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other instanceof Task) { + Task otherTask = (Task) other; + return name.equals(otherTask.name) + && description.equals(otherTask.description) + && deadline.equals(otherTask.deadline); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(id, name, description, isDone); + } + + /** + * Format state as text for viewing. + */ + @Override + public String toString() { + return '[' + name.toString() + ']'; + } +} diff --git a/src/main/java/swift/model/task/TaskName.java b/src/main/java/swift/model/task/TaskName.java new file mode 100644 index 00000000000..60292fedbe1 --- /dev/null +++ b/src/main/java/swift/model/task/TaskName.java @@ -0,0 +1,64 @@ +package swift.model.task; + +import static java.util.Objects.requireNonNull; +import static swift.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class TaskName implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Names should only contain alphanumeric characters, commas, hyphen, apostrophes and spaces, " + + "and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "^[\\p{Alnum}][\\p{Alnum}\\s\\-\\.\\']*$"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public TaskName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + fullName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public int compareTo(TaskName other) { + return fullName.compareTo(other.fullName); + } + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskName // instanceof handles nulls + && fullName.equals(((TaskName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/swift/model/task/TaskNameContainsKeywordsPredicate.java b/src/main/java/swift/model/task/TaskNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..baf62e562af --- /dev/null +++ b/src/main/java/swift/model/task/TaskNameContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package swift.model.task; + +import java.util.List; +import java.util.function.Predicate; + +import swift.commons.util.StringUtil; + +/** + * Tests that a {@code Task}'s {@code Name} matches any of the keywords given. + */ +public class TaskNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TaskNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TaskNameContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/swift/model/task/UniqueTaskList.java b/src/main/java/swift/model/task/UniqueTaskList.java new file mode 100644 index 00000000000..f153d61b7ce --- /dev/null +++ b/src/main/java/swift/model/task/UniqueTaskList.java @@ -0,0 +1,153 @@ +package swift.model.task; + +import static java.util.Objects.requireNonNull; +import static swift.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import swift.model.task.exceptions.DuplicateTaskException; +import swift.model.task.exceptions.TaskNotFoundException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not + * allow nulls. + * A task is considered unique by comparing using {@code Task#equals(Task)}. + * As such, adding and updating of + * tasks uses Task#equals(Task) for equality so as to ensure that the person + * being added or updated is + * unique in terms of name and owner in the UniqueTaskList. + *

+ * Supports a minimal set of list operations. + * + * @see Task#isSameTask(Task) + */ +public class UniqueTaskList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent task as the given argument. + * + * @param toCheck The task to check for. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a task to the list. + * The task must not already exist in the list. + * + * @param toAdd The task to add. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The task name and owner of {@code editedTask} must not be the same as another + * existing task in the list. + * + * @param target The task to be replaced. + * @param editedTask The task to replace with. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.equals(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } + + internalList.set(index, editedTask); + } + + /** + * Removes the equivalent task from the list. + * The task must exist in the list. + * + * @param toRemove Task to be removed. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + * + * @param tasks The list of tasks to replace the current list with. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateTaskException(); + } + + internalList.setAll(tasks); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + * + * @param tasks A list of tasks. + */ + private boolean tasksAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).equals(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/swift/model/task/exceptions/DuplicateTaskException.java b/src/main/java/swift/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..f20550a1368 --- /dev/null +++ b/src/main/java/swift/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,12 @@ +package swift.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Tasks (Taskss are + * considered duplicates if they have the same + * name and owner). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/swift/model/task/exceptions/TaskNotFoundException.java b/src/main/java/swift/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..29ca2391101 --- /dev/null +++ b/src/main/java/swift/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,7 @@ +package swift.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException { +} diff --git a/src/main/java/swift/model/util/SampleDataUtil.java b/src/main/java/swift/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..afa1c98ba4b --- /dev/null +++ b/src/main/java/swift/model/util/SampleDataUtil.java @@ -0,0 +1,111 @@ +package swift.model.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import swift.model.AddressBook; +import swift.model.ReadOnlyAddressBook; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Address; +import swift.model.person.Email; +import swift.model.person.Person; +import swift.model.person.PersonName; +import swift.model.person.Phone; +import swift.model.tag.Tag; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.Task; +import swift.model.task.TaskName; + +/** + * Contains utility methods for populating {@code AddressBook} with sample data. + */ +public class SampleDataUtil { + public static ArrayList getSamplePersons() { + return new ArrayList<>(Arrays.asList( + new Person(UUID.fromString("6517916e-80c0-40e1-ac13-7cb870f57d80"), + new PersonName("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), + getTagSet("clients")), + new Person(UUID.fromString("049fb6e6-7e43-4075-a1e3-faad028faa0f"), + new PersonName("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + getTagSet("developers")), + new Person(UUID.fromString("5f3f93b9-d839-4d5c-b197-9f3e53ebbb71"), + new PersonName("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + getTagSet("developers")), + new Person(UUID.fromString("f2d431ed-1793-4761-9121-3652441e0ea2"), + new PersonName("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + getTagSet("clients")), + new Person(UUID.fromString("98245b81-7d46-4834-b557-ed2f720110e8"), + new PersonName("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), + getTagSet("clients")), + new Person(UUID.fromString("1c6513a5-f530-4995-88ba-c9a7eb77d81c"), + new PersonName("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), + getTagSet("designers")) + )); + } + + public static ArrayList getSampleTasks() { + return new ArrayList<>(Arrays.asList( + new Task(UUID.fromString("08a458c9-4f0e-4819-a716-1876ff57356f"), + new TaskName("Discuss user requirements"), + Optional.of(new Description("Draw diagrams")), + Optional.of(new Deadline("12-12-2022 1400")), + false), + new Task(UUID.fromString("9f1d7d61-e75b-41f2-b66b-1ce95ec251e3"), + new TaskName("Update developer guide"), + Optional.of(new Description("Include UML diagram")), + Optional.empty(), + true), + new Task(UUID.fromString("990b1561-ea5f-498d-96e6-eab5381b609e"), + new TaskName("Review PR"), + Optional.empty(), + Optional.of(new Deadline("14-12-2022 1800")), + false), + new Task(UUID.fromString("f2b134b4-d505-463c-bc10-0e72c4566002"), + new TaskName("Brainstorm user stories"), + Optional.of(new Description("Meeting link: https://www.meeting.com")), + Optional.of(new Deadline("14-12-2022 1800")), + true) + )); + } + + public static ArrayList getSamplePersonTaskBridge() { + return new ArrayList<>(Arrays.asList( + new PersonTaskBridge(UUID.fromString("6517916e-80c0-40e1-ac13-7cb870f57d80"), + UUID.fromString("08a458c9-4f0e-4819-a716-1876ff57356f")), + new PersonTaskBridge(UUID.fromString("049fb6e6-7e43-4075-a1e3-faad028faa0f"), + UUID.fromString("9f1d7d61-e75b-41f2-b66b-1ce95ec251e3")), + new PersonTaskBridge(UUID.fromString("049fb6e6-7e43-4075-a1e3-faad028faa0f"), + UUID.fromString("990b1561-ea5f-498d-96e6-eab5381b609e")), + new PersonTaskBridge(UUID.fromString("f2d431ed-1793-4761-9121-3652441e0ea2"), + UUID.fromString("f2b134b4-d505-463c-bc10-0e72c4566002")) + )); + } + + public static ReadOnlyAddressBook getSampleAddressBook() { + AddressBook sampleAb = new AddressBook(); + sampleAb.setPersons(getSamplePersons()); + sampleAb.setTasks(getSampleTasks()); + sampleAb.setBridges(getSamplePersonTaskBridge()); + return sampleAb; + } + + /** + * Returns a tag set containing the list of strings given. + */ + public static Set getTagSet(String... strings) { + return Arrays.stream(strings) + .map(Tag::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/swift/storage/AddressBookStorage.java similarity index 85% rename from src/main/java/seedu/address/storage/AddressBookStorage.java rename to src/main/java/swift/storage/AddressBookStorage.java index 4599182b3f9..e7b9bd7e3b8 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/swift/storage/AddressBookStorage.java @@ -1,14 +1,14 @@ -package seedu.address.storage; +package swift.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import swift.commons.exceptions.DataConversionException; +import swift.model.ReadOnlyAddressBook; /** - * Represents a storage for {@link seedu.address.model.AddressBook}. + * Represents a storage for {@link swift.model.AddressBook}. */ public interface AddressBookStorage { diff --git a/src/main/java/swift/storage/JsonAdaptedBridge.java b/src/main/java/swift/storage/JsonAdaptedBridge.java new file mode 100644 index 00000000000..0b703b27d48 --- /dev/null +++ b/src/main/java/swift/storage/JsonAdaptedBridge.java @@ -0,0 +1,70 @@ +package swift.storage; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import swift.commons.exceptions.IllegalValueException; +import swift.model.bridge.PersonTaskBridge; + +/** + * Jackson-friendly version of {@link PersonTaskBridge}. + */ +class JsonAdaptedBridge { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Bridge's %s field is missing!"; + + private final String personId; + private final String taskId; + + /** + * Constructs a {@code JsonAdaptedBridge} with the given person details. + */ + @JsonCreator + public JsonAdaptedBridge(@JsonProperty("personId") String personId, @JsonProperty("taskId") String taskId) { + this.personId = personId; + this.taskId = taskId; + } + + /** + * Converts a given {@code PersonTaskBridge} into this class for Jackson use. + */ + public JsonAdaptedBridge(PersonTaskBridge source) { + personId = source.getPersonId().toString(); + taskId = source.getTaskId().toString(); + } + + /** + * Converts this Jackson-friendly adapted person-task bridge object into the + * model's + * {@code PersonTaskBridge} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person-task bridge. + */ + public PersonTaskBridge toModelType() throws IllegalValueException { + if (personId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, UUID.class.getSimpleName())); + } + try { + UUID.fromString(personId); + } catch (IllegalArgumentException e) { + throw new IllegalValueException(e.getMessage()); + } + final UUID modelPersonId = UUID.fromString(personId); + + if (taskId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, UUID.class.getSimpleName())); + } + try { + UUID.fromString(taskId); + } catch (IllegalArgumentException e) { + throw new IllegalValueException(e.getMessage()); + } + final UUID modelTaskId = UUID.fromString(taskId); + + return new PersonTaskBridge(modelPersonId, modelTaskId); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/swift/storage/JsonAdaptedPerson.java similarity index 69% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/swift/storage/JsonAdaptedPerson.java index a6321cec2ea..ba9407c0031 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/swift/storage/JsonAdaptedPerson.java @@ -1,21 +1,22 @@ -package seedu.address.storage; +package swift.storage; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; 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; +import swift.commons.exceptions.IllegalValueException; +import swift.model.person.Address; +import swift.model.person.Email; +import swift.model.person.Person; +import swift.model.person.PersonName; +import swift.model.person.Phone; +import swift.model.tag.Tag; /** * Jackson-friendly version of {@link Person}. @@ -24,6 +25,7 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + private final String id; private final String name; private final String phone; private final String email; @@ -34,9 +36,11 @@ class JsonAdaptedPerson { * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + public JsonAdaptedPerson(@JsonProperty("id") String id, + @JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, @JsonProperty("tagged") List tagged) { + this.id = id; this.name = name; this.phone = phone; this.email = email; @@ -50,6 +54,7 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { + id = source.getId().toString(); name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; @@ -60,23 +65,36 @@ public JsonAdaptedPerson(Person source) { } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * 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. + * @throws IllegalValueException if there were any data constraints violated in + * the adapted person. */ public Person toModelType() throws IllegalValueException { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, UUID.class.getSimpleName())); + } + try { + UUID.fromString(id); + } catch (IllegalArgumentException e) { + throw new IllegalValueException(e.getMessage()); + } + final UUID modelId = UUID.fromString(id); + final List personTags = new ArrayList<>(); for (JsonAdaptedTag tag : tagged) { personTags.add(tag.toModelType()); } if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PersonName.class.getSimpleName())); } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + if (!PersonName.isValidName(name)) { + throw new IllegalValueException(PersonName.MESSAGE_CONSTRAINTS); } - final Name modelName = new Name(name); + final PersonName modelName = new PersonName(name); if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); @@ -103,7 +121,7 @@ public Person toModelType() throws IllegalValueException { final Address modelAddress = new Address(address); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelId, modelName, modelPhone, modelEmail, modelAddress, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/swift/storage/JsonAdaptedTag.java similarity index 89% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/swift/storage/JsonAdaptedTag.java index 0df22bdb754..123ace66949 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/swift/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package swift.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 swift.commons.exceptions.IllegalValueException; +import swift.model.tag.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/swift/storage/JsonAdaptedTask.java b/src/main/java/swift/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..eeb477a96e3 --- /dev/null +++ b/src/main/java/swift/storage/JsonAdaptedTask.java @@ -0,0 +1,100 @@ +package swift.storage; + +import java.util.Optional; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import swift.commons.exceptions.IllegalValueException; +import swift.logic.parser.ParserUtil; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.Task; +import swift.model.task.TaskName; + +/** + * Jackson-friendly version of {@link Task}. + */ +class JsonAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + private final String id; + private final String taskName; + private final String description; + private final String deadline; + + private final boolean isDone; + + /** + * Constructs a {@code JsonAdaptedTask} with the given person details. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("id") String id, @JsonProperty("taskName") String taskName, + @JsonProperty("description") String description, @JsonProperty("deadline") String deadline, + @JsonProperty("isDone") boolean isDone) { + this.id = id; + this.taskName = taskName; + this.description = description; + this.deadline = deadline; + this.isDone = isDone; + } + + /** + * Converts a given {@code Task} into this class for Jackson use. + */ + public JsonAdaptedTask(Task source) { + id = source.getId().toString(); + taskName = source.getName().fullName; + description = source.getDescription().map(Description::toString).orElse(null); + deadline = source.getDeadline().map(Deadline::toString).orElse(null); + isDone = source.isDone(); + } + + /** + * Converts this Jackson-friendly adapted task object into the model's + * {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in + * the adapted task. + */ + public Task toModelType() throws IllegalValueException { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, UUID.class.getSimpleName())); + } + try { + UUID.fromString(id); + } catch (IllegalArgumentException e) { + throw new IllegalValueException(e.getMessage()); + } + final UUID modelId = UUID.fromString(id); + + if (taskName == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, TaskName.class.getSimpleName())); + } + if (!TaskName.isValidName(taskName)) { + throw new IllegalValueException(TaskName.MESSAGE_CONSTRAINTS); + } + final TaskName modelName = new TaskName(taskName); + + if (description != null && !Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + Optional modelDescription = Optional.empty(); + if (description != null) { + modelDescription = Optional.of(ParserUtil.parseDescription(description)); + } + + if (deadline != null && !Deadline.isValidDeadline(deadline)) { + throw new IllegalValueException(Deadline.MESSAGE_CONSTRAINTS); + } + Optional modelDeadline = Optional.empty(); + if (deadline != null) { + modelDeadline = Optional.of(ParserUtil.parseDeadline(deadline)); + } + + return new Task(modelId, modelName, modelDescription, modelDeadline, isDone); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/swift/storage/JsonAddressBookStorage.java similarity index 86% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/swift/storage/JsonAddressBookStorage.java index dfab9daaa0d..7a91f41ac13 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/swift/storage/JsonAddressBookStorage.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package swift.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.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import swift.commons.core.LogsCenter; +import swift.commons.exceptions.DataConversionException; +import swift.commons.exceptions.IllegalValueException; +import swift.commons.util.FileUtil; +import swift.commons.util.JsonUtil; +import swift.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/swift/storage/JsonSerializableAddressBook.java similarity index 50% rename from src/main/java/seedu/address/storage/JsonSerializableAddressBook.java rename to src/main/java/swift/storage/JsonSerializableAddressBook.java index 5efd834091d..5446da165b2 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/swift/storage/JsonSerializableAddressBook.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package swift.storage; import java.util.ArrayList; import java.util.List; @@ -8,10 +8,12 @@ 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 swift.commons.exceptions.IllegalValueException; +import swift.model.AddressBook; +import swift.model.ReadOnlyAddressBook; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; /** * An Immutable AddressBook that is serializable to JSON format. @@ -20,24 +22,36 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_TASK = "Tasks list contains duplicate task(s)."; + public static final String MESSAGE_DUPLICATE_BRIDGE = "Bridges list contains duplicate bridge(s)."; private final List persons = new ArrayList<>(); + private final List tasks = new ArrayList<>(); + private final List bridges = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons, + * tasks and bridges. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("tasks") List tasks, + @JsonProperty("bridges") List bridges) { this.persons.addAll(persons); + this.tasks.addAll(tasks); + this.bridges.addAll(bridges); } /** * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. + * @param source future changes to this will not affect the created + * {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + tasks.addAll(source.getTaskList().stream().map(JsonAdaptedTask::new).collect(Collectors.toList())); + bridges.addAll(source.getBridgeList().stream().map(JsonAdaptedBridge::new).collect(Collectors.toList())); } /** @@ -54,6 +68,20 @@ public AddressBook toModelType() throws IllegalValueException { } addressBook.addPerson(person); } + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + Task task = jsonAdaptedTask.toModelType(); + if (addressBook.hasTask(task)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TASK); + } + addressBook.addTask(task); + } + for (JsonAdaptedBridge jsonAdaptedBridge : bridges) { + PersonTaskBridge bridge = jsonAdaptedBridge.toModelType(); + if (addressBook.hasBridge(bridge)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_BRIDGE); + } + addressBook.addBridge(bridge); + } return addressBook; } diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/swift/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/swift/storage/JsonUserPrefsStorage.java index bc2bbad84aa..5b74e25b6e7 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/swift/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package swift.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import swift.commons.exceptions.DataConversionException; +import swift.commons.util.JsonUtil; +import swift.model.ReadOnlyUserPrefs; +import swift.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/swift/storage/Storage.java similarity index 73% rename from src/main/java/seedu/address/storage/Storage.java rename to src/main/java/swift/storage/Storage.java index beda8bd9f11..a57d8810a66 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/swift/storage/Storage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package swift.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import swift.commons.exceptions.DataConversionException; +import swift.model.ReadOnlyAddressBook; +import swift.model.ReadOnlyUserPrefs; +import swift.model.UserPrefs; /** * API of the Storage component diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/swift/storage/StorageManager.java similarity index 89% rename from src/main/java/seedu/address/storage/StorageManager.java rename to src/main/java/swift/storage/StorageManager.java index 6cfa0162164..af7905f22d6 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/swift/storage/StorageManager.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package swift.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.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import swift.commons.core.LogsCenter; +import swift.commons.exceptions.DataConversionException; +import swift.model.ReadOnlyAddressBook; +import swift.model.ReadOnlyUserPrefs; +import swift.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/swift/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/swift/storage/UserPrefsStorage.java index 29eef178dbc..b793abdfe02 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/swift/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package swift.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import swift.commons.exceptions.DataConversionException; +import swift.model.ReadOnlyUserPrefs; +import swift.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link swift.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link swift.model.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/swift/ui/CommandBox.java b/src/main/java/swift/ui/CommandBox.java new file mode 100644 index 00000000000..5b45801f82b --- /dev/null +++ b/src/main/java/swift/ui/CommandBox.java @@ -0,0 +1,159 @@ +package swift.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; +import javafx.scene.text.Text; +import swift.commons.core.LogsCenter; +import swift.logic.commands.CommandResult; +import swift.logic.commands.CommandSuggestor; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.exceptions.ParseException; + +/** + * The UI component that is responsible for receiving user command inputs. + */ +public class CommandBox extends UiPart { + + public static final String ERROR_STYLE_CLASS = "error"; + private static final String FXML = "CommandBox.fxml"; + private static final Logger logger = LogsCenter.getLogger(CommandBox.class); + + private final CommandExecutor commandExecutor; + + @FXML + private TextField commandTextField; + + @FXML + private TextField commandSuggestionTextField; + + private CommandSuggestor commandSuggestor; + + /** + * Creates a {@code CommandBox} with the given {@code CommandExecutor}. + */ + public CommandBox(CommandExecutor commandExecutor) { + super(FXML); + this.commandExecutor = commandExecutor; + // calls #setStyleToDefault() whenever there is a change to the text of the command box. + commandTextField.textProperty() + .addListener((unused1, unused2, unused3) -> setStyleToDefault()); + commandTextField.textProperty() + .addListener((observable, oldValue, newValue) -> updateCommandSuggestion(newValue)); + commandTextField.addEventFilter(KeyEvent.KEY_PRESSED, event -> handleKeyPressed(event)); + + commandSuggestionTextField.setEditable(false); + commandSuggestionTextField.setFocusTraversable(false); + commandSuggestionTextField.setMouseTransparent(true); + commandSuggestor = new CommandSuggestor(); + } + + /** + * Handles the Enter button pressed event. + */ + @FXML + private void handleCommandEntered() { + String commandText = commandTextField.getText(); + if (commandText.equals("")) { + return; + } + + try { + commandExecutor.execute(commandText); + commandTextField.setText(""); + } catch (CommandException | ParseException e) { + setStyleToIndicateCommandFailure(); + } + } + + /** + * Sets the command box style to use the default style. + */ + private void setStyleToDefault() { + commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); + } + + /** + * Sets the command box style to indicate a failed command. + */ + private void setStyleToIndicateCommandFailure() { + ObservableList styleClass = commandTextField.getStyleClass(); + + if (styleClass.contains(ERROR_STYLE_CLASS)) { + return; + } + + styleClass.add(ERROR_STYLE_CLASS); + } + + /** + * Represents a function that can execute commands. + */ + @FunctionalInterface + public interface CommandExecutor { + /** + * Executes the command and returns the result. + * + * @see swift.logic.Logic#execute(String) + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + } + + /** + * Auto-completes user input when the user presses the Tab key. + */ + public void handleKeyPressed(KeyEvent e) { + try { + String userInput = commandTextField.getText(); + if (e.getCode() == KeyCode.TAB) { + String commandSuggestion = commandSuggestor.suggestCommand(userInput); + String autocompletedCommand = commandSuggestor.autocompleteCommand(userInput, commandSuggestion); + if (!autocompletedCommand.equals("")) { + commandTextField.setText(autocompletedCommand); + commandTextField.end(); + } + updateCommandSuggestion(commandTextField.getText()); + e.consume(); + } + } catch (CommandException ce) { + logger.info("Invalid Command Entered"); + setStyleToIndicateCommandFailure(); + e.consume(); + } + } + + /** + * Updates the command suggestion text field. + */ + private void updateCommandSuggestion(String commandText) { + if (commandText.equals("") || isOverflow()) { + commandSuggestionTextField.setText(""); + return; + } + try { + commandSuggestionTextField.setText(commandSuggestor.suggestCommand(commandText)); + commandSuggestionTextField.positionCaret(commandTextField.getText().length()); + } catch (CommandException e) { + logger.info("Invalid Command Entered"); + commandSuggestionTextField.setText(commandText); + setStyleToIndicateCommandFailure(); + } + } + + /** + * Checks if the command text field is overflowing. + * @return true if the command text field is overflowing. + */ + public boolean isOverflow() { + Text t = new Text(commandTextField.getText() + "12345"); // `12345` is used to pad the text + t.setFont(commandTextField.getFont()); + double width = t.getLayoutBounds().getWidth(); + return width > commandTextField.getWidth(); + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/swift/ui/HelpWindow.java similarity index 90% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/swift/ui/HelpWindow.java index 3f16b2fcf26..6494b0aead2 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/swift/ui/HelpWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import java.util.logging.Logger; @@ -8,15 +8,15 @@ import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import swift.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 USER_GUIDE_URL = "https://ay2223s1-cs2103t-t12-2.github.io/tp/UserGuide.html"; + public static final String HELP_MESSAGE = "Refer to the user guide: " + USER_GUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @@ -96,7 +96,7 @@ public void focus() { private void copyUrl() { final Clipboard clipboard = Clipboard.getSystemClipboard(); final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); + url.putString(USER_GUIDE_URL); clipboard.setContent(url); } } diff --git a/src/main/java/swift/ui/MainWindow.java b/src/main/java/swift/ui/MainWindow.java new file mode 100644 index 00000000000..d0c5d94b3cd --- /dev/null +++ b/src/main/java/swift/ui/MainWindow.java @@ -0,0 +1,351 @@ +package swift.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import swift.commons.core.GuiSettings; +import swift.commons.core.LogsCenter; +import swift.logic.Logic; +import swift.logic.commands.CommandResult; +import swift.logic.commands.CommandType; +import swift.logic.commands.exceptions.CommandException; +import swift.logic.parser.exceptions.ParseException; + +/** + * The Main Window. Provides the basic application layout containing + * a menu bar and space where other JavaFX elements can be placed. + */ +public class MainWindow extends UiPart { + + private static final String FXML = "MainWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private Logic logic; + + // Independent Ui parts residing in this Ui container + private PersonListPanel personListPanel; + private TaskListPanel taskListPanel; + private ResultDisplay resultDisplay; + private PersonTaskListPanel personTaskListPanel; + private TaskPersonListPanel taskPersonListPanel; + private HelpWindow helpWindow; + + private boolean isContactTabShown; + + @FXML + private StackPane listPanelPlaceholder; + + @FXML + private StackPane resultDisplayPlaceholder; + + @FXML + private StackPane personTaskListPanelPlaceholder; + + @FXML + private StackPane statusbarPlaceholder; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private Button tasksButton; + + @FXML + private Button contactsButton; + + /** + * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. + */ + public MainWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + + // Configure the UI + setWindowDefaultSize(logic.getGuiSettings()); + + setAccelerator(); + + helpWindow = new HelpWindow(); + + isContactTabShown = true; + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + + /** + * Sets the accelerator and toggles the tab + * when the user presses the shortcut keys. + */ + private void setAccelerator() { + /* + * TODO: the code below can be removed once the bug reported here + * https://bugs.openjdk.java.net/browse/JDK-8131666 + * is fixed in later version of SDK. + * + * According to the bug report, TextInputControl (TextField, TextArea) will + * consume function-key events. Because CommandBox contains a TextField, and + * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will + * not work when the focus is in them because the key event is consumed by + * the TextInputControl(s). + * + * For now, we add following event filter to capture such key events and open + * help window purposely so to support accelerators even when focus is + * in CommandBox or ResultDisplay. + */ + KeyCodeCombination keyCodeCombination = new KeyCodeCombination(KeyCode.TAB, KeyCombination.CONTROL_DOWN); + + getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if (keyCodeCombination.match(event)) { + toggleTab(); + event.consume(); + } + }); + } + + private void toggleTab() { + if (isContactTabShown) { + showTaskTab(); + } else { + showContactTab(); + } + isContactTabShown = !isContactTabShown; + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + personTaskListPanel = new PersonTaskListPanel(logic.getFilteredTaskList(), + logic.getUnfilteredBridgeList(), logic.getFilteredPersonList()); + personTaskListPanelPlaceholder.getChildren().add(personTaskListPanel.getRoot()); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + CommandBox commandBox = new CommandBox(this::executeCommand); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + setMenuButtonStyle(contactsButton); + } + + /** + * Sets the default size based on {@code guiSettings}. + */ + private void setWindowDefaultSize(GuiSettings guiSettings) { + primaryStage.setHeight(guiSettings.getWindowHeight()); + primaryStage.setWidth(guiSettings.getWindowWidth()); + if (guiSettings.getWindowCoordinates() != null) { + primaryStage.setX(guiSettings.getWindowCoordinates().getX()); + primaryStage.setY(guiSettings.getWindowCoordinates().getY()); + } + } + + /** + * Opens the help window or focuses on it if it's already opened. + */ + @FXML + public void handleHelp() { + if (!helpWindow.isShowing()) { + helpWindow.show(); + } else { + helpWindow.focus(); + } + } + + void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + logic.setGuiSettings(guiSettings); + helpWindow.hide(); + primaryStage.hide(); + } + + /** + * Switch to tasks tab. + */ + @FXML + private void showTaskTab() { + setMenuButtonStyle(tasksButton); + resetMenuButtonStyle(contactsButton); + + taskListPanel = new TaskListPanel(logic.getFilteredTaskList(), logic.getUnfilteredBridgeList(), + logic.getUnfilteredPersonList()); + + listPanelPlaceholder.getChildren().clear(); + listPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + + showContactsInTaskTab(); + } + + /** + * Switch to contacts tab. + */ + @FXML + private void showContactTab() { + setMenuButtonStyle(contactsButton); + resetMenuButtonStyle(tasksButton); + + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + listPanelPlaceholder.getChildren().clear(); + listPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + showTasksInContactTab(); + } + + /** + * Show the associated tasks list panel only if there is one contact selected in contact list. + */ + @FXML + private void showTasksInContactTab() { + removeSidePanel(); + personTaskListPanel = new PersonTaskListPanel(logic.getFilteredTaskList(), + logic.getUnfilteredBridgeList(), logic.getUnfilteredPersonList()); + personTaskListPanel.switchToAllTasks(); + personTaskListPanelPlaceholder.getChildren().add(personTaskListPanel.getRoot()); + } + + /** + * Show the associated persons list panel only if there is one task selected in task list. + */ + @FXML + private void showContactsInTaskTab() { + removeSidePanel(); + taskPersonListPanel = new TaskPersonListPanel(logic.getFilteredPersonList()); + taskPersonListPanel.switchToAllContacts(); + personTaskListPanelPlaceholder.getChildren().add(taskPersonListPanel.getRoot()); + } + + /** + * Show the assigned contacts in task tab side panel. + */ + @FXML + private void showAssignedContacts() { + taskPersonListPanel.switchToAssignedContacts(); + } + + /** + * Show the assigned tasks in the contact tab side panel. + */ + @FXML + private void showAssignedTasks() { + personTaskListPanel.switchToAssignedTasks(); + } + + /** + * Remove list in side panel + * @return + */ + @FXML + private void removeSidePanel() { + personTaskListPanelPlaceholder.getChildren().clear(); + } + + /** + * Underline the menu button that represents the tab the application is on. + * + * @param button the menu button to be styled. + */ + private void setMenuButtonStyle(Button button) { + button.setStyle("-fx-border-color: transparent transparent #6D28D9 transparent"); + } + + private void resetMenuButtonStyle(Button button) { + button.setStyle("-fx-border-color: transparent transparent transparent transparent"); + } + + public PersonListPanel getPersonListPanel() { + return personListPanel; + } + + /** + * Refreshes the current view. + */ + public void refreshTab() { + if (isContactTabShown) { + showContactTab(); + } else { + showTaskTab(); + } + } + + /** + * Executes the command and returns the result. + * + * @see swift.logic.Logic#execute(String) + */ + private CommandResult executeCommand(String commandText) throws CommandException, ParseException { + try { + CommandResult commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + CommandType commandType = commandResult.getCommandType(); + switch (commandType) { + case HELP: + handleHelp(); + break; + case EXIT: + handleExit(); + break; + case CONTACTS: + showContactTab(); + isContactTabShown = true; + break; + case TASKS: + showTaskTab(); + isContactTabShown = false; + break; + case ASSIGN: + case UNASSIGN: + // Fallthrough + refreshTab(); + break; + case SELECT_CONTACT: + showContactTab(); + showAssignedTasks(); + break; + case SELECT_TASK: + showTaskTab(); + showAssignedContacts(); + break; + default: + break; + } + + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/swift/ui/PersonCard.java similarity index 77% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/swift/ui/PersonCard.java index 7fc927bc5d9..60e4933f084 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/swift/ui/PersonCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import java.util.Comparator; @@ -7,7 +7,7 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import swift.model.person.Person; /** * An UI component that displays information of a {@code Person}. @@ -54,7 +54,19 @@ public PersonCard(Person person, int displayedIndex) { email.setText(person.getEmail().value); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + .forEach(tag -> { + Label label = new Label(tag.tagName.toUpperCase()); + setStyle(label); + tags.getChildren().add(label); + }); + } + + private void setStyle(Label... labels) { + for (Label label: labels) { + label.setStyle("-fx-background-color:transparent; -fx-text-fill: #AFB4FF;" + + "-fx-font-family: Arial; -fx-font-size: 12; -fx-font-weight: bold;" + + "-fx-background-insets: 1;"); + } } @Override diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/swift/ui/PersonListPanel.java similarity index 81% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/swift/ui/PersonListPanel.java index f4c501a897b..1f67e00f7e1 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/swift/ui/PersonListPanel.java @@ -1,14 +1,15 @@ -package seedu.address.ui; +package swift.ui; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.fxml.FXML; +import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import swift.commons.core.LogsCenter; +import swift.model.person.Person; /** * Panel containing the list of persons. @@ -17,6 +18,8 @@ public class PersonListPanel extends UiPart { private static final String FXML = "PersonListPanel.fxml"; private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + @FXML + private Label listPanelHeading; @FXML private ListView personListView; @@ -29,6 +32,13 @@ public PersonListPanel(ObservableList personList) { personListView.setCellFactory(listView -> new PersonListViewCell()); } + /** + * Remove heading from the panel. + */ + protected void removeHeading() { + listPanelHeading.setText(""); + } + /** * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. */ diff --git a/src/main/java/swift/ui/PersonTaskCard.java b/src/main/java/swift/ui/PersonTaskCard.java new file mode 100644 index 00000000000..6340eb74252 --- /dev/null +++ b/src/main/java/swift/ui/PersonTaskCard.java @@ -0,0 +1,105 @@ +package swift.ui; + +import java.util.UUID; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Deadline; +import swift.model.task.Task; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class PersonTaskCard extends UiPart { + + private static final String FXML = "PersonTaskCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private FlowPane contacts; + @FXML + private Label deadline; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public PersonTaskCard(Task task, int displayedIndex, ObservableList personTaskBridgeList, + ObservableList personList) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + name.setText(task.getName().fullName); + deadline.setText(task.getDeadline().map(Deadline::toString).orElse("NO DEADLINE")); + setAssociatedContacts(personTaskBridgeList, personList); + } + + private void setAssociatedContacts(ObservableList personTaskBridgeList, + ObservableList personList) { + personTaskBridgeList.stream() + .filter(bridge -> bridge.getTaskId().equals(task.getId())) + .collect(Collectors.toList()) + .forEach(taskBridge -> { + UUID personId = taskBridge.getPersonId(); + Person associatedPerson; + + for (Person person : personList) { + associatedPerson = person; + if (associatedPerson.getId().equals(personId)) { + Label label = new Label(associatedPerson.getName().toString()); + setStyle(label); + contacts.getChildren().add(label); + return; + } + } + }); + } + + private void setStyle(Label... labels) { + for (Label label: labels) { + label.setStyle("-fx-background-color:derive(#6D28D9, 50%); -fx-text-fill: #FFFFFF;" + + "-fx-font-family: 'Space Grotesk Regular'; -fx-font-size: 10; -fx-font-weight: bold;" + + "-fx-border-insets: 3; -fx-border-radius: 4px; -fx-border-width: 2px;" + + "-fx-border-color: derive(#6D28D9, 50%); -fx-background-insets: 4; -fx-label-padding: 2;"); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PersonTaskCard)) { + return false; + } + + // state check + PersonTaskCard card = (PersonTaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/swift/ui/PersonTaskListPanel.java b/src/main/java/swift/ui/PersonTaskListPanel.java new file mode 100644 index 00000000000..abbdf9d178a --- /dev/null +++ b/src/main/java/swift/ui/PersonTaskListPanel.java @@ -0,0 +1,77 @@ +package swift.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import swift.commons.core.LogsCenter; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Panel containing the list of persons. + */ +public class PersonTaskListPanel extends UiPart { + private static final String FXML = "PersonTaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonTaskListPanel.class); + + @FXML + private ListView personTaskListView; + @FXML + private Label heading; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public PersonTaskListPanel(ObservableList taskList, ObservableList bridgeList, + ObservableList personList) { + super(FXML); + personTaskListView.setItems(taskList); + personTaskListView.setCellFactory(listView -> new PersonTaskListViewCell(bridgeList, personList)); + } + + /** + * Change panel to show contacts assigned to task. + */ + protected void switchToAssignedTasks() { + heading.setText("ASSIGNED TASKS"); + } + + /** + * Change panel to show all contacts. + */ + protected void switchToAllTasks() { + heading.setText("TASKS"); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class PersonTaskListViewCell extends ListCell { + private ObservableList bridgeList; + private ObservableList personList; + + protected PersonTaskListViewCell(ObservableList bridgeList, + ObservableList personList) { + this.bridgeList = bridgeList; + this.personList = personList; + } + + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PersonTaskCard(task, getIndex() + 1, bridgeList, personList).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/swift/ui/ResultDisplay.java similarity index 91% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/swift/ui/ResultDisplay.java index 7d98e84eedf..c2a30187c3e 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/swift/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import static java.util.Objects.requireNonNull; @@ -23,6 +23,7 @@ public ResultDisplay() { public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); + resultDisplay.setWrapText(true); } } diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/swift/ui/StatusBarFooter.java similarity index 96% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/swift/ui/StatusBarFooter.java index b577f829423..aaa26cf58f5 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/swift/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/swift/ui/TaskCard.java b/src/main/java/swift/ui/TaskCard.java new file mode 100644 index 00000000000..032f4aa093e --- /dev/null +++ b/src/main/java/swift/ui/TaskCard.java @@ -0,0 +1,115 @@ +package swift.ui; + +import java.util.UUID; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Deadline; +import swift.model.task.Description; +import swift.model.task.Task; + +/** + * A UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label taskName; + @FXML + private Label id; + @FXML + private FlowPane contacts; + @FXML + private Label deadline; + @FXML + private Label description; + @FXML + private Label checkBox; + @FXML + private Label uncheckBox; + + /** + * Creates a {@code TaskCode} with the given {@code Task} and index to display. + */ + public TaskCard(Task task, int displayedIndex, ObservableList personTaskBridgeList, + ObservableList personList) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + taskName.setText(task.getName().fullName); + deadline.setText(task.getDeadline().map(Deadline::toString).orElse("NO DEADLINE")); + description.setText(task.getDescription().map(Description::toString).orElse("NO DESCRIPTION")); + setAssociatedContacts(personTaskBridgeList, personList); + checkBox.setVisible(task.isDone()); + uncheckBox.setVisible(!task.isDone()); + } + + private void setAssociatedContacts(ObservableList personTaskBridgeList, + ObservableList personList) { + personTaskBridgeList.stream() + .filter(bridge -> bridge.getTaskId().equals(task.getId())) + .collect(Collectors.toList()) + .forEach(taskBridge -> { + UUID personId = taskBridge.getPersonId(); + Person associatedPerson; + + for (Person person : personList) { + associatedPerson = person; + if (associatedPerson.getId().equals(personId)) { + Label label = new Label(associatedPerson.getName().toString()); + setStyle(label); + contacts.getChildren().add(label); + return; + } + } + }); + } + + private void setStyle(Label... labels) { + for (Label label: labels) { + label.setStyle("-fx-background-color:derive(#6D28D9, 50%); -fx-text-fill: #FFFFFF;" + + "-fx-font-family: Inter; -fx-font-size: 11; -fx-font-weight: bold;" + + "-fx-border-insets: 3; -fx-border-radius: 4px; -fx-border-width: 2px;" + + "-fx-border-color: derive(#6D28D9, 50%); -fx-background-insets: 4; -fx-label-padding: 1;"); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/swift/ui/TaskListPanel.java b/src/main/java/swift/ui/TaskListPanel.java new file mode 100644 index 00000000000..f5f76db31e2 --- /dev/null +++ b/src/main/java/swift/ui/TaskListPanel.java @@ -0,0 +1,63 @@ +package swift.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import swift.commons.core.LogsCenter; +import swift.model.bridge.PersonTaskBridge; +import swift.model.person.Person; +import swift.model.task.Task; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private Label listPanelHeading; + @FXML + private ListView taskListView; + + /** + * Creates a {@code TaskListPanel} with the given {@code ObservableList}. + */ + public TaskListPanel(ObservableList taskList, ObservableList personTaskBridgeList, + ObservableList personList) { + super(FXML); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell(personTaskBridgeList, personList)); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskCard}. + */ + class TaskListViewCell extends ListCell { + private ObservableList personTaskBridgeList; + private ObservableList personList; + + protected TaskListViewCell(ObservableList personTaskBridgeList, + ObservableList personList) { + this.personTaskBridgeList = personTaskBridgeList; + this.personList = personList; + } + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskCard(task, getIndex() + 1, personTaskBridgeList, personList).getRoot()); + } + } + } + +} diff --git a/src/main/java/swift/ui/TaskPersonCard.java b/src/main/java/swift/ui/TaskPersonCard.java new file mode 100644 index 00000000000..6b4134dc875 --- /dev/null +++ b/src/main/java/swift/ui/TaskPersonCard.java @@ -0,0 +1,84 @@ +package swift.ui; + +import java.util.Comparator; + +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 swift.model.person.Person; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class TaskPersonCard extends UiPart { + + private static final String FXML = "TaskPersonCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Person person; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label email; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public TaskPersonCard(Person person, int displayedIndex) { + super(FXML); + this.person = person; + id.setText(displayedIndex + ". "); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + email.setText(person.getEmail().value); + person.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> { + Label label = new Label(tag.tagName.toUpperCase()); + setStyle(label); + tags.getChildren().add(label); + }); + } + + private void setStyle(Label... labels) { + for (Label label: labels) { + label.setStyle("-fx-background-color:transparent; -fx-text-fill: #AFB4FF;" + + "-fx-font-family: Arial; -fx-font-size: 10; -fx-font-weight: bold;"); + } + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskPersonCard)) { + return false; + } + + // state check + TaskPersonCard card = (TaskPersonCard) other; + return id.getText().equals(card.id.getText()) + && person.equals(card.person); + } +} diff --git a/src/main/java/swift/ui/TaskPersonListPanel.java b/src/main/java/swift/ui/TaskPersonListPanel.java new file mode 100644 index 00000000000..b142b6f0f9d --- /dev/null +++ b/src/main/java/swift/ui/TaskPersonListPanel.java @@ -0,0 +1,65 @@ +package swift.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import swift.commons.core.LogsCenter; +import swift.model.person.Person; + +/** + * Panel containing the list of persons. + */ +public class TaskPersonListPanel extends UiPart { + private static final String FXML = "TaskPersonListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskPersonListPanel.class); + + @FXML + private ListView taskPersonListView; + @FXML + private Label heading; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public TaskPersonListPanel(ObservableList personList) { + super(FXML); + taskPersonListView.setItems(personList); + taskPersonListView.setCellFactory(listView -> new TaskPersonListViewCell()); + } + + /** + * Change panel to show contacts assigned to task. + */ + protected void switchToAssignedContacts() { + heading.setText("ASSIGNED CONTACTS"); + } + + /** + * Change panel to show all contacts. + */ + protected void switchToAllContacts() { + heading.setText("CONTACTS"); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class TaskPersonListViewCell 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 TaskPersonCard(person, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/swift/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/swift/ui/Ui.java index 17aa0b494fe..f2bdf0a9414 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/swift/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/swift/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/swift/ui/UiManager.java index fdf024138bc..004eb0f4eb1 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/swift/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ 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; +import swift.MainApp; +import swift.commons.core.LogsCenter; +import swift.commons.util.StringUtil; +import swift.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/swift.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/swift/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/swift/ui/UiPart.java index fc820e01a9c..f89252d327d 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/swift/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package swift.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import swift.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/resources/fonts/Inter-Bold.ttf b/src/main/resources/fonts/Inter-Bold.ttf new file mode 100644 index 00000000000..8e82c70d108 Binary files /dev/null and b/src/main/resources/fonts/Inter-Bold.ttf differ diff --git a/src/main/resources/fonts/Inter-Medium.ttf b/src/main/resources/fonts/Inter-Medium.ttf new file mode 100644 index 00000000000..b53fb1c4acb Binary files /dev/null and b/src/main/resources/fonts/Inter-Medium.ttf differ diff --git a/src/main/resources/fonts/Inter-Regular.ttf b/src/main/resources/fonts/Inter-Regular.ttf new file mode 100644 index 00000000000..8d4eebf2066 Binary files /dev/null and b/src/main/resources/fonts/Inter-Regular.ttf differ diff --git a/src/main/resources/fonts/Inter-SemiBold.ttf b/src/main/resources/fonts/Inter-SemiBold.ttf new file mode 100644 index 00000000000..c6aeeb16a6d Binary files /dev/null and b/src/main/resources/fonts/Inter-SemiBold.ttf differ diff --git a/src/main/resources/fonts/SpaceGrotesk-Regular.ttf b/src/main/resources/fonts/SpaceGrotesk-Regular.ttf new file mode 100644 index 00000000000..d0e5eff1ff4 Binary files /dev/null and b/src/main/resources/fonts/SpaceGrotesk-Regular.ttf differ diff --git a/src/main/resources/images/address_icon.png b/src/main/resources/images/address_icon.png new file mode 100644 index 00000000000..d223ca2d7f5 Binary files /dev/null and b/src/main/resources/images/address_icon.png differ diff --git a/src/main/resources/images/address_icon_2.png b/src/main/resources/images/address_icon_2.png new file mode 100644 index 00000000000..3e6477dc9af Binary files /dev/null and b/src/main/resources/images/address_icon_2.png differ diff --git a/src/main/resources/images/check_icon.png b/src/main/resources/images/check_icon.png new file mode 100644 index 00000000000..1073a962e69 Binary files /dev/null and b/src/main/resources/images/check_icon.png differ diff --git a/src/main/resources/images/check_icon_2.png b/src/main/resources/images/check_icon_2.png new file mode 100644 index 00000000000..ebf8ae5d4ab Binary files /dev/null and b/src/main/resources/images/check_icon_2.png differ diff --git a/src/main/resources/images/checked.png b/src/main/resources/images/checked.png new file mode 100644 index 00000000000..9ede46a30f5 Binary files /dev/null and b/src/main/resources/images/checked.png differ diff --git a/src/main/resources/images/deadline_icon.png b/src/main/resources/images/deadline_icon.png new file mode 100644 index 00000000000..6504a4368b9 Binary files /dev/null and b/src/main/resources/images/deadline_icon.png differ diff --git a/src/main/resources/images/deadline_icon_2.png b/src/main/resources/images/deadline_icon_2.png new file mode 100644 index 00000000000..a6ac174c3f0 Binary files /dev/null and b/src/main/resources/images/deadline_icon_2.png differ diff --git a/src/main/resources/images/description_icon.png b/src/main/resources/images/description_icon.png new file mode 100644 index 00000000000..3baef1f88b1 Binary files /dev/null and b/src/main/resources/images/description_icon.png differ diff --git a/src/main/resources/images/description_icon_2.png b/src/main/resources/images/description_icon_2.png new file mode 100644 index 00000000000..3229895ace8 Binary files /dev/null and b/src/main/resources/images/description_icon_2.png differ diff --git a/src/main/resources/images/email_icon.png b/src/main/resources/images/email_icon.png new file mode 100644 index 00000000000..c2210251d64 Binary files /dev/null and b/src/main/resources/images/email_icon.png differ diff --git a/src/main/resources/images/email_icon_2.png b/src/main/resources/images/email_icon_2.png new file mode 100644 index 00000000000..0c1ba3e4e4d Binary files /dev/null and b/src/main/resources/images/email_icon_2.png differ diff --git a/src/main/resources/images/phone_icon.png b/src/main/resources/images/phone_icon.png new file mode 100644 index 00000000000..4f6d94e5b90 Binary files /dev/null and b/src/main/resources/images/phone_icon.png differ diff --git a/src/main/resources/images/phone_icon_2.png b/src/main/resources/images/phone_icon_2.png new file mode 100644 index 00000000000..4c7dee3a22f Binary files /dev/null and b/src/main/resources/images/phone_icon_2.png differ diff --git a/src/main/resources/images/swift.png b/src/main/resources/images/swift.png new file mode 100644 index 00000000000..348482073ad Binary files /dev/null and b/src/main/resources/images/swift.png differ diff --git a/src/main/resources/images/uncheck_icon.png b/src/main/resources/images/uncheck_icon.png new file mode 100644 index 00000000000..46d4b3005fd Binary files /dev/null and b/src/main/resources/images/uncheck_icon.png differ diff --git a/src/main/resources/images/uncheck_icon_2.png b/src/main/resources/images/uncheck_icon_2.png new file mode 100644 index 00000000000..32dae56b4bd Binary files /dev/null and b/src/main/resources/images/uncheck_icon_2.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..232151cfad8 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -4,6 +4,7 @@ - + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..6445fb31eb7 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,6 +1,33 @@ +@font-face { + font-family: "Inter SemiBold"; + font-style: normal; + font-weight: 500; + src: url("/fonts/Inter-SemiBold.ttf"); +} + +@font-face { + font-family: "Inter Regular"; + font-style: normal; + font-weight: 700; + src: url("/fonts/Inter-Regular.ttf"); +} + +@font-face { + font-family: "Inter Medium"; + font-style: normal; + font-weight: 700; + src: url("/fonts/Inter-Medium.ttf"); +} + +@font-face { + font-family: "Space Grotesk"; + font-style: normal; + src: url("/fonts/SpaceGrotesk-Regular.ttf"); +} + .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: #FAFAFA; + background-color: #FAFAFA; /* Used in the default.html file */ } .label { @@ -8,6 +35,7 @@ -fx-font-family: "Segoe UI Semibold"; -fx-text-fill: #555555; -fx-opacity: 0.9; + -fx-background-color: #FFFFFF; } .label-bright { @@ -26,7 +54,7 @@ .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "SpaceGrotesk Regular"; } .tab-pane { @@ -40,9 +68,9 @@ } .table-view { - -fx-base: #1d1d1d; + -fx-base: #FAFAFA; -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-background-color: #FAFAFA; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -84,77 +112,168 @@ .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #FAFAFA; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #FAFAFA; } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; + -fx-background-color: #FAFAFA; } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #FEFEFE; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #F9F9F9; } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-border-width: 1px; + -fx-border-color: derive(#6D28D9, 20%); + -fx-background-color: #e7e4f9; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; + -fx-border-color: #6D28D9; } -.list-cell .label { - -fx-text-fill: white; +.task-side-panel { + -fx-background-color: #FAFAFA; +} + +.task-side-panel .list-cell:filled { + -fx-background-color: #FAFAFA; + -fx-padding: 5; +} + +.task-side-panel .list-cell:filled:selected { + -fx-border-width: 1px; + -fx-border-color: derive(#6D28D9, 20%); +} + +.task-side-panel .list-cell:filled:selected #cardPane { + -fx-border-color: #6D28D9; +} + +.side-panel-cell-big-label { + -fx-font-size: 15px; + -fx-font-family: "Inter Medium"; + -fx-font-weight: bold; + -fx-text-fill: #333333; + -fx-background-color: transparent; } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Inter SemiBold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: #444444; + -fx-background-color: transparent; +} + +.cell_small_label, .due_date_label { + -fx-font-family: "Inter Medium"; + -fx-font-size: 12px; + -fx-text-fill: #64748B; + -fx-background-color: transparent; +} + +.check_icon { + -fx-background-color: transparent; } -.cell_small_label { - -fx-font-family: "Segoe UI"; +.sub_heading { + -fx-font-family: "Arial"; + -fx-font-weight: bold; + -fx-font-size: 12px; + -fx-text-fill: #A1A1AA; + -fx-background-color: #FAFAFA; +} + +.cell_small_flowPane .label { + -fx-font-family: "Inter Medium"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #AFB4FF; + -fx-background-color: #AFB4FF; +} + +.circle_icon_label { + -fx-background-color: transparent; +} + +.task_cell_deco_circle { + -fx-fill: #A1A1AA; + -fx-stroke: #A1A1AA; + -fx-stroke-type: inside; +} + +.task_cell_deco_circle:selected { + -fx-fill: #6D28D9; +} + +.task_cell_deco_line { + -fx-stroke: #A1A1AA; +} + +.tags_pane .label { + -fx-font-fill: #FFFFFF; + -fx-background-color: #FAFAFA; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-radius: 10; + -fx-background-radius: 10; + -fx-background-color: derive(#E4E4E7, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: #FAFAFA; + -fx-border-color: derive(#F3F4F6, 10%); + -fx-border-top-width: 1px; +} + +.right-pane-with-border { + -fx-background-color: #FAFAFA; + -fx-border-color: derive(#F3F4F6, 10%); + -fx-border-width: 0px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #6D28D9; } .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Space Grotesk"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #7C3AED; } .result-display .label { - -fx-text-fill: black !important; + -fx-text-fill: black; +} + +.result_heading { + -fx-font-family: "Arial"; + -fx-font-weight: bold; + -fx-font-size: 12px; + -fx-text-fill: #A1A1AA; + -fx-background-color: transparent; +} + +.result-display-pane { + -fx-background-color: transparent; + -fx-border-width: 0px, 0px, 0px, 0px; + -fx-border-top-width: 2px; + -fx-border-color: #F3F4F6 } .status-bar .label { @@ -162,6 +281,7 @@ -fx-text-fill: white; -fx-padding: 4px; -fx-pref-height: 30px; + -fx-background-color: #6D28D9; } .status-bar-with-border { @@ -185,76 +305,51 @@ } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#888EA4, 50%); } .context-menu .label { + -fx-background-color: transparent; -fx-text-fill: white; } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#FAFAFA, 20%); + -fx-padding: 0 0 0 20; } .menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-size: 9pt; + -fx-font-family: "Arial"; + -fx-font-weight: bold; + -fx-text-fill: #A1A1AA; -fx-opacity: 0.9; + -fx-background-color: transparent; } .menu .left-container { - -fx-background-color: black; -} - -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ -.button { - -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; - -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; - -fx-background-insets: 0 0 0 0, 0, 1, 2; + -fx-background-color: #FAFAFA; } -.button:hover { - -fx-background-color: #3a3a3a; -} - -.button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + .button { + -fx-background-color: #FAFAFA; + -fx-font-size: 9pt; + -fx-font-family: "Arial"; + -fx-font-weight: bold; + -fx-text-fill: #95959b; + -fx-opacity: 0.9; + -fx-padding: 8 10 8 8; } -.button:focused { - -fx-border-color: white, white; - -fx-border-width: 1, 1; - -fx-border-style: solid, segments(1, 1); - -fx-border-radius: 0, 0; - -fx-border-insets: 1 1 1 1, 0; +.button:hover { + -fx-background-color: #F2F2F2; } -.button:disabled, .button:default:disabled { - -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; +.button:pressed, .button:default:hover:pressed { + -fx-background-color: #F0F0F0; + -fx-text-fill: #959595; } -.button:default { - -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; -} - -.button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); -} .dialog-pane { -fx-background-color: #1d1d1d; @@ -282,11 +377,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#64748B, 85%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#E0DCE5, 50%); -fx-background-insets: 3; } @@ -314,18 +409,26 @@ #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #F1F1F1; } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: transparent; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Space Grotesk"; + -fx-font-size: 13pt; + -fx-text-fill: #000000; +} + +#commandSuggestionTextField { + -fx-background-color: transparent; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Space Grotesk"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #91919A; } #filterField, #personListPanel, #personWebpage { @@ -333,7 +436,7 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: transparent, derive(#E4E4E7, 20%), transparent, derive(#E4E4E7, 20%); -fx-background-radius: 0; } diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..04f0c30e3cb 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -5,7 +5,7 @@ .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: #FAFAFA; } .tag-selector { diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..91e90f29741 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -1,19 +1,23 @@ -#copyButton, #helpMessage { +#helpMessage { + -fx-text-fill: black; +} + +#copyButton { -fx-text-fill: white; } #copyButton { - -fx-background-color: dimgray; + -fx-background-color: #6D28D9; } #copyButton:hover { - -fx-background-color: gray; + -fx-background-color: derive(#6D28D9, 20%); } #copyButton:armed { - -fx-background-color: darkgray; + -fx-background-color: #6D28D9; } #helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#ffffff, 20%); } diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..cedd8b2cee2 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,18 +3,18 @@ - - - - + + + + + - + - + @@ -23,38 +23,67 @@ - - -

- - - - - - - - - - - - + + +