diff --git a/.gitignore b/.gitignore index 823d175eb670..ac7954517c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,9 @@ lib/* *.log *.log.* *.csv -config.json +/config.json src/test/data/sandbox/ +users.json preferences.json .DS_Store ./screenshot*.png diff --git a/.travis.yml b/.travis.yml index 1ffe1f2a5c77..538686b08e0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: script: >- ./config/travis/run-checks.sh && - travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyDummySearchPage + travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyEventSearchPage deploy: skip_cleanup: true diff --git a/LICENSE b/LICENSE index 39b3478982c3..154d3ad2fc26 100644 --- a/LICENSE +++ b/LICENSE @@ -2,11 +2,11 @@ MIT License Copyright (c) 2016 Software Engineering Education - FOSS Resources -Permission is hereby granted, free of charge, to any person obtaining a copy +Permission is hereby granted, free of charge, to any event obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit events to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all diff --git a/README.adoc b/README.adoc index 142ae1b17fbd..bc929511176d 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,7 @@ -= Address Book (Level 4) -ifdef::env-github,env-browser[:relfileprefix: docs/] += Event Manager -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2113-AY1819S1-T12-1/main[image:https://travis-ci.org/CS2113-AY1819S1-T12-1/main.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2113-AY1819S1-T12-1/main?branch=master[image:https://coveralls.io/repos/github/CS2113-AY1819S1-T12-1/main/badge.svg?branch=master[Coverage Status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,27 +11,23 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Event Manager application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. == Site Map -* <> -* <> -* <> -* <> -* <> +* https://github.com/CS2113-AY1819S1-T12-1/main/blob/master/docs/UserGuide.adoc[User Guide] +* https://github.com/CS2113-AY1819S1-T12-1/main/blob/master/docs/DeveloperGuide.adoc[Developer Guide] +* https://github.com/CS2113-AY1819S1-T12-1/main/blob/master/docs/LearningOutcomes.adoc[Learning Outcomes] +* https://github.com/CS2113-AY1819S1-T12-1/main/blob/master/docs/AboutUs.adoc[About Us] +* https://github.com/CS2113-AY1819S1-T12-1/main/blob/master/docs/ContactUs.adoc[Contact Us] == Acknowledgements * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] +* Project based on: https://se-edu.github.io/addressbook-level4/[se-edu/addressbook-level4] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..e1f65cc16c4f --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "chiaxr", + "displayName": "CHI...ONG", + "authorNames": ["chiaxr"] + }, + { + "githubId": "cqinkai", + "displayName": "CHU...KAI", + "authorNames": ["cqinkai"] + }, + { + "githubId": "Geraldcdx", + "displayName": "GER...ANG", + "authorNames": ["Geraldcdx","Gerald Chua Deng Xiang"] + }, + { + "githubId": "Tertium3", + "displayName": "HO ...ONG", + "authorNames": ["Tertium3"] + }, + { + "githubId": "jamesyaputra", + "displayName": "JAM...TRA", + "authorNames": ["jamesyaputra"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..ba896886bea7 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,8 @@ dependencies { String jUnitVersion = '5.1.0' implementation group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' + implementation group: 'org.jsoup', name: 'jsoup', version: '1.11.3' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' implementation group: 'com.google.guava', name: 'guava', version: '19.0' @@ -66,6 +68,8 @@ dependencies { implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.0' implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0' implementation group: 'javax.activation', name: 'activation', version: '1.1.1' + implementation group: 'org.jsoup', name: 'jsoup', version: '1.11.3' + implementation group: 'org.mnode.ical4j', name: 'ical4j', version: '3.0.0' testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation group: 'org.testfx', name: 'testfx-core', version: testFxVersion, { @@ -82,7 +86,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'eventmanager.jar' destinationDir = file("${buildDir}/jar/") } @@ -134,6 +138,7 @@ test { testLogging { events TestLogEvent.FAILED, TestLogEvent.SKIPPED + exceptionFormat 'full' // Prints the currently running test's name in the CI's build log, // so that we can check if tests are being silently skipped or @@ -207,8 +212,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', + 'site-name': 'Event Manager', + 'site-githuburl': 'https://github.com/CS2113-AY1819S1-T12-1/main', 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] @@ -236,8 +241,8 @@ task deployOfflineDocs(type: Copy) { } } -task copyDummySearchPage(type: Copy) { - from 'docs/DummySearchPage.html' +task copyEventSearchPage(type: Copy) { + from 'docs/EventSearchPage.html' into "${buildDir}/docs/html5" } diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..a6ea429759b5 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,52 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +Event Manager is based on Address-Book Level 4, which was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + +We are a team of Y2 Computer Engineering undergraduate students based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Chia Xiang Rong +image::chiaxr.png[width="150", align="left"] +{empty}[http://github.com/chiaxr[github]] [<>] -Role: Project Advisor +Role: Developer + +Responsibilities: RSVP/Attendance ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Gerald Chua Deng Xiang +image::geraldcdx.png[width="250", align="left"] +{empty}[https://www.comp.nus.edu.sg/~geraldc/website2-0/index.html[homepage]] [https://github.com/Geraldcdx[github]][<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: Comment Section Architect ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== James Arista Yaputra +image::jamesyaputra.png[width="150", align="left"] +{empty}[http://github.com/jamesyaputra[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: User Authentication ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Chuang Qin Kai +image::cqinkai.png[width="150", align="left"] +{empty}[http://github.com/cqinkai[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Event Reminders ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Ho Phi Long +image::tertium3.png[width="150", align="left"] +{empty}[https://github.com/Tertium3[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Sort, Filter and Calendar export ''' diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..201f8ba8e01b 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += Event Manager - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,9 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2113-AY1819S1-T12-1/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `CS2113T-T12-1`      Since: `Aug 2018`      Licence: `MIT` == Setting up @@ -147,7 +147,8 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `EventManagerChangedEvent` when the event manager data are changed, instead of +asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. @@ -167,7 +168,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `EventListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses 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 link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -187,9 +188,9 @@ image::LogicClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `EventManagerParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a event) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -208,12 +209,12 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the event manager data. +* exposes 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. * does not depend on any of the other three components. [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can store a `Tag` list in `event manager`, which `Event` can reference. This would allow `event manager` to only require one `Tag` object per unique `Tag`, instead of each `Event` needing their own `Tag` object. An example of how such a model may look like is given below. + + image:ModelClassBetterOopDiagram.png[width="800"] @@ -228,69 +229,142 @@ image::StorageClassDiagram.png[width="800"] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the event manager data in xml format and read it back. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. == Implementation This section describes some noteworthy details on how certain features are implemented. +// tag::authentication[] +=== Authentication +==== Current Implementation + +The authentication mechanism is facilitated by the `Command` class and the `UserAccount` class. +It stores the user information, which includes username and password, using a JSON file. + +New methods are added in the `Model` interface to check whether a user account exists in the JSON file, and to create new user accounts in the JSON file. +Additional methods are also added to check the login and admin status of the user. + +Additionally, a `JsonUserStorage` class has been created to handle the reading, parsing and writing of the JSON file. + +Given below is an example usage scenario and how authentication behaves at each step. + +|=== +|Step 1. The user launches the application for the first time. The `JsonUserStorage` class will create a default JSON file in `data/users.json` that stores the basic `admin` account information. + +Step 2. The user has the option to create a new account using the command `signup u/USERNAME p/PASSWORD`. This will trigger the method `createUser(user)` in the `Model Manager` class, which is linked to `UserSession`. + +Step 3. The user executes the command `login u/USERNAME p/PASSWORD`. This will trigger the method `userExists(user)` in the `ModelManager` class. + +Step 4. `UserSession` will prompt `JsonUserStorage` to read the JSON file and return to it the JSONObject parsed from the file. + +Step 5. `UserSession` will then compare the logged username and password with the ones stored in the JSON file. If the comparisons return true, the `userExists` method will return true. + +Step 6. The `currentUser` in the `Command` class will then be set and the login flag will be set to true. If the user is an admin, the admin flag will be set to true as well. + +Step 7. After authentication, the user can now start using the application. + +Step 8. The user can choose to log out of the application as well with the `logout` command. This command sets the login flag to false and clears `currentUser`. +|=== + +The sequence diagrams below describes the steps elaborated above. + +image::SignupDiagram.png[width="800"] +image::LoginDiagram.png[width="800"] + +[NOTE] +If an authentication fails, i.e. credentials are wrong or do not exist in the JSON file, the `login` command will throw a `CommandException`. + +[IMPORTANT] +Only one admin account is registered at any given time, with `admin` and `root` being used as username and password respectively. + +==== Password encryption +Allows passwords to be encrypted instead of being stored as plain text. Password encryption and validating is done through the `PasswordUtil` class using the _PBKDF2WithHmacSHA1_ encryption algorithm. + + +The encrypted password consists of a randomly generated salt and a hash generated from the plain text password, both converted to hexadecimal before being stored inside `users.json`. + +==== Design Considerations +* Instead of encrypting each user's password, we initially considered encrypting the entire `users.json` file instead. However, after careful consideration, we decided that it would be sub-optimal due to the +inefficiency of having to constantly encypt and decrypt the files while the application is running. +* Instead of having the authentication feature being a part of the `logic` component, we initially considered having `authentication` as a component of its own, which precedes the entire running of the `MainApp`. However, +we decided that it would introduce redundancies in the codebase as it requires rewriting a handful of functionalities that are already present within the `logic` component, thus we opted for our current implementation. + +// end::authentication[] + // tag::undoredo[] === Undo/Redo feature ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. +The undo/redo mechanism is facilitated by `VersionedEventManager`. +It extends `EventManager` with an undo/redo history, stored internally as an `eventManagerStateList` and +`currentStatePointer` +. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +* `VersionedEventManager#commit()` -- Saves the current event manager state in its history. +* `VersionedEventManager#undo()` -- Restores the previous event manager state from its history. +* `VersionedEventManager#redo()` -- Restores a previously undone event manager state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitEventManager()`, `Model#undoEventManager()` and `Model#redoEventManager()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedEventManager` will be initialized with the +initial event manager state, and the `currentStatePointer` pointing to that single event manager state. image::UndoRedoStartingStateListDiagram.png[width="800"] -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th event in the event manager. The `delete` command calls +`Model#commitEventManager()`, causing the modified state of the event manager after the `delete 5` command executes to be saved in the `eventManagerStateList`, and the `currentStatePointer` is shifted to the newly inserted event manager state. image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add n/Party ...` to add a new event. The `add` command also calls `Model#commitEventManager()`, causing another modified event manager state to be saved into the `eventManagerStateList`. image::UndoRedoNewCommand2StateListDiagram.png[width="800"] [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`. +If a command fails its execution, it will not call `Model#commitEventManager()`, so the event manager state will not be saved into the `eventManagerStateList`. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the event was a mistake, and decides to undo that action by executing the +`undo` command. The `undo` command will call `Model#undoEventManager()`, which will shift the `currentStatePointer` once +to the left, pointing it to the previous event manager state, and restores the event manager to that state. image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book 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. +If the `currentStatePointer` is at index 0, pointing to the initial event manager state, then there are no previous +event manager states to restore. The `undo` command uses `Model#canUndoEventManager()` to check if this is the case. +If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite -- it calls `Model#redoEventManager()`, which shifts the `currentStatePointer` once +to the right, pointing to the previously undone state, and restores the event manager to that state. [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book 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. +If the `currentStatePointer` is at index `eventManagerStateList.size() - 1`, pointing to the latest event manager state, +then there are no undone event manager states to restore. The `redo` command uses `Model#canRedoEventManager()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that do not modify the event manager, such as +`list`, will usually not call `Model#commitEventManager()`, `Model#undoEventManager()` or `Model#redoEventManager()`. Thus, the +`eventManagerStateList` remains unchanged. image::UndoRedoNewCommand3StateListDiagram.png[width="800"] -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. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#commitEventManager()`. Since the `currentStatePointer` is not +pointing at the end of the `eventManagerStateList`, all event manager states after the `currentStatePointer` will be +purged. + +We designed it this way because it no longer makes sense to redo the `add n/Party ...` command. This is the behavior +that most modern desktop applications follow. image::UndoRedoNewCommand4StateListDiagram.png[width="800"] @@ -302,27 +376,336 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire event manager. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **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). +** Pros: Will use less memory (e.g. for `delete`, just save the event being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of event manager states. ** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both +`HistoryManager` and `VersionedEventManager`. * **Alternative 2:** Use `HistoryManager` for undo/redo ** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] + +// tag::comments[] +=== Comments +==== Current Implementation + +The comments feature is facilitated by `Comments` class in the Logic/Comments folder. `AddComment`, `DeleteComment` and `ReplyComment` classes extend the `Comments` class. `CommentFacade` class creates objects of `AddComment`, `DeleteComment` and `ReplyComment`. The features of the following classes are as such: + +* `Comments` -- Handles storage of comments, contains `initComments(String input)` to reformat comment section to HTML, `parseCommentSection(String input)` to format the comment section into a vector and `rewrite(Vector commentsVector)` to obtain the edited comment section. + +* `AddComment` -- Adds a new comment to the end of the comment section with the `addComment(String comment, String username)` + +* `DeleteComment` -- Deletes a comment given the line parameter in `deleteComment(int line)` + +* `ReplyComment` -- Replies a comment given the line parameter in `replyComment(String comment, int line, String username)` + +* `CommentFacade` -- An implementation of the Facade design pattern to interact with AddCommentCommand, DeleteCommentCommand and ReplyCommentCommand. It contains addComment(String input, String comment, String username) to be used in AddCommentCommand to add a comment, deleteComment(String input, int line) to be used in DeleteCommentCommand to delete a comment and replyComment(String input, int line, String comment) to be used in ReplyCommentCommand to reply comments. + +The Command Line Interface uses `AddCommentCommand`, `DeleteCommentCommand`, `ReplyCommentCommand` and `EditCommand` for the user to interact with the comment section. The features of the following classes are as such: + +* `AddCommentCommand` -- Adds a comment using `CommentFacade` and `AddCommentCommandParser` + +* `DeleteCommentCommand` -- Deletes a comment using `CommentFacade` and `DeleteCommentCommandParser` + +* `ReplyCommentCommand` -- Replies to a comment using `CommentFacade` and `ReplyCommentCommandParser` + +* `EditCommand` -- Resets the whole comment section using `editEventDescriptor` and `EditCommandParser` + +Given below is an example usage scenario and how the Comments mechanism behaves at each step. + +Step 1. The user launches the application, logs in and click on an event or types `select INDEX` into the CLI. The comment section will be seen along with other details in the `BrowserPanel`. + +Step 2. The user/admin executes `addComment 1 C/May I ask, what is the attire for the event?` to add a comment to the 1st event in the Event Manager. `AddCommentCommand` command obtains the comment section from `eventmanager.xml` calls `CommentFacade` to add comment "May I ask, what is the attire for the event", into the comment section and stores the comment section into `eventmanager.xml` + +The following sequence diagram shows how the AddCommentCommand operation works: + +.Sequence Diagram for AddCommentCommand +image::addCommentSequenceDiagram.png[width="800"] + +[Note] +==== +*Detailed description of diagram*: The user inputs "addComment 1 C/Hi". `LogicManager#execute("addComment 1 C/Hi")` and calls `EventManagerParser#parseCommand("addComment 1 C/Hi")`. Then, `AddCommentCommandParser#parse("1 C/Hi")` will be called and `AddCommentCommand#execute()` will obtain the event needed from `eventmanager.xml`. Finally, `CommandFacade#addComment` will be called and `AddComment#addComment` will process and add the new comment into the comment section. After all this, results will be returned to the various receivers and display an updated comments section to the user. + +The `replyComment` and `deleteComment` command does similar methods and need not be elaborated. +==== + +Step 3. The user/admin executes `replyComment 1 L/1 C/Athletic attire` to reply the comment in step 2. `ReplyCommentCommand` command obtains the comment section from `eventmanager.xml` calls `CommentFacade` to reply comment with "Athletic attire", into the comment section and stores the comment section into `eventmanager.xml` + +Step 4. The admin executes `deleteComment 1 L/1` to delete a comment at index 1, line 1 of comment section. `DeleteCommentCommand` command obtains the comment section from `eventmanager.xml` calls `CommentFacade` to delete "Athletic attire" from the comment section and stores the comment section into `eventmanager.xml` + +Step 5. If the admin wants to reset or make a new comment section of an event, the valid command of `edit INDEX C/{span}Comment Section{/span}{ol}{/ol}` can be used + +[NOTE] +==== +* If a command's syntax is wrong, the application will prompt the user to try again and suggest a relevant format to follow. +* In the case a false indexed event is not present, the functions will return an invalid index message. +* In the case a false comment section line is given, the functions will return an invalid line message. + If the user uses the `find` command, the following functions will follow the indexing of the `find` command. +==== + +==== Design Considerations + +===== Aspect: How comment section is stored + +* **Alternative 1 (current choice):** Comment section stored in a single field in `eventmanager.xml` +** Pros: Comment section will be easy to parse because only one field is used for comment section. +** Cons: If a developer wants to manipulate specific comments through eventmanager.xml file, there is no functions created for it. +* **Alternative 2:** Store each comment as a seperate field and extract each comment individually. +** Pros: No HTML tags will be stored in the field. +** Cons: New methods or data structures will need to be implemented to make many fields for comments. + +===== Aspect: Data structure to support the comment function commands + +* **Alternative 1 (current choice):** A vector is used to store the comment section to add, insert or delete relevant comments. +** Pros: A simple data structure that has vector.add() and vector.delete() methods to help edit the comment section easily. +** Cons: Additional method is needed to parse the comment section into a vector. +* **Alternative 2:** An arrayList or List +** Pros: Library functions can help parse the comment section into the arrayList. +** Cons: More code is needed to simply insert or delete elements inside the data structure. + +// end::comments[] + +// tag::rsvp[] +=== RSVP +==== Current Implementation +The RSVP feature consists of the `register` & `unregister` command. A `removeAttendee` command is included for admin use to remove users forcibly if required. The implementations of the commands use the `EditEventDescriptor` class and `createEditedEvent` method from `EditCommand` to aid in updating event attendance. Attendees of an event are stored in the `eventmanager.xml` file, in a similar fashion to the storage of tags. + +Below is an example usage scenario and how the RSVP mechanism behaves: + +|=== +Step 1: The user launches the application, and logs in. + +Step 2: The user clicks on an event or types `select 2` into the CLI. Details of the 2nd event including event attendance are displayed. + +Step 3: The user executes `register 2` to register for the 2nd event. The `register` command takes in the current model and event at index 2, getting the username of the current user via `Model#getUsername`, and the current attendance of the event as a `HashSet` with `Event#getAttendance`. + +Step 4: The `register` command tries to add the username into the current attendance with `HashSet#add`. If the username already exists in the attendance, a `CommandException` is thrown. Else, `EditCommand#EditEventDescriptor` is used with the new attendance to create an edited event. + +Step 5: The model is updated and committed, overwriting `eventmanager.xml`. The event page is reloaded to display the new event attendance. + +Step 6: If the user decides to unregister from the event, the user executes `unregister 2`, and the `unregister` command gets the username and attendance in the same manner as the `register` command in Step 3. + +Step 7: The `unregister` command command tries to remove the username from the current attendance with `HashSet#remove`. If the username does not exist in the attendance, a `CommandException` is thrown. Else, `EditCommand#EditEventDescriptor` is used with the new attendance to create an edited event. + +Step 8: The model is updated and committed as in Step 4, and the event page is again reloaded. +|=== + +[NOTE] +`removeAttendee` works in similar manner to `unregister`, except the username of the target attendee is used when calling `HashSet#remove`. + +.Sorting of attendance +[NOTE] +`TreeSet` is used when retrieving the attendance for display as it allows for easy sorting of attendee usernames. Usernames are sorted in case-insensitive lexicographical order. + +The following sequence diagrams show how the `register` and `removeAttendee` operations work: + +.Sequence diagram for register operation +image::registerSD.png[width="800"] + +.Sequence diagram for removeAttendee operation +image::removeAttendeeSD.png[width="800"] + +==== Design Considerations +===== Aspect: How to display attendance +* **Alternative 1 (current choice):** Display event attendance list +** Pros: Can see which other users are attending the event +** Cons: Attendees might have privacy concerns regarding how other users can see whether they are attending an event. +* **Alternative 2:** Display whether current user is registered for an event +** Pros: Easier to implement, user can easily see whether they are registered +** Cons: Cannot see other attendees. + +===== Aspect: Where to store attendance +* **Alternative 1 (current choice):** Attendance stored in `eventmanager.xml` in similar fashion to tags. +** Pros: Methods for parsing tags can be applied to parse attendance +** Cons: Inefficient to retrieve list of events which a user has registered for +* **Alternative 2:** Store in user profile +** Pros: Can easily check which events a user has registered for. +** Cons: Inefficient to check which users are attending an event. +* **Alternative 3:** Store in both `eventmanager.xml` and user profile. +** Pros: Allows for efficient retrieval of both event attendance and events that user has registered for. +** Cons: Additional complexity to implement storage in user profile, data redundancy. + +===== Aspect: How to store attendance +* **Alternative 1 (current choice):** Attendance stored in unsorted order. +** Pros: Easy to add new attendee to attendance. +** Cons: Requires sorting whenever attendance is displayed. +* **Alternative 2:** Attendance stored in sorted order. +** Pros: No need to sort each time an event is reloaded. +** Cons: More complexity for inserting in correct location. +// end::rsvp[] + +//tag::eventStatus&Reminder[] +=== Event Status and Reminders +==== Current Implementation +The Event Status and Reminder feature consists of two commands periodically executed by `LogicManager`. Specifically, the `UpdateStatusCommand` and the `ReminderCommand`. The automated process is facilitated by the `Timer` and `TimerTask` classes in the `java.util` package. The `TimerTask` `updateEventStatus` has a period of 300,000 ms where it will execute an instance of the `UpdateStatusCommand`, while the `TimerTask` `checkEventReminders` has a period of 36,000,000 ms where it will execute an instance of the `ReminderCommand`. + +Both commands initially calls the model#getFilteredEventList() method to obtain the displayed list of events as `lastShownList`. + +The *UpdateStatusCommand* updates the status of each event in the `lastShownList` using `Status#setStatus()` and `model#updateEvent()`. It then refreshes the displayed list by calling `model#updateFilteredEventList()`. + +The following sequence diagram shows how the UpdateStatusCommand works: + +.Sequence Diagram for UpdateStatusCommand +image::Update-sequenceDiagram.png[width="800"] + +[NOTE] +The Event Status feature is supported by the `Status` and `DateTime` field in the `Event`. Events with `DateTime` fields before the current `Date` will assume the `COMPLETED Status`, whereas those with `DateTime` fields after the current `Date` will take on the `UPCOMING Status`. + +The *ReminderCommand* checks for the following in each event: +===== +. `checkAttendeeKeywordsMatchEventAttendee` -- checks if the current user is registered as an attendee +. `checkEventIsUpcoming` -- checks if the event is upcoming (happening in the next 24 hours) +===== + +If the two conditions are satisfied, a `sendEventReminder` event containing the event's name and starting time is used to communicate with the `UiManager` to show an alert dialog using `Ui#showAlertDialogAndWait()` to display the event's information. + +The following sequence diagram shows how the ReminderCommand works: + +.Sequence Diagram for ReminderCommand +image::Reminder-sequenceDiagram.png[width="800"] + +==== Design Considerations +===== Aspect: To automate the commands or make them user-enabled +* **Alternative 1 (current choice):** Status updates and reminders automated using `Timer`. +** Pros: Takes the updating and checking tasks off users. Less reliance on users' end also means that updates and reminders are executed more regularly. +** Cons: Uses up more processing resources. +* **Alternative 2:** Users have to run status updates and reminders checking. +** Pros: Ensures that the updated status or reminders are provided to users when they want it. +** Cons: Users may be looking at very outdated statuses and will not receive reminders if they forget to check for it. + +===== Aspect: How to automate the updates/checks +* **Alternative 1 (current choice):** Status updates and reminder checks called using `Timer`. +** Pros: A more reliable way to update the status and check for reminders. +** Cons: More complexity added to the codes and timers use up more processing resources. +* **Alternative 2:** Status updates and reminders called after each command given by the user. +** Pros: Easier implementation by calling the status update or reminder check after every user command. +** Cons: Less reliable and less effective method of updating since the statuses will not be updated if the user does not execute any commands. + +===== Aspect: Where to implement the update and reminder command +* **Alternative 1 (current choice):** Both features are subclass of the `Command` superclass. +** Pros: Easier implementation since there are already methods to execute commands. This implementation also allows users to call the commands if necessary. +** Cons: Both features are not really commands that should be executed by the user and thus should not be subclasses of `Command`. +* **Alternative 2:** Create a new class in `LogicManager` which is responsible for the execution. +** Pros: The `TimerTask` could be implemented in the command and the `UpdateStatusCommand` and `ReminderCommand` need only be called once. This also decreases the coupling with `MainApp` and `EventManagerParser`. +** Cons: A new method for executing the two commands would be required and the user would not be able to call for a status update should the need arise. + +===== Aspect: How to implement the status update +* **Alternative 1 (current choice):** Implemented using pre-existing codes such as `EditEventDescriptor` and `model#updateEvent()`. +** Pros: No need to add new codes for the implementation and add unnecessary complexities into the project code. +** Cons: Inefficient method to update only the status since every other field in the event has to be copied over each time the status is updated. +* **Alternative 2:** Write a `Event#setStatus()` method to update the status. +** Pros: More efficient way of updating the statuses of events thus reducing the consumption of processing resources. +** Cons: More lines of codes required and also adds to the complexity of the project code. + +===== Aspect: Whether to add implementation to allow users to set reminders +* **Alternative 1 (current choice):** Reminders are automatically sent to users who registered for an event. +** Pros: Saves users the trouble of having to set the reminders themselves. +** Cons: Users cannot unsubscribe to the reminders for events that they have registered for. +* **Alternative 2:** Allow users to set reminders as they wish. +** Pros: Users who do not want reminders can refuse to set reminders. +** Cons: Users would have to set their own reminders. Some users may choose to save themselves the trouble of setting reminders and miss the events they have registered for. +//end::eventStatus&Reminder[] + +//tag::sorting[] +=== Default sorting +==== Current Implementation +Since our product is an event manager, events should be controlled and view in chronological order. +To do this, `UniqueEventList` class was modified so as to sort the event list in Date order, follow by Name order. + + +Consider this scenario: + +Step 1: User launches application, then logs in + +Step 2: User adds a new event which will occur before some of the other events in the list + +e.g: `add n/Jack Birthday Party ... d/10/10/2018 20:30...` + +Step 3: When add method is called, it performs the intended operation, then sorts the list before returning it to other components. + +Step 4: The event list panel is reloaded and displays the newly added event in the correct place. + +==== Aspect: How to sort the list +* **Alternative 1 (current choice):** Event list will be sorted based on sort method implemented in `UniqueEventList` class to modify the internal list which event manager is backed on. +** Pros: Easy to implement with minimum modification that could affect other components. +** Cons: Every method that changes the internal list (e.g: add, setEvent, delete) will need to implement the sorting method again at the end of the method. + +* **Alternative 2:** Sort only when we need to get the list if the list is not sorted. +** Pros: The easiest implementation without affecting other components. +** Cons: The sort operation when called by other components, for example the UI component, will return operations to the main thread, which will severely affect testing with JUnit on JavaFX thread. + +* **Alternative 3:** Only sort the list for displaying on the UI +** Pros: Will perform minimal operation while still returning what we need to observe. +** Cons: Very complicated implementation as the UI is updated based on observing internal list. We will need a class to update the UI if we only want to sort the list on display. +// end::sorting[] + +// tag::findEnhancement[] +=== Enhance current find command +==== Current Implementation +`find` command is used for better navigation. Therefore, it is enhanced to search for more properties in an Event. + + +`find` can search for any data with the default keywords and . If specific prefixes are added, find can search for events that must contain that keyword in the specific fields. + +[NOTE] +If there are more than 1 prefixes of the same type, for example, `find n/new n/dark n/meeting`, they will be automatically combined together, which means that this command will be assumed to be the same as `find n/new dark meeting`. + +Current version implementation uses logic AND operator for different prefixes. + +==== Aspect: How to improve search +* **Alternative 1 (current choice):** Modify the predicate to display the events that contain one of the keywords. +** Pros: Follows the current structure of `find` command, which means that current resources can be reused. +** Cons: With the current implementation of the predicate, scaling will severely affect product performance. + +==== Future enhancement: +* Search options for keywords contained or for the exact keywords. +* Search with both `logic AND and OR operator` with different prefixes. +* Search for events within a time range. +//end::findEnhancement[] + +// tag::exportcalendar[] +=== Export RVSP-ed events to iCalendar file +==== Current implementation +This feature will increase the compatibility of Event Manager with other calendar app for better planning. + +Consider the following scenario: + +Step 1: User launches the application, then logs in. + +Step 2: User executes `export my event calendar` command. The export command receive argument to accept as filename + +Step 3: Current user, who is logged in, will be used to receive an event filtered list that he/she has registered for. + +Step 4: An FileOutputStream will be created to create new file/re-write if the file exist will the data from the filtered event list convert to iCalendar file format. File will be stored in *folder that you store EventManager* + +==== Aspect: How to export the event list +* **Alternative 1 (current implementation):** Using ical4j external library to create methods to convert events to RFC5545 format, then stream to FileOutputStream with given filename from user. + +All method are written in the `ExportCalendarCommand` class. +** Pros: Easy to implement, can reuse current resources and easy to match wth the implementation of Attendance list. +** Cons: Violates some of the OOP design as the export method should be in the storage class. +* **Alternative 2:** Create a class to write an .ics file with given RFC5545 standard. +** Pros: Have better control of the output file, since the ical4j support API has not been updated for a long time and currently shows some areas which are lacking. +** Cons: Very complicated and time consuming. + +[NOTE] +==== +Calendar will be exported to your src/data folder. +==== + +==== [Proposed]: Future enhancement +Export should be able to export the attendance list of an event according to user preference. +// end::exportcalendar[] + // tag::dataencryption[] -=== [Proposed] Data Encryption +//=== [Proposed] Data Encryption -_{Explain here how the data encryption feature will be implemented}_ +//_{Explain here how the data encryption feature will be implemented}_ // end::dataencryption[] @@ -340,8 +723,8 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is * `WARNING` : Can continue, but with caution * `INFO` : Information showing the noteworthy actions by the App * `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size - [[Implementation-Configuration]] + === Configuration Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). @@ -528,338 +911,197 @@ Here are the steps to create a new release. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, event manager depends on the http://wiki.fasterxml +.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started - -Suggested path for new programmers: - -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. - -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. - -[[GetStartedProgramming-EachComponent]] -=== Improving each component - -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). - -[discrete] -==== `Logic` component - -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. - -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. - -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** - -[discrete] -==== `Model` component - -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. - -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** - -[discrete] -==== `Ui` component - -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. - -[TIP] -Do take a look at <> before attempting to modify the `UI` component. - -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** - -[discrete] -==== `Storage` component - -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. - -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** - -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` - -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. - -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. - -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` - -Examples: - -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. - -==== Step-by-step Instructions - -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. - -**Main:** - -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. - -**Tests:** - -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. - -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` - -**Main:** - -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. - -**Tests:** - -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. - -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. - -**Main:** - -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +== Product Scope -**Tests:** +*Target user profile*: -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +* has a need to manage a significant number of contacts +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +*Value proposition*: manage contacts faster than a typical mouse/GUI driven app -**Main:** +[appendix] +== User Stories -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -**Tests:** +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |New user |See usage instructions |Refer to them when I forget how to use the App -. Add test for `Remark`, to test the `Remark#equals()` method. +|`* * *` |New user |Create an account |RSVP for events -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|`* * *` |User |View event details |- -**Main:** +|`* * *` |User |Be reminded of events I have registered for |Remember to attend those events -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +|`* * *` |Busy Student |List Events |View all events to keep track -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +|`* * *` |Admin |Create new events |Users can RSVP to them -**Main:** +|`* * *` |Admin/User |View all participants |Get overall attendance for event -. Add a new Xml field for `Remark`. +|`* * *` |Admin |Delete a event |Remove entries that I no longer need -**Tests:** +|`* * *` |User |find a event by name |locate details of events without having to go through the entire list -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +|`* *` |User |hide <> by default |minimize chance of someone else seeing them by accident -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|`* *` |Admin |Edit events |So changes can be made without me deleting and creating a new event -**Tests:** +|`* *` |User |Filter events | To list the types of events that are coming up -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +|`* *` |User |See Statuses of events |Easily discern completed events from upcoming ones -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +|`* *` |User | Able to post questions somewhere | I clarify any doubts regarding the event -**Main:** +|`* *` |Admin | Manage comment section | To prevent abuse by users in a comments section -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +|`*` |user with many events in the Event Manager |sort events by name |locate a event easily +|======================================================================= -**Tests:** +_{More to be added}_ -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +[appendix] +== Use Cases -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +For all the use cases below, System refers to the EventManager, Actor refers to the admin/user. -**Main:** +// tag::authenticationUsecase[] +[discrete] +=== Use Case: Authentication +*MSS* -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +. User signs up for an account in the Event Manager. + +. User logs in by entering correct username and password. + +. Event Manager grants access to User and displays welcome message. + +Use case ends. -**Tests:** +*Extensions:* + +[none] +* 2a. User inputs incorrect password. +[none] +** 2a1. Event Manager denies access to the user and displays error message. + +Use Case resumes at step 1. +// end::authenticationUsecase[] -. Update `RemarkCommandTest` to test that the `execute()` logic works. +// tag::rsvpUsecase[] +[discrete] +=== Use case: Registration +*MSS* -==== Full Solution + 1. User requests to list events. + 2. EventManager displays list of events. + 3. User selects event with status [UPCOMNG]. + 4. EventManager displays details of selected event, including current attendance list. + 5. User requests to register for the event. + 6. EventManager adds user to attendance list and displays confirmation message. -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +*Extensions:* +[none] +* 5a. User is already registered for event. +[none] +** 5a1. EventManager displays error message. +Use case resumes at step 2. -[appendix] -== Product Scope +[none] +* 6a. User unregisters from event. +[none] +** 6a1. User requests to unregister for the event. +** 6a2. EventManager removes user from attendance list and displays confirmation message. +Use case ends. -*Target user profile*: +[none] +* 6b. User is banned from event. +[none] +** 6c1. Admin requests to remove user from event. +** 6c2. EventManager removes user from attendance list and displays confirmation message. +Use case ends. +// end::rsvpUsecase[] -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +// tag::reminderUsecase[] +[discrete] +=== Use case: Reminder +*MSS* -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +. User registers for an event "House Dinner". +. (a few days passes) A reminder is sent to the user for the event "House Dinner", 24 hours before the event time. +. User is redirected to the event's Browser Panel. +. User hits enter to close the alert dialog. +. User reads the information about the event. +Use case ends. +// end::reminderUsecase[] -[appendix] -== User Stories +// tag::updateStatusUsecase[] +[discrete] +=== Use case: Event Status +*MSS* -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +. User views list of events. +. User requests to update the statuses of events. +. Event Manager updates the statuses of events and refreshes the displayed list of events. +. User can easily locate "UPCOMING" events. +Use case ends +// end::updateStatusUsecase[] -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|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 +[discrete] +=== Use case: Comments +*MSS* -|`* * *` |user |add a new person | +. User views the event information by clicking on the event cards or using the select command. + +. EventManager displays a comment section. + +. User inputs comment command. + +. EventManager executes command based on what user keys in. + +. Repeat 3 and 4 until User types “exit”. + +Use case ends -|`* * *` |user |delete a person |remove entries that I no longer need +*Extensions:* +[none] -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +* 3a. The comment commands are as such: ++ +[none] +** 3a1. `replyComment INDEX L/LINE C/STRING` will reply to the comment at event INDEX at LINE of comment section. +** 3a2. `addComment INDEX C/STRING` will adds the STRING to the bottom of the comment section at event INDEX. +** 3a3. `deleteComment INDEX L/LINE` (only for admin) it will delete the comment at event INDEX and comment at LINE of comment section. + +Resume use case at step 4. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +[discrete] +=== Use Case: Export calendar +*MSS* -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +1. User request to export registered event list with given name + +2. EventManager execute exportCalendarCommand +3. A filename with .ics extension create or re-write in src/data/ folder -_{More to be added}_ +*Extensions:* + [none] + 0a) User viewing current list of registered events. -[appendix] -== Use Cases + 1a) User modified registered list with command. + 1a1) The new list is exported in .ics file. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `EventManager` and the *Actor* is the `user`, unless specified otherwise) [discrete] -=== Use case: Delete person +=== Use case: Delete event *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list events +2. EventManager shows a list of events +3. User requests to delete a specific event in the list +4. EventManager deletes the event + Use case ends. @@ -873,7 +1115,7 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. EventManager shows an error message. + Use case resumes at step 2. @@ -883,7 +1125,7 @@ _{More to be added}_ == Non Functional Requirements . Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 1000 events without a noticeable sluggishness in performance for typical usage. . 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}_ @@ -938,15 +1180,15 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person +=== Deleting a event -. Deleting a person while all persons are listed +. Deleting a event while all events are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all events using the `list` command. Multiple events in the list. .. 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. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No event is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. diff --git a/docs/EventSearchPage.html b/docs/EventSearchPage.html new file mode 100644 index 000000000000..cf85ac55b2c2 --- /dev/null +++ b/docs/EventSearchPage.html @@ -0,0 +1,44 @@ + + + + + Dummy Search Page + + + +

Event Name:

+ Event IC:
+ IC Contact:
+ IC Email:
+ Event Venue:
+ Time:
+
+ + Event Attendance:
+ +
+
+ + + + diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..a194de8dcbea 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += Event Manager - User Guide :site-section: UserGuide :toc: :toc-title: @@ -18,13 +18,13 @@ By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +Event Manager is made for the residents of the Residential Colleges (RC) in the National University of Singapore (NUS). This application is build for those who prefer to use a desktop application for managing events and attendees. Moreover, the Event Manager offers a faster workflow *for those who prefer working with a Command Line Interface* while still retaining the benefits of having a Graphical User Interface. If you can type fast, you can work even faster with Event Manager to manage the various events in your RC! All you need is a laptop/computer to run this application. Interested? Jump to the <> to get started. Enjoy! == Quick Start . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `EventManager.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your EventManager. . Double-click the file to start the app. The GUI should appear in a few seconds. + image::Ui.png[width="790"] @@ -33,9 +33,9 @@ image::Ui.png[width="790"] e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`list`* : lists all events +* **`add`**`n/Computer Science Workshop c/John Doe p/98765432 e/johnd@example.com v/John street, block 123, #01-01 d/12/03/2018 15:30` : adds an event named `Computer Science Workshop` to the Event Manager. +* **`delete`**`3` : deletes the 3rd event shown in the current list * *`exit`* : exits the app . Refer to <> for details of each command. @@ -43,85 +43,173 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. [[Features]] == Features +// tag::autosort[] + +==== +*Automatic sorting*: Events in Event Manager will always be displayed in chronological order, followed by alphabetical order. +==== +// end::autosort[] + ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/EVENT`, `EVENT` is a parameter which can be used as `add n/Computer Science Workshop`. +* Items in square brackets are optional e.g `n/Comp Sci Workshop [t/TAG]` can be used as `n/Comp Sci Workshop t/sport` or as `n/Comp Sci Workshop`. * Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Parameters can be in any order e.g. if the command specifies `n/EVENT p/PHONE_NUMBER`, `p/PHONE_NUMBER n/EVENT` is also acceptable. ==== +IMPORTANT: Only `select`, `list` and `update` commands can be performed without logging in. Also, `add`, `edit`, `delete` and `deleteComment` can only performed by the admin account. + +IMPORTANT: As of now, there is only one admin account, with the username being `admin` and the password being `root`. + +// tag::authentication[] +=== Signing up for an account : `signup` + +User: Creates a *user* account + +Format: `signup u/USERNAME p/PASSWORD` + + +|=== +|Examples: + +* `signup u/Gerald Chua p/password12345` + +Creates account with the username Gerald Chua + + +* `signup u/James Yaputra p/drowssap12345` + +Creates account with the username James Yaputra +|=== + +=== Logging into an account : `login` + +User: Logs in to account + +Format: `login u/USERNAME p/PASSWORD` + + +|=== +|Examples: + +* `login u/Gerald Chua p/password12345` + +Logs in Gerald Chua + + +* `login u/James Yaputra p/drowssap12345` + +Logs in James Yaputra +|=== + +=== Logging out of an account : `logout` + +User: Logs out of an account + +Format: `logout` + + +|=== +|Examples: + +* `login u/Gerald Chua p/password12345` + +Logs in Gerald Chua + + +* `logout` + +Logs out from Gerald Chua +|=== +// end::authentication[] + === Viewing help : `help` +Admin: Shows Admin commands + +User: Shows User commands + Format: `help` -=== Adding a person: `add` +=== Adding a event: `add` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Admin: Adds an event to the Event Manager + +Adds an event to the Event Manager + +Format: `add n/EVENT_NAME c/CONTACT_NAME p/PHONE_NUMBER e/EMAIL v/VENUE d/DATETIME [t/TAG]...` [TIP] -A person can have any number of tags (including 0) +An event can have any number of tags (including 0). Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `add n/Com Sci Workshop c/John Doe p/98765432 e/johnd@example.com v/John street, block 123, #01-01 d/20/10/2017 10:30` +* `add n/Sports Day c/Betsy Crow t/Sports e/betsycrow@example.com v/COM2 #02-01 p/12345678 d/21/02/2019 08:30 t/Leisure` -=== Listing all persons : `list` +=== Listing all events : `list` -Shows a list of all persons in the address book. + +User/Admin: Lists all events in the Event Manager in chronological order. + Format: `list` -=== Editing a person : `edit` +=== Editing a event : `edit` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Admin: Edits an existing event in the Event Manager. + +Format: `edit INDEX [n/EVENT] [c/CONTACT NAME] [p/PHONE] [e/EMAIL] [v/VENUE] [d/DATETIME] [t/TAG]...` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... +* Edits the event at the specified `INDEX`. The index refers to the index number shown in the displayed event 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. +* When editing tags, the existing tags of the event will be removed i.e adding of tags is not cumulative. +* You can remove all the event's tags by typing `t/` without specifying any tags after it. +* If you want to reset the comments section you can input `C/{span}Comment Section{/span}{ol}{/ol}` as a field. **** 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. +Edits the phone number and email address of the 1st event to be `91234567` and `johndoe@example.com` respectively. +* `edit 2 n/Sports Meet t/` + +Edits the name of the 2nd event to be `Sports Day` and clears all existing tags. + +// tag::find[] +=== Locating events: `find` -=== Locating persons by name: `find` +Admin/User: Finds events whose field contains any of the given keywords specified by the user. + +Format: `find PREFIX/KEYWORD [MORE_PREFIX][MORE_KEYWORDS]` + -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Available prefixes: + +* k/ Default search option +* n/ Name search +* c/ Contact search +* e/ Email search +* p/ Phone search +* v/ Venue search +* d/ Datetime search +* t/ Tag search +* a/ Attendee search **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* 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` +* The search is case insensitive. e.g `sports` will match `Sports`. +* The order of the keywords does not matter. e.g. `Sports Day` will match `Day Sports`. +* Only full words will be matched e.g. `Sport` will not match `Sports`. +* Events matching at least one keyword will be returned (i.e. `OR` search). e.g. `Sports Day` will return `Sports games`, `Good Day`. +* Command default prefix (k/) will allow searches in any field of an event, while other prefixes will result in events that must have that keyword in the indicated field. +* At least one of the available prefixes must be presented. +* When using different prefixes together, _logic AND operator_ is used. This means that if there is one or more prefixes in the combination that does not have any keywords following it, search results will always be empty. **** +[NOTE] +1. If there are more than 1 prefixes of the same type, for example, `find n/new n/dark n/meeting`, they will be automatically combined together, which means this command will be assumed to be the same as `find n/new dark meeting` + +2. If there is only the prefix without any keywords following it, no events will be found as there are no events with the required fields left empty + +3. Unknown prefixes will be ignored. + Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `find n/Day` + +Returns events with names `Sports Day` and `Any day` +* `find k/Sports Sci friends` + +Returns events having the name `Sports Competition`, venue `Sci Avenue`, and tag `friends` +* `find d/12/01/2018 04:30` + +Returns any event having date `12/01/2018` or time `04:30` +* `find k/Day n/Sports d/12/01/2018` + +Returns any event having keyword `day` with names including `Sports` and with dates `12/01/2018` +// end::find[] -=== Deleting a person : `delete` +=== Deleting an event : `delete` -Deletes the specified person from the address book. + +Admin: Deletes the specified event from the Event Manager. + Format: `delete INDEX` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Deletes the event at the specified `INDEX`. +* The index refers to the index number shown in the displayed event list. * The index *must be a positive integer* 1, 2, 3, ... **** @@ -129,19 +217,19 @@ Examples: * `list` + `delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + +Deletes the 2nd event in the Event Manager. +* `find Sports` + `delete 1` + -Deletes the 1st person in the results of the `find` command. +Deletes the 1st event in the results of the `find` command. -=== Selecting a person : `select` +=== Selecting an event : `select` -Selects the person identified by the index number used in the displayed person list. + +Admin/User: Selects the event identified by the index number in the displayed event list. + Format: `select INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Selects the event and loads the event page of the event at the specified `INDEX`. +* The index refers to the index number shown in the displayed event list. * The index *must be a positive integer* `1, 2, 3, ...` **** @@ -149,14 +237,14 @@ Examples: * `list` + `select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + +Selects the 2nd event in the Event Manager. +* `find Sports` + `select 1` + -Selects the 1st person in the results of the `find` command. +Selects the 1st event in the results of the `find` command. === Listing entered commands : `history` -Lists all the commands that you have entered in reverse chronological order. + +Admin/User: Lists all the commands that you have entered in reverse chronological order. + Format: `history` [NOTE] @@ -172,7 +260,7 @@ Format: `undo` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit`, `clear`, `register`, `addComment` and `replyComment`). ==== Examples: @@ -216,41 +304,246 @@ The `redo` command fails as there are no `undo` commands executed previously. === Clearing all entries : `clear` -Clears all entries from the address book. + +Admin: Clears all entries from the Event Manager. + Format: `clear` +// tag::rsvp[] +=== Register for event : `register` + +User: Registers for an event by adding the current user's username to the attendance list. + +Format: `register INDEX` + +**** +* Registers for the event at the specified INDEX. +* The index refers to the index number shown in the displayed event list. +* The index *must be a positive integer* `1, 2, 3, ...` +* User must not be registered for event. +**** + +Examples: + +* `list` + +`register 3` + +Registers user for the 3rd event of the Event Manager. +* `find Sports` + +`register 1` + +Registers User for the 1st event in the results of the `find` command. + +.Expected outcome after user Peter Parker registers for the event at index 5 (annotations in red). +image::registerUG.png[width="800"] + +=== Remove registration from event : `unregister` + +User: Unregisters for an event by removing the current user's username from the attendance list. + +Format: `unregister INDEX` + +**** +* Unegisters for the event at the specified INDEX. +* The index refers to the index number shown in the displayed event list. +* The index *must be a positive integer* `1, 2, 3, ...` +* User must be already registered for event. +**** + +Examples: + +* `list` + +`unregister 1` + +Unregisters user from the 1st event of the Event Manager. +* `find Sports` + +`unregister 2` + +Unregisters user from the 2nd event in the results of the `find` command. + +=== List all registered events : `attending` + +User: Lists all events that the user has registered for. + +Format: `attending` + +.Events that user Peter Parker is registered to are displayed, similar to find operation (annotations in red). +image::attendingUG.png[width="700"] + +=== Remove user from event : `removeAttendee` + +Admin: Removes a user registered for an event. + +Format: `removeAttendee INDEX u/USERNAME` + +**** +* Removes USERNAME from the event at the specified INDEX. +* The index refers to the index number shown in the displayed event list. +* The index *must be a positive integer* `1, 2, 3, ...` +* User with the specified username must be registered for the event. +**** + +Examples: + +* `list` + +`removeAttendee 1 u/Peter Parker` + +Removes the user with username `Peter Parker` from the attendance of the 1st event of the Event Manager. +* `find Party` + +`removeAttendee 2 u/Alice` + +Removes the user with username `Alice` from the attendance of the 2nd event in the results of the `find` command. +// end::rsvp[] + +// tag::eventReminders[] +=== Show event reminders : `reminder` + +Admin/User: Shows event reminders for all upcoming events that you have registered for. + +Format: `reminder` + +NOTE: Upcoming events are those that are occurring within the next 24 hours. + +**** +* You will only receive reminders for events that they have *registered* for. +* You have to be *logged-in* to receive reminders. +* Reminders are *automatically* sent out every 6 hours, unless you initiate the command. +* Close the event alert dialog using your mouse or by hitting 'enter' on your keyboard. +**** +// end::eventReminders[] + +// tag::eventStatus[] +=== Updates the statuses of events : `update` + +Admin/User: Updates the statuses of events. + +Format: `update` + +**** +* Events that have already occurred will have the [COMPLETED] status. +* Events that have yet to occur will have the [UPCOMING] status. +* The `update` command can be used *without* you having to log in. +* Event statuses are automatically updated every 5 minutes. +**** + +.A 'Completed' Status +image::Completed.png[width="100"] +.A 'Upcoming' Status +image::Upcoming.png[width="100"] + +TIP: You may experience some disruptions to the UI during the automatic update due to high load, especially if your computer is using an older processor. Give it a few seconds to load and if the problem still persists, use the `update` command. +// end::eventStatus[] + + +//tag::comment[] +=== Add a comment : `addComment` + +Admin/User: Adds a comment into the specified event's comment section, with the username preceding the comment (when you are logged in). + +Format: `addComment INDEX C/STRING` + +**** +* Adds a comment at the specified `INDEX`. The index refers to the index number shown in the displayed event list. The index *must be a positive integer* 1, 2, 3, ... +* For `C/STRING`, `STRING` cannot be empty. `STRING` is the comment. +* Undo and Redo command works with this! However, every time you undo or redo, the Browser Panel will not refresh itself + so you need to refresh it yourself. +* If you want to revert a comment previously made, use `undo`! +**** + +Examples: + +* `addComment 1 C/What is the attire to wear for the event?` + +Adds "What is the attire to wear for the event?" into the comment section of the 1st event, preceded by the user's username. +* `addComment 5 C/What is the attire to wear for this event?` + +Adds "What is the attire to wear for this event?" into the comment section of the 5th event, preceded by the user's username. + +.Adding a comment +image::addComment.png[width="790"] + +=== Reply to a comment : `replyComment` + +Admin/User: Reply to a comment with the username preceding the comment (when you are logged in). + +Format: `replyComment INDEX L/LINE C/STRING` + +**** +* Replies to a comment of the event at the specified `INDEX`. The index refers to the index number shown in the displayed event list. The index *must be a positive integer* 1, 2, 3, ... +* For `C/STRING`, `STRING` cannot be empty. `STRING` is the comment. +* Line has to be a non-zero integer and has to exist within the comment section. The numbers below the "Comment Section" in the BrowerPanel are the line parameters to index every comment. The comment will be replied under the line index given. +* Undo and redo works! However, every time you undo or redo, the Browser Panel will not refresh itself + so you need to refresh it yourself. +* If you want to revert a comment previously made, use `undo`! +**** + +Examples: + +* `replyComment 1 L/5 C/What is the attire to wear for the event?` + +Adds the comment "What is the attire to wear for the event?" to line 6 of the comment section of the 1st event, preceded by the user's username. +* `replyComment 2 L/2 C/What is the attire to wear for the event?` + +Adds the comment "What is the attire to wear for the event?" into line 3 of the comment section of the 2nd event, preceded by the user's username. + +.Replying a comment +image::replyComment.png[width="790"] + +=== Delete a comment : `deleteComment` + +Admin: Deletes a comment (when you are logged in as admin). + +Format: `deleteComment INDEX L/LINE` + +**** +* Edits the event at the specified `INDEX`. The index refers to the index number shown in the displayed event list. The index *must be a positive integer* 1, 2, 3, ... +* Line has to be a non-zero integer and has to exist within the comment section. The numbers below the "Comment Section" in the Brower Panel are the line parameters to index each comment. The line parameter provided will delete the corresponding comment index. +* Undo and Redo works! However, every time you undo or redo, the BrowserPanel will not refresh itself so you need to refresh it yourself. +**** + +Examples: + +* `deleteComment 1 L/5` + +Deletes the comment at line 5 of the 1st event. +* `deleteComment 2 L/2` + +Deletes the comment at line 2 of the 2nd event. + +.Deleting a comment +image::deleteComment.png[width="790"] +//end::comment[] + +// tag::exportcalendar[] +=== Exports calendar: `export` + +Admin/User: Exports current registered/hosted events of the user/admin to an iCalendar file to use with other calendar application + +Format: `export FILENAME` + +Examples: + +* `export myCalendar` + +Exports the iCalendar file with name 'myCalendar' to *folder that you store EventManager* + +[NOTE] +==== +File name should not be longer than 500 characters and can not be empty. +==== +// end::exportcalendar[] + === Exiting the program : `exit` -Exits the program. + +Admin/User: Exits the program. + Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +Event Manager's data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. // tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +//=== Encrypting data files `[coming in v2.0]` -_{explain how the user can enable/disable data encryption}_ +//_{explain how the user can enable/disable data encryption}_ // end::dataencryption[] == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Event Manager folder. == Command Summary -* *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` +* *Sign up* : `signup n/USERNAME p/PASSWORD c/CONFIRM_PASSWORD` + +e.g. `signup n/Gerald Chua p/password12345 c/password12345` +* *Login* : `login n/USERNAME p/PASSWORD` + +e.g. `login n/Gerald Chua p/password12345` +* *Add* `add n/EVENT_NAME c/CONTACT_NAME p/PHONE_NUMBER e/EMAIL v/VENUE d/DATETIME [t/TAG]...` + +e.g. `add n/Sports Day c/James Ho p/22224444 e/jamesho@example.com v/123, Clementi Rd, 1234665 d/12/08/2018 08:30 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` +* *Edit* : `edit INDEX [n/EVENT] [p/PHONE_NUMBER] [e/EMAIL] [v/VENUE] [d/DATETIME] [t/TAG]...` + +e.g. `edit 2 n/Good Day e/jameslee@example.com` +* *Find* : `find PREFIX/KEYWORD [MORE_PREFIX][MORE_KEYWORDS]` + +e.g. `find k/Comp Sci n/Workshop` * *List* : `list` * *Help* : `help` * *Select* : `select INDEX` + @@ -258,3 +551,20 @@ e.g.`select 2` * *History* : `history` * *Undo* : `undo` * *Redo* : `redo` +* *Register* : `register INDEX` + +e.g. `register 1` +* *Unregister* : `unregister INDEX` + +e.g. `unregister 1` +* *View Attending* : `attending` +* *Remove Attendee* : `removeAttendee INDEX u/USERNAME` + +e.g. `removeAttendee 1 u/Peter Parker` +* *Show reminders* : `reminder` +* *Update event statuses* : `update` +* *AddComment* : `addComment INDEX C/STRING` + +e.g. `addComment 1 C/HELLO` +* *ReplyComment* : `replyComment INDEX L/LINE C/STRING` + +e.g. `replyComment 1 L/2 C/Hello` +* *DeleteComment* : `deleteComment INDEX L/LINE` + +e.g. `deleteComment 1 L/2` +* *ExportCalendarCommand* : `export FILENAME` + +e.g. `export BobCalendar` diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..e59357c7d1cd 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..be147dde7254 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..7fda387e4788 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx index d0561dfd305a..0e6b204dfb62 100644 Binary files a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx and b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..ecffb80e26f4 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..1e668ac89933 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..b8276ee422ed 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoActivityDiagram.pptx b/docs/diagrams/UndoRedoActivityDiagram.pptx index 16fec930cf3f..b1de5b017d0f 100644 Binary files a/docs/diagrams/UndoRedoActivityDiagram.pptx and b/docs/diagrams/UndoRedoActivityDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx index 6fd31b5f3fbd..e35d8f613289 100644 Binary files a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx and b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx index 1f3261976dce..d119121223f2 100644 Binary files a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx index e2907d4a9cae..ebe7c5afbe80 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx index 4ecc659bd600..b747110af7b8 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx index 16ebf585ddbd..ea9a08ca0dc5 100644 Binary files a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..c9e442207593 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx index 98ce067642ff..285492cabd47 100644 Binary files a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx and b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx differ diff --git a/docs/images/Completed.png b/docs/images/Completed.png new file mode 100644 index 000000000000..d2b77411fbea Binary files /dev/null and b/docs/images/Completed.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png index 0462b9b7be6e..0d8b401d6f6b 100644 Binary files a/docs/images/DeletePersonSdForLogic.png and b/docs/images/DeletePersonSdForLogic.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..320e034a42b1 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LoginDiagram.png b/docs/images/LoginDiagram.png new file mode 100644 index 000000000000..21ded3001823 Binary files /dev/null and b/docs/images/LoginDiagram.png differ diff --git a/docs/images/ModelClassBetterOopDiagram.png b/docs/images/ModelClassBetterOopDiagram.png index 9ba8eb5e31d0..ebb27ec36916 100644 Binary files a/docs/images/ModelClassBetterOopDiagram.png and b/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..c5c74188d2bc 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/QK_photo.png b/docs/images/QK_photo.png new file mode 100644 index 000000000000..13f3e2006522 Binary files /dev/null and b/docs/images/QK_photo.png differ diff --git a/docs/images/Reminder-sequenceDiagram.png b/docs/images/Reminder-sequenceDiagram.png new file mode 100644 index 000000000000..c7e01e5afa22 Binary files /dev/null and b/docs/images/Reminder-sequenceDiagram.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png index 1e836f10dcd8..9d1b9a5e04e0 100644 Binary files a/docs/images/SDforDeletePerson.png and b/docs/images/SDforDeletePerson.png differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png index ecec0805d32c..dd686cd29087 100644 Binary files a/docs/images/SDforDeletePersonEventHandling.png and b/docs/images/SDforDeletePersonEventHandling.png differ diff --git a/docs/images/SignupDiagram.png b/docs/images/SignupDiagram.png new file mode 100644 index 000000000000..99f9ea592449 Binary files /dev/null and b/docs/images/SignupDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..f6f2f05d17c0 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 5ec9c527b49c..d96a9a45a5f1 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 369469ef176e..e3951dbff93a 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png index 55e4138cc64f..c03573a272d8 100644 Binary files a/docs/images/UndoRedoActivityDiagram.png and b/docs/images/UndoRedoActivityDiagram.png differ diff --git a/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/docs/images/UndoRedoExecuteUndoStateListDiagram.png index 29c365d6b4a1..9a4c2d16786d 100644 Binary files a/docs/images/UndoRedoExecuteUndoStateListDiagram.png and b/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand1StateListDiagram.png b/docs/images/UndoRedoNewCommand1StateListDiagram.png index 76e661d62027..8ce7c641e60f 100644 Binary files a/docs/images/UndoRedoNewCommand1StateListDiagram.png and b/docs/images/UndoRedoNewCommand1StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StateListDiagram.png b/docs/images/UndoRedoNewCommand2StateListDiagram.png index adcb9aeadc51..34934eced11e 100644 Binary files a/docs/images/UndoRedoNewCommand2StateListDiagram.png and b/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StateListDiagram.png b/docs/images/UndoRedoNewCommand3StateListDiagram.png index aac9c5fe05db..92f084d16470 100644 Binary files a/docs/images/UndoRedoNewCommand3StateListDiagram.png and b/docs/images/UndoRedoNewCommand3StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand4StateListDiagram.png b/docs/images/UndoRedoNewCommand4StateListDiagram.png index 66a0a3b5f323..1e63adcba8a3 100644 Binary files a/docs/images/UndoRedoNewCommand4StateListDiagram.png and b/docs/images/UndoRedoNewCommand4StateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..37e609a65f32 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/UndoRedoStartingStateListDiagram.png b/docs/images/UndoRedoStartingStateListDiagram.png index 002f3e2bbf79..b858990ff5c4 100644 Binary files a/docs/images/UndoRedoStartingStateListDiagram.png and b/docs/images/UndoRedoStartingStateListDiagram.png differ diff --git a/docs/images/Upcoming.png b/docs/images/Upcoming.png new file mode 100644 index 000000000000..acc401f804c8 Binary files /dev/null and b/docs/images/Upcoming.png differ diff --git a/docs/images/Update-sequenceDiagram.png b/docs/images/Update-sequenceDiagram.png new file mode 100644 index 000000000000..0febe73d5be5 Binary files /dev/null and b/docs/images/Update-sequenceDiagram.png differ diff --git a/docs/images/addComment.png b/docs/images/addComment.png new file mode 100644 index 000000000000..ec98facf54da Binary files /dev/null and b/docs/images/addComment.png differ diff --git a/docs/images/addCommentSequenceDiagram.png b/docs/images/addCommentSequenceDiagram.png new file mode 100644 index 000000000000..f9e6fe3da980 Binary files /dev/null and b/docs/images/addCommentSequenceDiagram.png differ diff --git a/docs/images/appveyor/add-project-1.png b/docs/images/appveyor/add-project-1.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/add-project-2.png b/docs/images/appveyor/add-project-2.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/add-project-3.png b/docs/images/appveyor/add-project-3.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/login.png b/docs/images/appveyor/login.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/project-settings-1.png b/docs/images/appveyor/project-settings-1.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/project-settings-2.png b/docs/images/appveyor/project-settings-2.png old mode 100755 new mode 100644 diff --git a/docs/images/appveyor/project-settings-3.png b/docs/images/appveyor/project-settings-3.png old mode 100755 new mode 100644 diff --git a/docs/images/attendingUG.png b/docs/images/attendingUG.png new file mode 100644 index 000000000000..f723ca9c8e4f Binary files /dev/null and b/docs/images/attendingUG.png differ diff --git a/docs/images/chiaxr.png b/docs/images/chiaxr.png new file mode 100644 index 000000000000..7618bf92d05d Binary files /dev/null and b/docs/images/chiaxr.png differ diff --git a/docs/images/cqinkai.png b/docs/images/cqinkai.png new file mode 100644 index 000000000000..13f3e2006522 Binary files /dev/null and b/docs/images/cqinkai.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/deleteComment.png b/docs/images/deleteComment.png new file mode 100644 index 000000000000..88e83977fdd4 Binary files /dev/null and b/docs/images/deleteComment.png differ diff --git a/docs/images/geraldcdx.png b/docs/images/geraldcdx.png new file mode 100644 index 000000000000..7c5a8c9c3943 Binary files /dev/null and b/docs/images/geraldcdx.png differ diff --git a/docs/images/jamesyaputra.png b/docs/images/jamesyaputra.png new file mode 100644 index 000000000000..d18a89aa4b91 Binary files /dev/null and b/docs/images/jamesyaputra.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/myphoto.jpg b/docs/images/myphoto.jpg new file mode 100644 index 000000000000..3fc09b2affa9 Binary files /dev/null and b/docs/images/myphoto.jpg differ diff --git a/docs/images/registerSD.png b/docs/images/registerSD.png new file mode 100644 index 000000000000..7314c56ee81f Binary files /dev/null and b/docs/images/registerSD.png differ diff --git a/docs/images/registerUG.png b/docs/images/registerUG.png new file mode 100644 index 000000000000..19594615919b Binary files /dev/null and b/docs/images/registerUG.png differ diff --git a/docs/images/removeAttendeeSD.png b/docs/images/removeAttendeeSD.png new file mode 100644 index 000000000000..a58b32f91ba1 Binary files /dev/null and b/docs/images/removeAttendeeSD.png differ diff --git a/docs/images/replyComment.png b/docs/images/replyComment.png new file mode 100644 index 000000000000..5d0efaf07b54 Binary files /dev/null and b/docs/images/replyComment.png differ diff --git a/docs/images/tertium3.png b/docs/images/tertium3.png new file mode 100644 index 000000000000..a25cebd01c3b Binary files /dev/null and b/docs/images/tertium3.png differ diff --git a/docs/images/xr_photo.jpg b/docs/images/xr_photo.jpg new file mode 100644 index 000000000000..d7f045c8e4a3 Binary files /dev/null and b/docs/images/xr_photo.jpg differ diff --git a/docs/team/chiaxr.adoc b/docs/team/chiaxr.adoc new file mode 100644 index 000000000000..e88aaa90ed2f --- /dev/null +++ b/docs/team/chiaxr.adoc @@ -0,0 +1,84 @@ += Chia Xiang Rong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:site-section: chiaxr +:toc: +:toc-title: +:toc-placement: preamble +:sectnums: +:xrefstyle: full +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:warning-caption: :warning: +endif::[] +:repoURL: https://github.com/CS2113-AY1819S1-T12-1/main + +[big]#PROJECT: Event Manager# + +== Overview +This document provides a summary of my contributions as a developer of the Event Manager project. It showcases both my proficiency in software engineering and clarity in technical writing. + +Event Manager is a cross-platform desktop application that manages events, targeted at the Halls and Residential Colleges of NUS. These residences tend to host many events for their students, and it can be difficult to keep track of events amidst the students' daily lives. Our application aims to consolidate these events and present them in a clear manner, allowing the residents to easily sieve through upcoming events that they are interested in. + +Other main features include: + +* Authentication +* Registration +* Reminders +* Comments +* Searching & Sorting +* Export of calendar file + + +The user interacts with the application using a Command Line Interface (CLI), and it has a Graphical User Interface (GUI) created with JavaFX. All features can be used by typing commands in the CLI, but the GUI provides important information as well as some ease of use to the user. It is written in Java, and has about 10 kLoC. + +== Summary of contributions +My contributions to the project are described below. + +* *Major enhancement*: Added *the ability to register/unregister for events and view attending events* +** *What it does*: ++ +Users can indicate their attendance for events, and view the usernames of other attendees. Events that the user are attending can be filtered out and displayed. + +** *Justification*: ++ +This feature encourages users to attend events and connect with other users they know who are attending the same event, promoting social engagement in the community. It also provides event coordinators with a rough estimate of the turnout, allowing them to plan accordingly and make suitable preparations. + +** *Highlights*: ++ +This enhancement required an in-depth analysis of design alternatives. The implementation too was moderately difficult as it required modification to the existing storage format. + +* *Minor enhancement*: Formatting and display of event details in event page + +* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.3` - `v1.4` (2 releases) on GitHub +*** Wrote additional integration tests for registration features to increase coverage (Pull request https://github.com/CS2113-AY1819S1-T12-1/main/pull/99[#99]) +** Documentation: +*** Updated User and Developer Guides to describe new features: https://github.com/CS2113-AY1819S1-T12-1/main/pull/81[#81] +** Community: +*** PRs reviewed: https://github.com/CS2113-AY1819S1-T12-1/main/pull/22[#22], https://github.com/CS2113-AY1819S1-T12-1/main/pull/58[#58], https://github.com/CS2113-AY1819S1-T12-1/main/pull/59[#59] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=rsvp] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=rsvp] +include::../DeveloperGuide.adoc[tag=rsvpUsecase] + diff --git a/docs/team/cqinkai.adoc b/docs/team/cqinkai.adoc new file mode 100644 index 000000000000..f089544dceb3 --- /dev/null +++ b/docs/team/cqinkai.adoc @@ -0,0 +1,86 @@ += Qin Kai - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +This portfolio summarizes my contributions to the https://github.com/CS2113-AY1819S1-T12-1/main/releases/download/v1.3/eventmanager.jar[Event Manager] Application, whereby our team of 5 (http://github.com/chiaxr[Xiang Rong], https://github.com/Geraldcdx[Gerald], http://github.com/jamesyaputra[James], http://github.com/tertium3[Long], http://github.com/cqinkai[Qin Kai]) worked on it as part of our module on software engineering, over a period of 8 weeks. I was the developer in charge of the Event Status feature and the Event Reminder feature in Event Manager. + +== PROJECT: Event Manager + +Event Manager is an enhanced version of the https://github.com/se-edu/addressbook-level4/releases/download/v0.8/addressbook.jar[Address Book] Application +--- + +== Overview + +Event Manager is a desktop application for students living in Residential Colleges to manage all the activities happening throughout the college. The user interacts with it using a Command Line Interface (CLI), and it has a GUI created with JavaFX. It is written in Java, and has about 10k lines of code (LoC). + +The following is a list of the main features in Event Manager: +==== +. *Authentication* -- Admin accounts to manage events and user accounts to identify users. +. *Find* -- A search feature to filter events based on keywords in the events' data fields. +. *Registration* -- Users can indicate their attendance for events. Attendance is displayed to all other users. +. *Reminder* -- Automated reminders are sent to users who have registered for events that are happening in the next 24 hours. +. *Comment* -- A comment section to facilitate interactions amongst the users for an event. +. *Status* -- Displays the status of an event to users. Statuses can be 'Completed' or 'Upcoming'. +==== + +== Summary of contributions +* *Major enhancement #1*: + +** added *a new self-updating status field for events* +** What it does: allows the user to easily discern completed events from upcoming ones. Completed events are events that have taken place while the upcoming events are those that have yet to occur. +** Justification: This feature is important and enhances the product because a user would have to strain his/her eyes to look through each event's details to ascertain which events have occurred and which ones are coming up. +** Highlights: This enhancement changes the current model and increases the list of commands available to users. It required an in-depth analysis of design alternatives. + +* *Major enhancement #2*: + +** added *a reminder mechanism for user-registered events* +** What it does: Sends a reminder to users for any upcoming events (those happening in the next 24 hours) that they have registered for. +** Justification: This +** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + +* *Minor enhancement*: added new information fields for the events. +** a contact field for the name of the person in-charge of the event. (https://github.com/CS2113-AY1819S1-T12-1/main/pull/14[#14]) +** a comment field for the comment section. (https://github.com/CS2113-AY1819S1-T12-1/main/pull/91[#91]) +** an attendee field for the event's attendees. (https://github.com/CS2113-AY1819S1-T12-1/main/pull/58[#58]) + +* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ + +* *Other contributions*: + +** Project management: +*** Set up milestones and issue labels for the project on GitHub +** Enhancements to existing features: +*** Morphed the address field into a venue field (https://github.com/CS2113-AY1819S1-T12-1/main/pull/53[#53]) +*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14]) +** Community: +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2113-AY1819S1-T09-4/main/issues/132[1], https://github.com/CS2113-AY1819S1-T09-4/main/issues/135[2], https://github.com/CS2113-AY1819S1-T09-4/main/issues/146[3]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=eventReminders] + +include::../UserGuide.adoc[tag=eventStatus] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=eventStatus&Reminder] + +include::../DeveloperGuide.adoc[tag=reminderUsecase] + +include::../DeveloperGuide.adoc[tag=updateStatusUsecase] + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/geraldcdx.adoc b/docs/team/geraldcdx.adoc new file mode 100644 index 000000000000..b9b570e1b6ac --- /dev/null +++ b/docs/team/geraldcdx.adoc @@ -0,0 +1,146 @@ += Gerald Chua Deng Xiang - Project Portfolio +--- +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:site-section: Geraldcdx +:toc: +:toc-title: +:toc-placement: preamble +:sectnums: +:xrefstyle: full +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:warning-caption: :warning: +endif::[] +:repoURL: https://github.com/CS2113-AY1819S1-T12-1/main +--- + +[big RED]#PROJECT: Event Manager# + +== Overview + +[.result] + +[loweralpha] +. *Introduction* + +This portfolio is a record of my contribution towards this project. It highlights the developed features and it’s detailed implementation to show technical competence in software engineering, and soft skills such as stellar writing for documentation. This will be further elaborated in the “Summary of Contributions”, “Contributions to User Guide” and “Contributions to Developer Guide” below. + +. *Product Summary* + +Event Manager (EM) is a desktop application that a Residential College (RC) in National University of Singapore (NUS) can use to manage events in RCs. Using the Command Line Interface (CLI), the Head of Events of an RC can promote events effectively to RC’s residents. The detailed features will be listed in the “List of Main Features”. + +. *Problem Statement* + +Currently, events in RCs are promoted through multiple groups in Whatsapp or Telegram and this is an inefficient process which wastes time and effort for residents and event organisers. + +. *Our Solution* + +My team developed the EM so that residents can have a centralised event platform to host a concise list of detailed events for RC residents to RSVP and be reminded when the event is drawing closer. Further, this application will better allow the organisers to prepare for the event. + +. *List of Main Features* + +[cols="1,2,3000000", options="header"] +|============================ +|no|Feature|Description +|1 +|Authentication +|The Head of Events can get requests for residents to add events as an Admin. + Whereas, residents can sign up as Users and browse through events. + +|2 +|Find +|Both Admin and Users can search for events based on keywords. E.g. If the user wants to find a sporting event, he can type find sports and the relevant events will be displayed. + +|3 +|RSVP +|Both Admin and Users can indicate their attendance for events. + +|4 +|Reminders +|Both Admin and Users can set reminders, and these reminders will alert them when the event is drawing near. + +|5 +|Comment Section +|Both Admin and Users have a comments section for each event. Users can add a comment or reply to comments, whereas, Admin has all these functionalities and able to delete inappropriate comments. + + +|6 +| CRUD of Events +|The Admin can create, reply, update and delete(CRUD) events through relevant commands. + +|7 +|Undo and Redo +|Both Admin and Users can undo or redo any command that was committed previously. + +|8 +|Sorting +|Events are sorted autonomously based on date and time; Events that are commencing earlier will be at the top of the event list. + +|9 +|Export +|Admin and users can export the list of events as a calendar to their local directory +|============================ + +== Summary of contributions + +[loweralpha] +. *Major enhancement*: A comment section for users to interact and clarify doubts on the events +[lowerroman] +.. *Functionality* + + Admin can add, reply, delete and reset comments. Whereas, Users can only add and reply comments. The relevant commands can be typed out in the CLI to achieve the previous statement. +.. *Justification* + +This functionality supports the additional challenged posed in CS2101 such that there is social responsiveness in the project. The interaction through the comments section will allow residents to clarify any doubts with the organisers. Additionally, the organisers can use the comments section as a feedback platform to get feedback on improvements. +.. *Highlights* + +This enhancement is tedious as it requires a new field to display the comments in a relevant format and onto the User Interface (UI). Many test cases written by the developers had to be edited to ensure that the testability of the project remained. +.. *Credits* + + Jsoup v1.11.3 library was used to manipulate Hyper Text Markup Language (HTML) tags so that we did not need to rewrite code but instead reuse efficient code. Furthermore, online user forums such as Stack Overflow was used to clarify doubts or find our develop code. +. *Minor enhancement* + +[lowerroman] +.. `AddCommand` had to be revised to intialise a comment section +.. `EditCommand` was edited to accomodate a new field for the comments section and the admin would be able to reset the comment section if there is a need. +.. Added comments into the `BrowserPanel` to be displayed in tht GUI. + +. *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=gerald&sort=displayName&since=2018-09-12&until=2018-11-05&timeframe=day&reverse=false&repoSort=true[Click Here]] [https://github.com/CS2113-AY1819S1-T12-1/main/pulls?q=is%3Apr+author%3Ageraldcdx[Pull Requests made]] + +. *Other contributions*: + +** Project management: +*** Scheduling regular meetings for CS2113T and CS2101 +*** Constantly ensure that our team was fulfilled milestones +*** Offered help to team members +** Enhancements to existing features: +*** Added code to `EditCommand` to take in another parameter for comments +*** Adding test cases to improve coverage by 6.06% [https://github.com/CS2113-AY1819S1-T12-1/main/pull/154[Pull Request #154]][https://github.com/CS2113-AY1819S1-T12-1/main/pull/156[Pull Request #156]][https://github.com/CS2113-AY1819S1-T12-1/main/pull/157[Pull Request #157]] +** Documentation: +*** Ported User Guide from team's Google docs to the repository's User Guide and making tweaks to the User guide. +*** Ported a huge section of Developer Guide from team's Google docs to the repository's Developer guide. +*** Monitored User Guide and Developer Guide submissions. + +** Community: +*** PRs reviewed by Geraldcdx [https://github.com/CS2113-AY1819S1-T12-1/main/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3AGeraldcdx[Github closed PRs]] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2113-AY1819S1-F10-2/main/issues/101[1], https://github.com/CS2113-AY1819S1-F10-2/main/issues/100[2], https://github.com/CS2113-AY1819S1-F10-2/main/issues/98[3], https://github.com/CS2113-AY1819S1-F10-2/main/issues/106[4]) +** Tools: +*** Integrated Reposense into the project + +== Contributions to the User Guide +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +include::../UserGuide.adoc[tag=comment] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=comments] + +== Other Projects +--- +[quanda] + +. [https://github.com/Geraldcdx/IVLEDownloader[Offline IVLE]] - This is an application that will be used by National University of Singapore(NUS) students to access files from the Integrated Virtual Learning Environment(IVLE) offline. +. [https://github.com/Geraldcdx/HTMLChatbot[HTML ChatBot]] - A Fullstack Webapp hosted on Google to run code for a self-sustainable attendance taking chatbot. + + + diff --git a/docs/team/jamesyaputra.adoc b/docs/team/jamesyaputra.adoc new file mode 100644 index 000000000000..db0ac609f068 --- /dev/null +++ b/docs/team/jamesyaputra.adoc @@ -0,0 +1,72 @@ += James Arista Yaputra - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:site-section: jamesyaputra +:toc: +:toc-title: +:toc-placement: preamble +:sectnums: +:xrefstyle: full +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:warning-caption: :warning: +endif::[] +:repoURL: https://github.com/CS2113-AY1819S1-T12-1/main + +[big RED]#PROJECT: Event Manager# + +== Overview + +Event Manager is a desktop application used for managing events in the Halls and Residential Colleges of NUS. Our application is primarily designed to aid residents in staying engaged and up-to-date with events happening within their community. + +The user interacts with the Event Manager using a Command Line Interface, alongside a Graphical User Interface written using JavaFX. It is written in Java, and has about 10 thousand lines of code. + +This document serves as a summary of my contributions to the Event Manager project and highlights both my technical skills in software engineering and my non-technical skills in writing documentation for a product. + +== Summary of contributions + +[loweralpha] +. *Major enhancement*: added *the ability to login/logout and signup for user accounts* +** What it does: allows the user log in and log out of their accounts and sign up for new ones. +** Justification: This feature allows the Event Manager to keep track of individual attendees of events. +** Highlights: This enhancement affects existing commands and commands to be added in the future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. +** Credits: GSON library by Google. +** Code contributed (https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=james&sort=displayName&since=2018-09-12&until=2018-11-05&timeframe=day&reverse=false&repoSort=true[Click here]) + + +. *Other contributions*: + +** Project management: +*** Managed releases `v1.3` - `v1.4` (2 releases) on GitHub +*** Wrote additional tests for authentication features to increase coverage by 2.3% (Pull request https://github.com/CS2113-AY1819S1-T12-1/main/pull/79[#79]) +** Documentation: +*** Updated User and Developer Guides to describe new features: https://github.com/CS2113-AY1819S1-T12-1/main/pull/81[#81] +** Community: +*** PRs reviewed: https://github.com/CS2113-AY1819S1-T12-1/main/pull/22[#22], https://github.com/CS2113-AY1819S1-T12-1/main/pull/58[#58], https://github.com/CS2113-AY1819S1-T12-1/main/pull/59[#59] +** Tools: +*** Integrated a third party library (Gson) to the project (https://github.com/CS2113-AY1819S1-T12-1/main/pull/39[#39]) +*** Integrated a new Github plugin (Coveralls) to the team repo + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=authentication] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=authentication] + +include::../DeveloperGuide.adoc[tag=authenticationTest] + +include::../DeveloperGuide.adoc[tag=authenticationUsecase] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/tertium3.adoc b/docs/team/tertium3.adoc new file mode 100644 index 000000000000..32d17dbf8df4 --- /dev/null +++ b/docs/team/tertium3.adoc @@ -0,0 +1,89 @@ += Ho Phi Long - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Event Manager + +--- + +== Overview + +Event Manager is a computer application developed base on Address Book - Level 4 and is used to manage events for Residence College(RC) Students of NUS. + + +This product prefers users to interact by Command Line Interface(CLI) with the help of Graphic User Interface(GUI) create by JavaFX. + + +Java is used for better compatibility with other platforms. + +* **Main features**: +** Authentication: Admin/User +** Basic operation: Create – Read – Edit – Delete (CRUD) +** Comment sections +** Event RSVP +** *Search features* +** Undo, redo commands +** Event reminder +** *Automatic sorting* +** *Export events as iCalendar file* + +== Role +I am in charge of develop *automatic sort*, *search* and *export calendar* features. + +== Summary of contributions +* *Major enhancement*: Add the ability to *export all registered events of the currently logging in user to an iCalendar file of user choice name* +** _What it does_: Allow users to take advantage of the available calendar applications to better manage their events. +** _Justification_: This feature can improve user management ability since there are many other tasks/events beside RC events that students have. +** _Highlights_: This enhancement follow command pattern that are currently applied in this project, which open for future expansion or improvement based on existing code. +** _Credits_: Import [http://ical4j.github.io/[ical4j]] version 3.0.0 to convert event and data to iCalendar format + +* *Major enhancement*: *New modified find command* to search by keywords in varieties of combination for better navigation. +** _What it does_:User are now allowed to search by any attributes that an event have and can include some must have keywords in certain data attributes instead of just name in the previous version. +** _Justification_: This enhancement significantly increases user navigation ability in the product compare to the original search by name +** _Highlights_: this enhancement extends previous find command structure to avoid affecting other components of this application + +* *Minor enhancement*: Add automatic sorting by event datetime, then by event name for current events that are saved in .xml file and displayed on GUI. + +* *Minor enhancement*: Add new attributes: datetime to the event + +* *Code contributed*: [https://nuscs2113-ay1819s1.github.io/dashboard/#=undefined&search=tertium3&sort=displayName&since=2018-09-12&until=2018-11-05&timeframe=day&reverse=false&repoSort=true[Click here]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.4` (1 release) on GitHub +** Enhancements to existing features: +*** Fix current tests that are affected by default sorting and new attributes +** Documentation: +*** Updated User Guide and Developer Guide to reflect latest stage of the product +** Community: +*** PRs reviewed (with non-trivial review comments): (Examples: https://github.com/CS2113-AY1819S1-T12-1/main/pull/79[#Pullrequest79]) +*** Contributed to forum discussions (examples: https://github.com/nusCS2113-AY1819S1/forum/issues/47[#CS2113 forum issue 47]) +*** Reported bugs and suggestions for other teams in the class +** Tools: +*** Integrated a third party library (http://ical4j.github.io/[ical4j]) to the project + + +== Contributions to the User Guide + +|=== +|_Below are all my contributions to different sections in product user guide. This demonstrate my abilities to communicate with end-users:_ +|=== + +include::../UserGuide.adoc[tag=autosort] + +include::../UserGuide.adoc[tag=find] + +include::../UserGuide.adoc[tag=exportcalendar] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=sorting] + +include::../DeveloperGuide.adoc[tag=findEnhancement] + +include::../DeveloperGuide.adoc[tag=exportcalendar] + diff --git a/docs/templates/LICENSE b/docs/templates/LICENSE index 2073b44dee64..df8e2929d9ac 100644 --- a/docs/templates/LICENSE +++ b/docs/templates/LICENSE @@ -5,11 +5,11 @@ MIT License Copyright (C) 2012-2018 Dan Allen, Ryan Waldron and the Asciidoctor Project -Permission is hereby granted, free of charge, to any person obtaining a copy +Permission is hereby granted, free of charge, to any event obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit events to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..c77a16aff571 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Optional; +import java.util.Timer; +import java.util.TimerTask; import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; @@ -15,23 +17,27 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.core.Version; import seedu.address.commons.events.ui.ExitAppRequestEvent; +import seedu.address.commons.events.ui.NewResultAvailableEvent; 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.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.EventManager; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEventManager; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.EventManagerStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlEManagerStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -40,7 +46,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +60,8 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + + logger.info("=============================[ Initializing EventManager ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +69,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + EventManagerStorage eventManagerStorage = new XmlEManagerStorage(userPrefs.getEventManagerFilePath()); + storage = new StorageManager(eventManagerStorage, userPrefsStorage); initLogging(config); @@ -74,28 +81,31 @@ public void init() throws Exception { ui = new UiManager(logic, config, userPrefs); initEventsCenter(); + + // Start event status update + initStatusUpdateAndReminders(); } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s event manager and {@code userPrefs}.
+ * The data from the sample event manager will be used instead if {@code storage}'s event manager is not found, + * or an empty event manager will be used instead if errors occur when reading {@code storage}'s event manager. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional eventManagerOptional; + ReadOnlyEventManager initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + eventManagerOptional = storage.readEventManager(); + if (!eventManagerOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample EventManager"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = eventManagerOptional.orElseGet(SampleDataUtil::getSampleEventManager); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty EventManager"); + initialData = new EventManager(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty EventManager"); + initialData = new EventManager(); } return new ModelManager(initialData, userPrefs); @@ -159,7 +169,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty EventManager"); initializedPrefs = new UserPrefs(); } @@ -177,15 +187,55 @@ private void initEventsCenter() { EventsCenter.getInstance().registerHandler(this); } + //@@author cqinkai + /** + * Starts performing the following commands for Events listed in event manager: + * 1. Status update by calling {@code UpdateStatusCommand} through {@code updateEventStatus} + * 2. Reminders for upcoming events by calling {@code ReminderCommand} through {@code checkEventReminders} + * Status update occurs every 300,000 milliseconds (5 minutes). + * Event reminders are sent every 36,000,000 milliseconds (6 hours). + */ + private void initStatusUpdateAndReminders() { + Timer timer = new Timer(); + TimerTask updateEventStatus = new TimerTask() { + + @Override + public void run() { + try { + CommandResult updateCommandResult = logic.execute("update"); + } catch (CommandException | ParseException pe) { + // Will never happen + } + } + }; + + timer.scheduleAtFixedRate(updateEventStatus, 300000, 300000); + + TimerTask checkEventReminders = new TimerTask() { + + @Override + public void run() { + try { + CommandResult reminderCommandResult = logic.execute("reminder"); + } catch (CommandException | ParseException pe) { + logger.info("No upcoming reminders."); + new NewResultAvailableEvent(pe.getMessage()); + } + } + }; + + timer.scheduleAtFixedRate(checkEventReminders, 36000000, 36000000); + } + @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting EventManager " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping EventManager ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/comparators/DateTimeComparator.java b/src/main/java/seedu/address/commons/comparators/DateTimeComparator.java new file mode 100644 index 000000000000..8bb0072fa157 --- /dev/null +++ b/src/main/java/seedu/address/commons/comparators/DateTimeComparator.java @@ -0,0 +1,16 @@ +package seedu.address.commons.comparators; + +import java.util.Comparator; + +import seedu.address.model.event.Event; + +/** + * Comparator use to compare @event by @DateTime objects + * @return a @Comparator + */ +public class DateTimeComparator implements Comparator { + @Override + public int compare(Event e1, Event e2) { + return e1.getDateTime().dateTime.compareTo(e2.getDateTime().dateTime); + } +} diff --git a/src/main/java/seedu/address/commons/comparators/NameComparator.java b/src/main/java/seedu/address/commons/comparators/NameComparator.java new file mode 100644 index 000000000000..e891de872e5d --- /dev/null +++ b/src/main/java/seedu/address/commons/comparators/NameComparator.java @@ -0,0 +1,16 @@ +package seedu.address.commons.comparators; + +import java.util.Comparator; + +import seedu.address.model.event.Event; + +/** + * Comparator use to compare @event by @DateTime objects + * @return a @Comparator + */ +public class NameComparator implements Comparator { + @Override + public int compare(Event e1, Event e2) { + return e1.getName().fullName.compareTo(e2.getName().fullName); + } +} diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..5e4154334c86 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Event Manager App"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); @@ -67,7 +67,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("App title : " + appTitle); sb.append("\nCurrent log level : " + logLevel); - sb.append("\nPreference file Location : " + userPrefsFilePath); + sb.append("\nPreference file location : " + userPrefsFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..d41550e463e2 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,11 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_EMPTY_COMMENT = "C/ cannot be empty! \n%1$s "; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_EVENT_DISPLAYED_INDEX = "The event index provided is invalid"; + public static final String MESSAGE_EVENTS_LISTED_OVERVIEW = "%1$d events listed!"; + public static final String MESSAGE_LINE_INVALID = "Line must be a non-signed integer!" + + " Example: deleteComment 1 L/2 or replyComment 1 L/2 C/5"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/EventManagerChangedEvent.java b/src/main/java/seedu/address/commons/events/model/EventManagerChangedEvent.java new file mode 100644 index 000000000000..478fb0cae51d --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/EventManagerChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyEventManager; + +/** Indicates the EventManager in the model has changed*/ +public class EventManagerChangedEvent extends BaseEvent { + + public final ReadOnlyEventManager data; + + public EventManagerChangedEvent(ReadOnlyEventManager data) { + this.data = data; + } + + @Override + public String toString() { + return "number of events " + data.getEventList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/EventSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/EventSelectionChangedEvent.java new file mode 100644 index 000000000000..f844753ba485 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/EventSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.event.Event; + +/** + * Represents a selection change in the Event List Panel + */ +public class EventSelectionChangedEvent extends BaseEvent { + + + private final Event newSelection; + + public EventSelectionChangedEvent(Event newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Event getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index c5c8b9ce90ed..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final Person newSelection; - - public PersonPanelSelectionChangedEvent(Person newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - public Person getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/SendEventReminder.java b/src/main/java/seedu/address/commons/events/ui/SendEventReminder.java new file mode 100644 index 000000000000..b2a5e4e2c2d2 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/SendEventReminder.java @@ -0,0 +1,26 @@ +//@@author cqinkai +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to send a reminder to the user + * for the upcoming event with {@code eventName} + */ +public class SendEventReminder extends BaseEvent { + + public final String eventName; + public final String eventDate; + public final String timeTillEvent; + + public SendEventReminder(String eventName, String eventDate, String timeTillEvent) { + this.eventName = eventName; + this.eventDate = eventDate; + this.timeTillEvent = timeTillEvent; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/util/PasswordUtil.java b/src/main/java/seedu/address/commons/util/PasswordUtil.java new file mode 100644 index 000000000000..8be658bcee5d --- /dev/null +++ b/src/main/java/seedu/address/commons/util/PasswordUtil.java @@ -0,0 +1,101 @@ +package seedu.address.commons.util; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +//@@ jamesyaputra +/** + * Utility class to encrypt and decrypt passwords. + */ +public class PasswordUtil { + + private static final int SALT_BYTE_SIZE = 16; + private static final int HASH_BYTE_SIZE = 64 * 8; + private static final int PKBDF2_ITERATIONS = 1000; + + /** + * Returns the password encrypted using PBKDF2WithHmacSHA1. + */ + public static String getEncryptedPassword(String plainPassword) + throws NoSuchAlgorithmException, InvalidKeySpecException { + return generateHash(plainPassword); + } + + /** + * Returns true if plainPassword is equal to encryptedPassword decrypted. + */ + public static boolean validatePassword(String plainPassword, String encryptedPassword) + throws NoSuchAlgorithmException, InvalidKeySpecException { + + String [] splits = encryptedPassword.split(":"); + int iterations = Integer.parseInt(splits[0]); + byte[] salt = convertFromHex(splits[1]); + byte[] hash = convertFromHex(splits[2]); + char[] chars = plainPassword.toCharArray(); + + PBEKeySpec pbeKeySpec = new PBEKeySpec(chars, salt, iterations, HASH_BYTE_SIZE); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + byte[] testHash = secretKeyFactory.generateSecret(pbeKeySpec).getEncoded(); + + int diff = hash.length ^ testHash.length; + for (int i = 0; i < hash.length && i < testHash.length; i++) { + diff |= hash[i] ^ testHash[i]; + } + + return diff == 0; + } + + /** + * Generates the encrypted password using PBKDF2WithHmacSHA1. + */ + private static String generateHash(String plainPassword) throws NoSuchAlgorithmException, InvalidKeySpecException { + char[] chars = plainPassword.toCharArray(); + byte[] salt = generateSalt(); + + PBEKeySpec pbeKeySpec = new PBEKeySpec(chars, salt, PKBDF2_ITERATIONS, HASH_BYTE_SIZE); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + byte[] hash = secretKeyFactory.generateSecret(pbeKeySpec).getEncoded(); + return PKBDF2_ITERATIONS + ":" + convertToHex(salt) + ":" + convertToHex(hash); + } + + /** + * Generates the salt used in password encryption. + */ + private static byte[] generateSalt() throws NoSuchAlgorithmException { + SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); + byte[] salt = new byte[SALT_BYTE_SIZE]; + secureRandom.nextBytes(salt); + return salt; + } + + /** + * Converts an array of bytes to a hexadecimal string. + */ + private static String convertToHex(byte[] array) { + BigInteger bigInteger = new BigInteger(1, array); + String hex = bigInteger.toString(SALT_BYTE_SIZE); + int paddingLength = (array.length * 2) - hex.length(); + + if (paddingLength > 0) { + return String.format("%0" + paddingLength + "d", 0) + hex; + } + + return hex; + } + + /** + * Converts a hexadecimal string into an array of bytes. + */ + private static byte[] convertFromHex(String hex) { + byte[] bytes = new byte[hex.length() / 2]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), SALT_BYTE_SIZE); + } + + return bytes; + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..11caabdab0e0 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; /** * API of the Logic component @@ -19,8 +19,8 @@ public interface Logic { */ CommandResult execute(String commandText) throws CommandException, ParseException; - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of events */ + ObservableList getFilteredEventList(); /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..049bc580e894 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -8,10 +8,10 @@ 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.EventManagerParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; /** * The main LogicManager of the app. @@ -21,19 +21,19 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final EventManagerParser eventManagerParser; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + eventManagerParser = new EventManagerParser(); } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = eventManagerParser.parseCommand(commandText); return command.execute(model, history); } finally { history.add(commandText); @@ -41,8 +41,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredEventList() { + return model.getFilteredEventList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index d88e831ff1ce..64b2d88042c8 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,62 +1,77 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; 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.logic.parser.CliSyntax.PREFIX_VENUE; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; /** - * Adds a person to the address book. + * Adds a event to the event manager. */ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an event to the event manager.\n" + "Parameters: " + PREFIX_NAME + "NAME " + + PREFIX_CONTACT + "CONTACT " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " + + PREFIX_VENUE + "VENUE " + + PREFIX_DATETIME + "DATETIME " + + "[" + PREFIX_TAG + "TAG]... " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NAME + "New Year Party " + + PREFIX_CONTACT + "Amy Tan " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_VENUE + "311, Clementi Ave 2, #02-25 " + + PREFIX_DATETIME + "31/12/2018 4:30 " + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_TAG + "party"; - 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 event added: %1$s"; + public static final String MESSAGE_DUPLICATE_EVENT = "This event already exists in the event manager"; - private final Person toAdd; + private final Event toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCommand to add the specified {@code Event} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCommand(Event event) { + requireNonNull(event); + toAdd = event; } @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + if (model.hasEvent(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); } - model.addPerson(toAdd); - model.commitAddressBook(); + model.addEvent(toAdd); + model.commitEventManager(); return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommentCommand.java b/src/main/java/seedu/address/logic/commands/AddCommentCommand.java new file mode 100644 index 000000000000..d8d2e7a64186 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddCommentCommand.java @@ -0,0 +1,121 @@ +//@@author Geraldcdx +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMMENT; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.comments.CommentFacade; +import seedu.address.model.Model; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Event; + +/** + * Adds a comment to the end comment section of the event + */ +public class AddCommentCommand extends Command { + public static final String COMMAND_WORD = "addComment"; + + public static final String MESSAGE = COMMAND_WORD + + ": Adds a comment to the end of the comment section of the event identified " + + "by the index number used in the displayed event list.\n " + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_COMMENT + "COMMENT] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_COMMENT + "johndoe@example.com is here"; + + public static final String MESSAGE_ADD_COMMENT = "Comment [%1$s] replied for Event %2$s"; + + private Index index; + private EditCommand.EditEventDescriptor editCommentDescriptor = new EditCommand.EditEventDescriptor(); + private String comment; + private String username; + + /** + * @param index of the event in the filtered event list to edit + * @param comment comment to add + */ + public AddCommentCommand(Index index, String comment) { + requireNonNull(index); + requireNonNull(comment); + + this.index = index; + this.comment = comment; + + } + + public String getComment() { + return this.comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public Index getIndex() { + return this.index; + } + + public void setIndex(Index index) { + this.index = index; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return this.username; + } + + /** + * Save edited information in model + * @param model {@code Model} which the command should operate on. + * @param history {@code CommandHistory} which the command should operate on. + * @return CommentResult + * @throws CommandException login failed or index invalid + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + List filteredEventList = model.getFilteredEventList(); + + if (index.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + setUsername(model.getUsername().toString()); + Event eventToEdit = filteredEventList.get(index.getZeroBased()); + Event editedEvent = addComment(eventToEdit, getUsername()); + model.updateEvent(eventToEdit, editedEvent); + model.commitEventManager(); + EventsCenter.getInstance().post(new JumpToListRequestEvent(index)); + return new CommandResult(String.format(MESSAGE_ADD_COMMENT, getComment(), index.getOneBased())); + } + + /** + * + * @param eventToEdit event with the given index + * @param username of the user + * @return event with added comment + */ + public Event addComment(Event eventToEdit, String username) { + CommentFacade comments = new CommentFacade(); + String addedComment = comments.addComment(eventToEdit.getComment().toString(), + getComment(), username); + editCommentDescriptor.setComment(new Comment(addedComment)); + return EditCommand.createEditedEvent(eventToEdit, editCommentDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/AttendingCommand.java b/src/main/java/seedu/address/logic/commands/AttendingCommand.java new file mode 100644 index 000000000000..5b329d4da476 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AttendingCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDEE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.Prefix; +import seedu.address.model.Model; +import seedu.address.model.event.EventContainsKeywordsPredicate; + +/** + * Finds and lists all events in event manager which the user has registered for. + */ +public class AttendingCommand extends Command { + + public static final String COMMAND_WORD = "attending"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all events you have registered for " + + "and displays them as a list with index numbers.\n"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + String currUsername = model.getUsername().toString(); + List currUsernameList = new ArrayList<>(); + currUsernameList.addAll(Arrays.asList(currUsername.trim().split("\\s+"))); + Map > keywordsMap = new HashMap<>(); + keywordsMap.put(PREFIX_ATTENDEE, currUsernameList); + EventContainsKeywordsPredicate predicate = new EventContainsKeywordsPredicate(keywordsMap); + model.updateFilteredEventList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..bc62d2bddb3f 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -3,23 +3,33 @@ import static java.util.Objects.requireNonNull; import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.EventManager; import seedu.address.model.Model; /** - * Clears the address book. + * Clears the event manager. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "Event manager has been cleared!"; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + model.resetData(new EventManager()); + model.commitEventManager(); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 34e99d786ec6..9b2e8d5b6e9d 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -9,6 +9,9 @@ */ public abstract class Command { + public static final String MESSAGE_LOGIN = "Please login to an account!"; + public static final String MESSAGE_ADMIN = "You are not authorized to perform this command!"; + /** * Executes the command and returns the result message. * @@ -18,5 +21,4 @@ public abstract class Command { * @throws CommandException If an error occurs during command execution. */ public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; - } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index abdc267a2c44..855f1823aa94 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -13,4 +13,8 @@ public CommandResult(String feedbackToUser) { this.feedbackToUser = requireNonNull(feedbackToUser); } + public String getString() { + return this.feedbackToUser; + } + } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index a20e9d49eac7..082093bf8aa1 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -9,21 +9,22 @@ import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; + /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a event identified using it's displayed index from the event manager. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; 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 event identified by the index number used in the displayed event list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_EVENT_SUCCESS = "Deleted Event: %1$s"; private final Index targetIndex; @@ -34,16 +35,25 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + List lastShownList = model.getFilteredEventList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Event eventToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteEvent(eventToDelete); + model.commitEventManager(); + return new CommandResult(String.format(MESSAGE_DELETE_EVENT_SUCCESS, eventToDelete)); } @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommentCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommentCommand.java new file mode 100644 index 000000000000..ebeec63c6e4f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteCommentCommand.java @@ -0,0 +1,116 @@ +//@@author Geraldcdx +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LINE; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.comments.CommentFacade; +import seedu.address.model.Model; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Event; + +/** + * Delete a comment in the comment section of the event given the line + */ +public class DeleteCommentCommand extends Command { + + public static final String COMMAND_WORD = "deleteComment"; + + public static final String MESSAGE = COMMAND_WORD + + ": Deletes a comment in the comment section of the event identified " + + "by the index number used in the displayed event list " + + "when the line parameter is given.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_LINE + "LINE] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_LINE + "91 "; + + public static final String MESSAGE_DELETE_COMMENT = "Comment deleted for Event %1$s at Line %2$s"; + public static final String MESSAGE_LINE_INVALID = "Line is invalid, try again. Example: deleteComment 1 L/2"; + + private final EditCommand.EditEventDescriptor editCommentDescriptor = new EditCommand.EditEventDescriptor(); + private int line = 0; + private Index index; + + /** + * @param index of the event in the filtered event list to edit + * @param line to delete comment section + */ + public DeleteCommentCommand(Index index, int line) { + requireNonNull(index); + requireNonNull(line); + + this.index = index; + this.line = line; + } + + public int getLine() { + return this.line; + } + + public Index getIndex() { + return this.index; + } + + public void setLine(int line) { + this.line = line; + } + + public void setIndex(Index index) { + this.index = index; + } + + /** + * Save edited information in model + * @param model {@code Model} which the command should operate on. + * @param history {@code CommandHistory} which the command should operate on. + * @return CommandResult + * @throws CommandException login failed, not admin, invalid index + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + List filteredEventList = model.getFilteredEventList(); + + if (index.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToEdit = filteredEventList.get(index.getZeroBased()); + Event editedEvent = deleteComment(eventToEdit); + model.updateEvent(eventToEdit, editedEvent); + model.commitEventManager(); + EventsCenter.getInstance().post(new JumpToListRequestEvent(index)); + return new CommandResult(String.format(MESSAGE_DELETE_COMMENT, index.getOneBased(), getLine())); + } + + /** + * Removes comment with given line parameter + * @param eventToEdit event to edit + * @return updated comment section + */ + public Event deleteComment(Event eventToEdit) throws CommandException { + CommentFacade comments = new CommentFacade(); + String deletedComment = comments.deleteComment(eventToEdit.getComment().toString(), getLine()); + editCommentDescriptor.setComment(new Comment(deletedComment)); + return EditCommand.createEditedEvent(eventToEdit, editCommentDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index dc782d8e230f..e84bea275261 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,12 +1,16 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDEE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; 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 seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; import java.util.Collections; import java.util.HashSet; @@ -20,88 +24,114 @@ import seedu.address.logic.CommandHistory; 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.attendee.Attendee; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Contact; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Email; +import seedu.address.model.event.Event; +import seedu.address.model.event.Name; +import seedu.address.model.event.Phone; +import seedu.address.model.event.Status; +import seedu.address.model.event.Venue; import seedu.address.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing event in the event manager. */ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - 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 event identified " + + "by the index number used in the displayed event list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_CONTACT + "CONTACT] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_VENUE + "VENUE] " + + "[" + PREFIX_DATETIME + "DD/MM/YYYY HH:MM " + + "[" + PREFIX_TAG + "TAG]... " + + "[" + PREFIX_ATTENDEE + "ATTENDEE]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + + public static final String MESSAGE_EDIT_EVENT_SUCCESS = "Edited Event: %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_EVENT = "This event already exists in the event manager."; private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditEventDescriptor editEventDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index of the event in the filtered event list to edit + * @param editEventDescriptor details to edit the event with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditEventDescriptor editEventDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); + requireNonNull(editEventDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editEventDescriptor = new EditEventDescriptor(editEventDescriptor); } @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + List lastShownList = model.getFilteredEventList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Event eventToEdit = lastShownList.get(index.getZeroBased()); + Event editedEvent = createEditedEvent(eventToEdit, editEventDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!eventToEdit.isSameEvent(editedEvent) && model.hasEvent(editedEvent)) { + throw new CommandException(MESSAGE_DUPLICATE_EVENT); } - model.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + model.updateEvent(eventToEdit, editedEvent); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + model.commitEventManager(); + return new CommandResult(String.format(MESSAGE_EDIT_EVENT_SUCCESS, editedEvent)); } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Event} with the details of {@code eventToEdit} + * edited with {@code editEventDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - Name 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()); + public static Event createEditedEvent(Event eventToEdit, EditEventDescriptor editEventDescriptor) { + + assert eventToEdit != null; + + Name updatedName = editEventDescriptor.getName().orElse(eventToEdit.getName()); + Contact updatedContact = editEventDescriptor.getContact().orElse(eventToEdit.getContact()); + Phone updatedPhone = editEventDescriptor.getPhone().orElse(eventToEdit.getPhone()); + Email updatedEmail = editEventDescriptor.getEmail().orElse(eventToEdit.getEmail()); + Venue updatedVenue = editEventDescriptor.getVenue().orElse(eventToEdit.getVenue()); + DateTime updatedDateTime = editEventDescriptor.getDateTime().orElse(eventToEdit.getDateTime()); + Status eventStatus = new Status(Status.setStatus(updatedDateTime)); + Comment updatedComment = editEventDescriptor.getComment().orElse(eventToEdit.getComment()); + Set updatedTags = editEventDescriptor.getTags().orElse(eventToEdit.getTags()); + Set updatedAttendees = editEventDescriptor.getAttendees().orElse(eventToEdit.getAttendance()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Event(updatedName, updatedContact, updatedPhone, updatedEmail, updatedVenue, updatedDateTime, + eventStatus, updatedComment, updatedTags, updatedAttendees); } @Override @@ -119,39 +149,49 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); + && editEventDescriptor.equals(e.editEventDescriptor); } /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the event with. Each non-empty field value will replace the + * corresponding field value of the event. */ - public static class EditPersonDescriptor { + public static class EditEventDescriptor { private Name name; + private Contact contact; private Phone phone; private Email email; - private Address address; + private Venue venue; + private DateTime dateTime; + private Status status; + private Comment comment; + private Set tags; + private Set attendees; - public EditPersonDescriptor() {} + public EditEventDescriptor() {} /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditEventDescriptor(EditEventDescriptor toCopy) { setName(toCopy.name); + setContact(toCopy.contact); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setVenue(toCopy.venue); + setDate(toCopy.dateTime); + setComment(toCopy.comment); setTags(toCopy.tags); + setAttendees(toCopy.attendees); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, contact, phone, email, venue, dateTime, comment, tags, attendees); } public void setName(Name name) { @@ -162,6 +202,14 @@ public Optional getName() { return Optional.ofNullable(name); } + public void setContact(Contact contact) { + this.contact = contact; + } + + public Optional getContact() { + return Optional.ofNullable(contact); + } + public void setPhone(Phone phone) { this.phone = phone; } @@ -178,14 +226,39 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setVenue(Venue venue) { + this.venue = venue; + } + + public Optional getVenue() { + return Optional.ofNullable(venue); + } + + public void setDate(DateTime dateTime) { + this.dateTime = dateTime; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getDateTime() { + return Optional.ofNullable(dateTime); } + public void setStatus(Status status) { + this.status = status; + } + + public Optional getStatus() { + return Optional.ofNullable(status); + } + + public void setComment(Comment comment) { + this.comment = comment; + } + + public Optional getComment() { + return Optional.ofNullable(comment); + } + + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -194,6 +267,14 @@ public void setTags(Set tags) { this.tags = (tags != null) ? new HashSet<>(tags) : null; } + /** + * Sets {@code attendees} to this object's {@code attendees}. + * A defensive copy of {@code attendees} is used internally. + */ + public void setAttendees(Set attendees) { + this.attendees = (attendees != null) ? new HashSet<>(attendees) : null; + } + /** * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -203,6 +284,15 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + /** + * Returns an unmodifiable attendee set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code attendees} is null. + */ + public Optional> getAttendees() { + return (attendees != null) ? Optional.of(Collections.unmodifiableSet(attendees)) : Optional.empty(); + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -211,18 +301,23 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditEventDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditEventDescriptor e = (EditEventDescriptor) other; return getName().equals(e.getName()) + && getContact().equals(e.getContact()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getVenue().equals(e.getVenue()) + && getDateTime().equals(e.getDateTime()) + && getStatus().equals(e.getStatus()) + && getComment().equals(e.getComment()) + && getTags().equals(e.getTags()) + && getAttendees().equals(e.getAttendees()); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index e848fa918964..3ab5ee80b692 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -12,7 +12,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Event Manager as requested ..."; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/address/logic/commands/ExportCalendarCommand.java b/src/main/java/seedu/address/logic/commands/ExportCalendarCommand.java new file mode 100644 index 000000000000..a1606b14f8a2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ExportCalendarCommand.java @@ -0,0 +1,207 @@ +package seedu.address.logic.commands; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import javafx.collections.ObservableList; +import net.fortuna.ical4j.data.CalendarOutputter; +import net.fortuna.ical4j.model.Calendar; +import net.fortuna.ical4j.model.component.VEvent; +import net.fortuna.ical4j.model.parameter.Cn; +import net.fortuna.ical4j.model.parameter.CuType; +import net.fortuna.ical4j.model.parameter.PartStat; +import net.fortuna.ical4j.model.parameter.Role; +import net.fortuna.ical4j.model.property.Attendee; +import net.fortuna.ical4j.model.property.CalScale; +import net.fortuna.ical4j.model.property.Location; +import net.fortuna.ical4j.model.property.Organizer; +import net.fortuna.ical4j.model.property.ProdId; +import net.fortuna.ical4j.model.property.Status; +import net.fortuna.ical4j.model.property.Uid; +import net.fortuna.ical4j.model.property.Version; +import net.fortuna.ical4j.util.UidGenerator; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.user.Username; + +/** + * Use to export list of current user + * registered events as a iCalendar file + */ +public class ExportCalendarCommand extends Command { + + public static final String COMMAND_WORD = "export"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " filename\n" + + "export current user registered event as an iCalender file\n" + + "filename should not be empty or longer than 255 character\n" + + "filename should only contain alphanumeric characters and some special characters" + "!#$%&'+=~^.@-\n" + + "Example: export myCalendar"; + + public static final String MESSAGE_EXPORT_SUCCESS = + "%1$d event(s) that you registered for has been successfully exported to your %2$s.ics"; + + public static final String MESSAGE_FILE_ERROR = "File %1$s.ics has errors and cannot be opened/created\n" + + "or filename is not a current system allowed filename\n"; + + public static final String MESSAGE_ZERO_EVENT_REGISTERED = "User %1$s has not registered for any event"; + + private static final String CALENDAR_FILE_PATH = "%1$s.ics"; + + private final String fileName; + + public ExportCalendarCommand(String filename) { + fileName = filename; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + Username currentUser = model.getUsername(); + ObservableList registeredEventList = model.getAttendingEventList(currentUser); + + //Check if no event has been registered + if (registeredEventList.size() <= 0) { + throw new CommandException(String.format(MESSAGE_ZERO_EVENT_REGISTERED, currentUser.value)); + } + + try { + exportICalenderFile(registeredEventList, fileName); + } catch (IOException e) { + throw new CommandException(String.format(MESSAGE_FILE_ERROR, fileName)); + } + + return new CommandResult(String.format(MESSAGE_EXPORT_SUCCESS, model.getFilteredEventList().size(), fileName)); + } + + //*****************************Method related to the new export calendar command******************************** + /** + * Convert the Event in Event Manager to VEvent type in ical4j to add to iCalendar file + * @param registeredEventList current user registered event + * @return a list of VEvent after conversion + * NOTE: Currently, due to the limit of java.util.Date so event are default to last from start time to end of the + * day + */ + public List convertEventListToVEventList(ObservableList registeredEventList) { + List calendarEvents = new ArrayList<>(); + + for (Event event : registeredEventList) { + VEvent toAddEvent = convertEventToVEvent(event); + calendarEvents.add(toAddEvent); + } + + return calendarEvents; + } + + /** + * Convert Event to VEvent + * @param event an Event in Event Manager + * @return a VEvent with given properties in event + */ + public VEvent convertEventToVEvent(Event event) { + VEvent vEvent = new VEvent(new net.fortuna.ical4j.model.DateTime(event.getDateTime().dateTime), + new net.fortuna.ical4j.model.DateTime(event.getDateTime().dateTime.getTime() + 1000 * 60 * 60), + event.getName().toString()); + + //Event properties + Location eventLocation = new Location(event.getVenue().value); + Uid eventUid; + try { + eventUid = new Uid(UUID.randomUUID() + "@" + InetAddress.getLocalHost().getHostName()); + } catch (UnknownHostException e) { + UidGenerator eventUidG = Uid::new; + eventUid = eventUidG.generateUid(); + } + + Organizer eventOrganizer = new Organizer(); + eventOrganizer.setCalAddress(URI.create("mailto:" + event.getEmail().value)); + eventOrganizer.getParameters().add(new Cn(event.getContact().fullContactName)); + + //Host properties + Attendee eventHost = new Attendee(URI.create("mailto:" + event.getEmail().value)); + eventHost.getParameters().add(CuType.INDIVIDUAL); + eventHost.getParameters().add(Role.REQ_PARTICIPANT); + eventHost.getParameters().add(new Cn(event.getContact().fullContactName)); + eventHost.getParameters().add(PartStat.ACCEPTED); + //TODO: add description when include, add current user as an attendee + + vEvent.getProperties().add(eventOrganizer); + vEvent.getProperties().add(eventUid); + vEvent.getProperties().add(eventLocation); + vEvent.getProperties().add(eventHost); + vEvent.getProperties().add(Status.VEVENT_CONFIRMED); + + return vEvent; + } + + /** + * Stream and write data from event to an iCalendar file will user specify file name + * @param eventsList user registered event list + * @param filename user preferences file name + * @return userCalendar + */ + private Calendar writeToUserCalendar(ObservableList eventsList, String filename) { + Calendar userCalendar = buildCalendar(filename); + List userRegisteredEvents = convertEventListToVEventList(eventsList); + + for (VEvent userRegisteredEvent : userRegisteredEvents) { + userCalendar.getComponents().add(userRegisteredEvent); + } + + return userCalendar; + } + + /** + * Build an Calendar with user preferences file name + * @param filename user preferences file name + * @return Calendar + */ + private Calendar buildCalendar(String filename) { + Calendar calendar = new Calendar(); + calendar.getProperties().add(new ProdId(String.format("-//%1$s//iCal4j 1.0//EN", filename))); + calendar.getProperties().add(Version.VERSION_2_0); + calendar.getProperties().add(CalScale.GREGORIAN); + + return calendar; + } + + /** + * Export an iCalendar from user registered event list + * + * @param registeredEventList + * @param fileName user preferences file name + * @throws IOException when file stream have problems + */ + public void exportICalenderFile(ObservableList registeredEventList, String fileName) throws IOException { + String outputFilename = Paths.get(String.format(CALENDAR_FILE_PATH, fileName)).toString(); + //File outputFile = new File(outputFilename); + + FileOutputStream fileOut = new FileOutputStream(outputFilename, false); + + CalendarOutputter outPutter = new CalendarOutputter(); + + Calendar calendar = writeToUserCalendar(registeredEventList, fileName); + + outPutter.output(calendar, fileOut); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExportCalendarCommand // instanceof handles nulls + && fileName.equals(((ExportCalendarCommand) other).fileName)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index beb178e3a3f5..8bcf4df2ea7f 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -4,34 +4,40 @@ import seedu.address.commons.core.Messages; import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.event.EventContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all events in event manager whose data 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 " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all events whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Parameters: KEYWORD [MORE KEYWORDS]\n" + + "Example: " + COMMAND_WORD + " alice bob 04:20"; - private final NameContainsKeywordsPredicate predicate; + private final EventContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(EventContainsKeywordsPredicate predicate) { this.predicate = predicate; } @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - model.updateFilteredPersonList(predicate); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + model.updateFilteredEventList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_EVENTS_LISTED_OVERVIEW, model.getFilteredEventList().size())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f1541fb57f20..b3dcb04e4e2b 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -6,6 +6,7 @@ import java.util.List; import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; /** @@ -18,8 +19,13 @@ public class HistoryCommand extends Command { public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(history); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + List previousCommands = history.getHistory(); if (previousCommands.isEmpty()) { diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..b8eee809dfed 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,25 +1,24 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; /** - * Lists all persons in the address book to the user. + * Lists all events in the event manager to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; - + public static final String MESSAGE_SUCCESS = "Listed all events\n"; @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/LoginCommand.java b/src/main/java/seedu/address/logic/commands/LoginCommand.java new file mode 100644 index 000000000000..61356ffd6de6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoginCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.user.User; + +//@@ jamesyaputra +/** + * Logs user into the Event Manager Application + */ +public class LoginCommand extends Command { + + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logs user into Event Manager. " + + "Parameters: " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD"; + + public static final String MESSAGE_SUCCESS = "Logged in: %1$s"; + public static final String MESSAGE_LOGGED = "Already logged in as %1$s!"; + public static final String MESSAGE_FAILURE = "Incorrect account credentials!"; + + private final User toLogin; + + /** + * Creates an LoginCommand to add the specified {@code User} + */ + public LoginCommand(User user) { + requireNonNull(user); + toLogin = user; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.getLoginStatus()) { + throw new CommandException(String.format(MESSAGE_LOGGED, model.getUsername().toString())); + } + + model.logUser(toLogin); + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, toLogin.getUsername().toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand // instanceof handles nulls + && toLogin.equals(((LoginCommand) other).toLogin)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/LogoutCommand.java b/src/main/java/seedu/address/logic/commands/LogoutCommand.java new file mode 100644 index 000000000000..b31b7a455be5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogoutCommand.java @@ -0,0 +1,33 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.user.Username; + +//@@ jamesyaputra +/** + * Logs user out of the Event Manager. + */ +public class LogoutCommand extends Command { + + public static final String COMMAND_WORD = "logout"; + public static final String MESSAGE_SUCCESS = "Logged out: %1$s"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + Username currentUsername = model.getUsername(); + model.clearUser(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, currentUsername.toString())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..58ee893f2329 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -1,14 +1,14 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; /** - * Reverts the {@code model}'s address book to its previously undone state. + * Reverts the {@code model}'s event manager to its previously undone state. */ public class RedoCommand extends Command { @@ -20,12 +20,16 @@ public class RedoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canRedoAddressBook()) { + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.canRedoEventManager()) { throw new CommandException(MESSAGE_FAILURE); } - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.redoEventManager(); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/RegisterCommand.java b/src/main/java/seedu/address/logic/commands/RegisterCommand.java new file mode 100644 index 000000000000..280582764db0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RegisterCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.logic.commands.EditCommand.EditEventDescriptor; +import static seedu.address.logic.commands.EditCommand.createEditedEvent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Event; + +/** + * Registers for an event identified using it's displayed index from the event manager. + */ +public class RegisterCommand extends Command { + + public static final String COMMAND_WORD = "register"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Registers for an event identified by the index number used in the displayed event list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_REGISTER_EVENT_SUCCESS = "Registered for event: %1$s"; + public static final String MESSAGE_ALREADY_REGISTERED = "Already registered for event."; + + private final Index targetIndex; + + public RegisterCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + List filteredEventList = model.getFilteredEventList(); + + if (targetIndex.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToRegister = filteredEventList.get(targetIndex.getZeroBased()); + + String attendeeName = model.getUsername().toString(); + + Set attendeeSet = new HashSet<>(eventToRegister.getAttendance()); + + if (!attendeeSet.add(new Attendee(attendeeName))) { + throw new CommandException(MESSAGE_ALREADY_REGISTERED); + } + + EditEventDescriptor registerEventDescriptor = new EditEventDescriptor(); + registerEventDescriptor.setAttendees(attendeeSet); + Event registeredEvent = createEditedEvent(eventToRegister, registerEventDescriptor); + + model.updateEvent(eventToRegister, registeredEvent); + model.commitEventManager(); + + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_REGISTER_EVENT_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RegisterCommand // instanceof handles nulls + && targetIndex.equals(((RegisterCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ReminderCommand.java b/src/main/java/seedu/address/logic/commands/ReminderCommand.java new file mode 100644 index 000000000000..9e3af95ce683 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReminderCommand.java @@ -0,0 +1,82 @@ +//@@author cqinkai +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.DateTimeUtil.daysDiff; +import static seedu.address.model.event.EventContainsKeywordsPredicate.checkAttendeeKeywordsMatchEventAttendee; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.SendEventReminder; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.event.Event; + +/** + * Sends reminders to logged-in user based on upcoming events that the user has registered for + * using the {@code RegisterCommand}. + * Upcoming events defined as events that will be occurring within a 24hours time frame. + */ +public class ReminderCommand extends Command { + + public static final String COMMAND_WORD = "reminder"; + + public static final String MESSAGE_REMINDER_SENT = "Reminder(s) sent."; + public static final String MESSAGE_NO_UPCOMING_EVENTS = "You do not have any upcoming events."; + public static final String MESSAGE_NOT_LOGGED_IN = "You are not logged in!"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + int index = 0; + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_NOT_LOGGED_IN); + } + + List lastShownList = model.getFilteredEventList(); + List userStringAsList = new ArrayList<>(); + List upcomingEvents = new ArrayList<>(); + Date currentDate = new Date(); + + String user = model.getUsername().toString(); + userStringAsList.add(user.trim()); + + for (Event event : lastShownList) { + Index targetIndex = Index.fromZeroBased(index++); + + if ((checkAttendeeKeywordsMatchEventAttendee(userStringAsList, event)) && checkEventIsUpcoming(event)) { + upcomingEvents.add(event); + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + EventsCenter.getInstance().post(new SendEventReminder(event.getName().toString(), + event.getDateTime().toString(), + String.valueOf(daysDiff(event.getDateTime().dateTime, currentDate, TimeUnit.MILLISECONDS)))); + } + } + + if (upcomingEvents.isEmpty()) { + return new CommandResult(MESSAGE_NO_UPCOMING_EVENTS); + } + + return new CommandResult(MESSAGE_REMINDER_SENT); + } + + /** + * Checks if the {@code Event} is upcoming + * based on the criteria that the {@code Event} will be occurring in the next 24 hours. + * @param event + */ + private boolean checkEventIsUpcoming(Event event) { + Date currentDate = new Date(); + Date eventDate = event.getDateTime().dateTime; + + return ((eventDate.after(currentDate)) && (daysDiff(eventDate, currentDate, TimeUnit.MILLISECONDS) <= 24)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemoveAttendeeCommand.java b/src/main/java/seedu/address/logic/commands/RemoveAttendeeCommand.java new file mode 100644 index 000000000000..1c17ffbed7ce --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemoveAttendeeCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.EditCommand.EditEventDescriptor; +import static seedu.address.logic.commands.EditCommand.createEditedEvent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Event; +import seedu.address.model.user.Username; + +/** + * Removes an attendee from an event using the event's displayed index from the event manager + * and the attendee's username. + */ +public class RemoveAttendeeCommand extends Command { + + public static final String COMMAND_WORD = "removeAttendee"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes an attendee from an event identified by the index number used in the displayed event list " + + "and the attendee's username.\n" + + "Parameters: INDEX (must be a positive integer) " + + "u/USERNAME (must be a valid username registered for the event)\n" + + "Example: " + COMMAND_WORD + " 1" + " u/Peter Parker"; + + public static final String MESSAGE_REMOVE_ATTENDEE_SUCCESS = "Removed user %1$s from event: %2$s"; + public static final String MESSAGE_INVALID_ATTENDEE = "Attendee is not registered for event."; + + private final Index targetIndex; + private final Username targetAttendee; + + public RemoveAttendeeCommand(Index targetIndex, Username targetAttendee) { + this.targetIndex = targetIndex; + this.targetAttendee = targetAttendee; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.getAdminStatus()) { + throw new CommandException(MESSAGE_ADMIN); + } + + List filteredEventList = model.getFilteredEventList(); + + if (targetIndex.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventWithAttendee = filteredEventList.get(targetIndex.getZeroBased()); + Set attendeeSet = new HashSet<>(eventWithAttendee.getAttendance()); + + if (!attendeeSet.remove(new Attendee(targetAttendee.toString()))) { + throw new CommandException(MESSAGE_INVALID_ATTENDEE); + } + + EditEventDescriptor removeAttendeeEventDescriptor = new EditEventDescriptor(); + removeAttendeeEventDescriptor.setAttendees(attendeeSet); + Event eventAttendeeRemoved = createEditedEvent(eventWithAttendee, removeAttendeeEventDescriptor); + + model.updateEvent(eventWithAttendee, eventAttendeeRemoved); + model.commitEventManager(); + + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_REMOVE_ATTENDEE_SUCCESS, + targetAttendee.toString(), targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveAttendeeCommand // instanceof handles nulls + && targetIndex.equals(((RemoveAttendeeCommand) other).targetIndex) + && targetAttendee.equals(((RemoveAttendeeCommand) other).targetAttendee)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ReplyCommentCommand.java b/src/main/java/seedu/address/logic/commands/ReplyCommentCommand.java new file mode 100644 index 000000000000..1b2e1fa1f9ba --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReplyCommentCommand.java @@ -0,0 +1,129 @@ +//@@author Geraldcdx +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LINE; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.comments.CommentFacade; +import seedu.address.model.Model; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Event; + +/** + * Replies a comment in the comment section of the event + */ +public class ReplyCommentCommand extends Command { + + public static final String COMMAND_WORD = "replyComment"; + + public static final String MESSAGE = COMMAND_WORD + ": Replies the comment section of the event identified " + + "by the index number used in the displayed event list " + + "with comment and line parameters given.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_LINE + "LINE] " + + "[" + PREFIX_COMMENT + "COMMENT] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_LINE + "91 " + + PREFIX_COMMENT + "johndoe@example.com is here"; + + public static final String MESSAGE_REPLY_COMMENT = "Comment [%1$s] replied for Event %2$s at Line %3$s"; + public static final String MESSAGE_LINE_INVALID = "Line is invalid, try again. Example: replyComment 1 L/1 C/Hello"; + + private final EditCommand.EditEventDescriptor editCommentDescriptor; + private int line = 0; + private String comment; + private String username; + private Index index; + + /** + * @param index of the event in the filtered event list to edit + * @param line to reply to in the comment section + * @param comment to add to comment section + */ + public ReplyCommentCommand(Index index, int line, String comment) { + requireNonNull(index); + requireNonNull(line); + requireNonNull(comment); + + this.index = index; + this.line = line; + this.comment = comment; + this.editCommentDescriptor = new EditCommand.EditEventDescriptor(); + } + + public String getComment() { + return this.comment; + } + + public int getLine() { + return this.line; + } + + public Index getIndex() { + return this.index; + } + + public void setIndex(Index index) { + this.index = index; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + /** + * Save edited information in model + * @param model {@code Model} which the command should operate on. + * @param history {@code CommandHistory} which the command should operate on. + * @return + * @throws CommandException + */ + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List filteredEventList = model.getFilteredEventList(); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (index.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + setUsername(model.getUsername().toString()); + Event eventToEdit = filteredEventList.get(index.getZeroBased()); + Event editedEvent = replyComment(eventToEdit, getUsername()); + model.updateEvent(eventToEdit, editedEvent); + model.commitEventManager(); + EventsCenter.getInstance().post(new JumpToListRequestEvent(index)); + return new CommandResult(String.format(MESSAGE_REPLY_COMMENT, getComment(), index.getOneBased(), getLine())); + } + + /** + * Replies Comments + * @param eventToEdit event to edit + * @param username username to add to comment + * @return event of edited comment section + */ + public Event replyComment(Event eventToEdit, String username) throws CommandException { + CommentFacade comments = new CommentFacade(); + String repliedComment = comments.replyComment(eventToEdit.getComment().toString(), getComment(), + getLine(), username); + editCommentDescriptor.setComment(new Comment(repliedComment)); + return EditCommand.createEditedEvent(eventToEdit, editCommentDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index f5e8c1a8722e..9dfd2acae45e 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -11,21 +11,21 @@ import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; /** - * Selects a person identified using it's displayed index from the address book. + * Selects a event identified using it's displayed index from the event manager. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" + + ": Selects the event identified by the index number used in the displayed event list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + public static final String MESSAGE_SELECT_EVENT_SUCCESS = "Selected Event: %1$s"; private final Index targetIndex; @@ -37,15 +37,14 @@ public SelectCommand(Index targetIndex) { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List filteredPersonList = model.getFilteredPersonList(); + List filteredEventList = model.getFilteredEventList(); - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (targetIndex.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); } EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - + return new CommandResult(String.format(MESSAGE_SELECT_EVENT_SUCCESS, targetIndex.getOneBased())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/SignupCommand.java b/src/main/java/seedu/address/logic/commands/SignupCommand.java new file mode 100644 index 000000000000..0c2f3b53b91a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SignupCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.user.User; + +//@@ jamesyaputra +/** + * Creates a user in the Event Manager. + */ +public class SignupCommand extends Command { + + public static final String COMMAND_WORD = "signup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sign up for an Event Manager account. " + + "Parameters: " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD"; + + public static final String MESSAGE_LOGGED = "Already logged in as %1$s!"; + public static final String MESSAGE_SUCCESS = "Signed up: %1$s"; + public static final String MESSAGE_EXISTS = "Username already exists!"; + + private final User toSignup; + + /** + * Creates an SignupCommand to add the specified {@code User} + */ + public SignupCommand(User user) { + requireNonNull(user); + toSignup = user; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.getLoginStatus()) { + throw new CommandException(String.format(MESSAGE_LOGGED, model.getUsername().toString())); + } + + if (model.userExists(toSignup)) { + throw new CommandException(MESSAGE_EXISTS); + } + + model.createUser(toSignup); + return new CommandResult(String.format(MESSAGE_SUCCESS, toSignup.getUsername().toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SignupCommand // instanceof handles nulls + && toSignup.equals(((SignupCommand) other).toSignup)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..ec78ba9395f1 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -1,14 +1,14 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; /** - * Reverts the {@code model}'s address book to its previous state. + * Reverts the {@code model}'s event manager to its previous state. */ public class UndoCommand extends Command { @@ -20,12 +20,16 @@ public class UndoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canUndoAddressBook()) { + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + if (!model.canUndoEventManager()) { throw new CommandException(MESSAGE_FAILURE); } - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.undoEventManager(); + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/UnregisterCommand.java b/src/main/java/seedu/address/logic/commands/UnregisterCommand.java new file mode 100644 index 000000000000..068015f1c386 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnregisterCommand.java @@ -0,0 +1,83 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.EditCommand.EditEventDescriptor; +import static seedu.address.logic.commands.EditCommand.createEditedEvent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Event; + +/** + * Unregisters for an event identified using it's displayed index from the event manager. + */ +public class UnregisterCommand extends Command { + + public static final String COMMAND_WORD = "unregister"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unregisters for an event identified by the index number used in the displayed event list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNREGISTER_EVENT_SUCCESS = "Unregistered for event: %1$s"; + public static final String MESSAGE_NOT_REGISTERED = "Not registered for event."; + + private final Index targetIndex; + + public UnregisterCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.getLoginStatus()) { + throw new CommandException(MESSAGE_LOGIN); + } + + List filteredEventList = model.getFilteredEventList(); + + if (targetIndex.getZeroBased() >= filteredEventList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_EVENT_DISPLAYED_INDEX); + } + + Event eventToUnregister = filteredEventList.get(targetIndex.getZeroBased()); + + String attendeeName = model.getUsername().toString(); + + Set attendeeSet = new HashSet<>(eventToUnregister.getAttendance()); + + if (!attendeeSet.remove(new Attendee(attendeeName))) { + throw new CommandException(MESSAGE_NOT_REGISTERED); + } + + EditEventDescriptor registerEventDescriptor = new EditEventDescriptor(); + registerEventDescriptor.setAttendees(attendeeSet); + Event registeredEvent = createEditedEvent(eventToUnregister, registerEventDescriptor); + + model.updateEvent(eventToUnregister, registeredEvent); + model.commitEventManager(); + + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_UNREGISTER_EVENT_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnregisterCommand // instanceof handles nulls + && targetIndex.equals(((UnregisterCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/UpdateStatusCommand.java b/src/main/java/seedu/address/logic/commands/UpdateStatusCommand.java new file mode 100644 index 000000000000..80e7852d5c2c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UpdateStatusCommand.java @@ -0,0 +1,49 @@ +//@@author cqinkai +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.EditCommand.EditEventDescriptor; +import static seedu.address.logic.commands.EditCommand.createEditedEvent; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_EVENTS; + +import java.util.List; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.event.Event; +import seedu.address.model.event.Status; + +/** + * Updates status of events listed in {@code lastShownList} based on new {@code Date}. + * New status identified by {@code setStatus}. + */ +public class UpdateStatusCommand extends Command { + + public static final String COMMAND_WORD = "update"; + + public static final String MESSAGE_SUCCESS = "Updated all event statuses."; + + public static final String MESSAGE_MISSING_EVENTS = "No events to update."; + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + List lastShownList = model.getFilteredEventList(); + + if (lastShownList.isEmpty()) { + return new CommandResult(MESSAGE_MISSING_EVENTS); + } + + for (Event updatingEvent : lastShownList) { + EditEventDescriptor updatedStatusDescriptor = new EditEventDescriptor(); + + Status updatedStatus = new Status(Status.setStatus(updatingEvent.getDateTime())); + updatedStatusDescriptor.setStatus(updatedStatus); + Event updatedEvent = createEditedEvent(updatingEvent, updatedStatusDescriptor); + + model.updateEvent(updatingEvent, updatedEvent); + } + model.updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/comments/AddComment.java b/src/main/java/seedu/address/logic/comments/AddComment.java new file mode 100644 index 000000000000..79f730c69dff --- /dev/null +++ b/src/main/java/seedu/address/logic/comments/AddComment.java @@ -0,0 +1,24 @@ +//@@author Geraldcdx +package seedu.address.logic.comments; + +import java.util.Vector; + +/** + * Adds Comment to the end of the comments section + */ +public class AddComment extends Comments { + + /** + * Appends comment to the end of the current vector and stores + * @param comment comment to be added + * @param username user's username + * @return new comments section + */ + public String addComment(String comment, String username) { + Vector commentSection; + commentSection = getComments(); + commentSection.add(username + " : " + comment); + return rewrite(commentSection); + } + +} diff --git a/src/main/java/seedu/address/logic/comments/CommentFacade.java b/src/main/java/seedu/address/logic/comments/CommentFacade.java new file mode 100644 index 000000000000..a6ff844970c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/comments/CommentFacade.java @@ -0,0 +1,49 @@ +//@@author Geraldcdx +package seedu.address.logic.comments; + +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * A facade to access comment related classes, AddComment, DeleteComment and ReplyComment classes + */ +public class CommentFacade { + + private AddComment add = new AddComment(); + private DeleteComment delete = new DeleteComment(); + private ReplyComment reply = new ReplyComment(); + + /** + * Add comment function + * @param input comments section + * @param comment to add + * @param username to add to comment + * @return String to be stored in eventManager.xml + */ + public String addComment(String input, String comment, String username) { + add.initComments(input); + return add.addComment(comment, username); + } + + /** + * Delete comment function + * @param input comment section + * @param line to delete in comment section + * @return String to be stored in eventManager.xml + */ + public String deleteComment(String input, int line) throws CommandException { + delete.initComments(input); + return delete.deleteComment(line); + } + + /** + * Reply comment function + * @param input comment section + * @param comment to add to comment section + * @param username to add to comment + * @return String to be stored in eventManager.xml + */ + public String replyComment(String input, String comment, int line, String username) throws CommandException { + reply.initComments(input); + return reply.replyComment(comment, line, username); + } +} diff --git a/src/main/java/seedu/address/logic/comments/Comments.java b/src/main/java/seedu/address/logic/comments/Comments.java new file mode 100644 index 000000000000..4f207def1ea7 --- /dev/null +++ b/src/main/java/seedu/address/logic/comments/Comments.java @@ -0,0 +1,86 @@ +//@@author Geraldcdx +package seedu.address.logic.comments; + +import java.util.Vector; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; +import org.jsoup.select.Elements; + +/** + * Parent class for AddComment, Deletecomment and ReplyComment for parsing and manipulating comments section + */ +public abstract class Comments { + + private static String input; + private static Vector comments; + + public Vector getComments() { + return comments; + } + + public String getInput() { + return input; + } + + /** + * Reformats the parsed String from eventManager.xml to HTML + * @param input parsed String from eventManager.xml + * @return valid HTML + */ + public String replaceBrackets(String input) { + input = input.replace("{", "<"); + input = input.replace("}", ">"); + return input; + } + + /** + * Runs a pre-processing on HTML to ensure that strings can be stored as a vector + * @param input HTML comment section + * @return a vector in comments form + */ + public Vector parseCommentSection(String input) { + Vector comments = new Vector(); + Document htmlfile = null; + htmlfile = Jsoup.parse(input); + Element element = htmlfile.select("ol").first(); + Elements divChildren = element.children(); + + Elements detachedDivChildren = new Elements(); + for (Element elem : divChildren) { + Element detachedChild = new Element(Tag.valueOf(elem.tagName()), + elem.baseUri(), elem.attributes().clone()); + detachedDivChildren.add(detachedChild); + } + for (Element elem : divChildren) { + comments.add(elem.ownText()); + } + return comments; + } + + /** + * A intialising of comments to store input and comments vector + * @param input parsed comment section from eventManager.xml + */ + public void initComments(String input) { + this.input = replaceBrackets(input); + this.comments = this.parseCommentSection(getInput()); + } + + /** + * Rewrites String to after a change for a suitable format to store in eventManager.xml + * @param commentsVector edited comment section vector + * @return String that can be store in eventManager.xml + */ + public static String rewrite(Vector commentsVector) { + String comments = "{span}Comment Section{/span}{ol}"; + for (int i = 0; i < commentsVector.size(); i++) { + comments += "{li}" + commentsVector.get(i) + "{/li}"; + } + comments += "{/ol}"; + return comments; + } + +} diff --git a/src/main/java/seedu/address/logic/comments/DeleteComment.java b/src/main/java/seedu/address/logic/comments/DeleteComment.java new file mode 100644 index 000000000000..1550b8bdd020 --- /dev/null +++ b/src/main/java/seedu/address/logic/comments/DeleteComment.java @@ -0,0 +1,30 @@ +//@@author Geraldcdx +package seedu.address.logic.comments; + +import java.util.Vector; + +import seedu.address.logic.commands.DeleteCommentCommand; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Admin only: Deletes a comment given the line of it + */ +public class DeleteComment extends Comments { + + /** + * Can delete comment given event Comment Section index and Line + * @param line to delete from vector + * @return edited comment Section + * @throws CommandException line is not present in comment section + */ + public String deleteComment(int line) throws CommandException { + Vector commentSection; + try { + commentSection = getComments(); + commentSection.remove(line - 1); + } catch (Exception e) { + throw new CommandException(DeleteCommentCommand.MESSAGE_LINE_INVALID); + } + return rewrite(commentSection); + } +} diff --git a/src/main/java/seedu/address/logic/comments/ReplyComment.java b/src/main/java/seedu/address/logic/comments/ReplyComment.java new file mode 100644 index 000000000000..929c00e520f5 --- /dev/null +++ b/src/main/java/seedu/address/logic/comments/ReplyComment.java @@ -0,0 +1,34 @@ +//@@author Geraldcdx +package seedu.address.logic.comments; + +import java.util.Vector; + +import seedu.address.logic.commands.ReplyCommentCommand; +import seedu.address.logic.commands.exceptions.CommandException; + + +/** + * Replies to a comment on a given line with a comment + */ +public class ReplyComment extends Comments { + + /** + * Replies with the comment to event Comment section of index and line + * @param comment to add to comment section + * @param line to add below of in comment section + * @param username to add to comment + * @return edited comment section + * @throws CommandException when line is not in comment section + */ + public String replyComment(String comment, int line, String username) throws CommandException { + Vector commentSection; + try { + commentSection = getComments(); + commentSection.add(line, " (REPLY) " + username + " : " + comment); + } catch (Exception e) { + throw new CommandException(ReplyCommentCommand.MESSAGE_LINE_INVALID); + } + return rewrite(commentSection); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..3ffd20ad4b45 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,22 +1,30 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; 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.logic.parser.CliSyntax.PREFIX_VENUE; +import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Contact; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Email; +import seedu.address.model.event.Event; +import seedu.address.model.event.Name; +import seedu.address.model.event.Phone; +import seedu.address.model.event.Status; +import seedu.address.model.event.Venue; import seedu.address.model.tag.Tag; /** @@ -31,22 +39,29 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_CONTACT, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_VENUE, PREFIX_DATETIME, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_CONTACT, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_VENUE, + PREFIX_DATETIME) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Contact contact = ParserUtil.parseContact(argMultimap.getValue(PREFIX_CONTACT).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()); + Venue venue = ParserUtil.parseVenue(argMultimap.getValue(PREFIX_VENUE).get()); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()); + Status status = new Status(Status.setStatus(dateTime)); + Comment comment = new Comment("{span}Comment Section{/span}{ol}{/ol}"); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set attendeeSet = new HashSet<>(); - Person person = new Person(name, phone, email, address, tagList); + Event event = new Event(name, contact, phone, email, venue, dateTime, status, comment, tagList, attendeeSet); - return new AddCommand(person); + return new AddCommand(event); } /** @@ -56,5 +71,4 @@ public AddCommand parse(String args) throws ParseException { private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } - } diff --git a/src/main/java/seedu/address/logic/parser/AddCommentCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommentCommandParser.java new file mode 100644 index 000000000000..7a404ec92558 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddCommentCommandParser.java @@ -0,0 +1,54 @@ +//@@author Geraldcdx + +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EMPTY_COMMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMMENT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddCommentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCommentCommand object + */ +public class AddCommentCommandParser implements Parser { + + private String comment = null; + private Index index = null; + + public String getComment() { + return comment; + } + public Index getIndex() { + return index; + } + + /** + * Parses the given {@code String} of arguments in the context of the AddCommentCommand object + * and returns an EditCommand object for execution. + * @param args arguments to work wit + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_COMMENT); + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommentCommand.MESSAGE), pe); + } + + if (!argMultimap.getValue(PREFIX_COMMENT).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommentCommand.MESSAGE)); + } + comment = ParserUtil.parseComment(argMultimap.getValue(PREFIX_COMMENT).get()); + if (comment.length() == 0) { + throw new ParseException(String.format(MESSAGE_INVALID_EMPTY_COMMENT, AddCommentCommand.MESSAGE)); + } + return new AddCommentCommand(index, comment); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..b8da325bac84 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,16 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_CONTACT = new Prefix("c/"); 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_VENUE = new Prefix("v/"); + public static final Prefix PREFIX_DATETIME = new Prefix("d/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_ATTENDEE = new Prefix("a/"); + public static final Prefix PREFIX_USERNAME = new Prefix("u/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("p/"); + public static final Prefix PREFIX_LINE = new Prefix("L/"); + public static final Prefix PREFIX_COMMENT = new Prefix("C/"); + public static final Prefix PREFIX_KEYWORD = new Prefix("k/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommentCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommentCommandParser.java new file mode 100644 index 000000000000..92b71133184c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteCommentCommandParser.java @@ -0,0 +1,50 @@ +//@@author Geraldcdx +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LINE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeleteCommentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommentCommand object + */ +public class DeleteCommentCommandParser implements Parser { + + private int line; + private Index index; + + public int getLine() { + return this.line; + } + + public Index getIndex() { + return this.index; + } + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommentCommand + * and returns an EditCommand object for execution. + * @param args arguments to work with + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCommentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_LINE); + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommentCommand.MESSAGE), pe); + } + + if (!argMultimap.getValue(PREFIX_LINE).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommentCommand.MESSAGE)); + } + line = ParserUtil.parseLine(argMultimap.getValue(PREFIX_LINE).get()); + + return new DeleteCommentCommand(index , line); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea1..8d476ecec215 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,11 +2,15 @@ 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_ATTENDEE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; 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.logic.parser.CliSyntax.PREFIX_VENUE; import java.util.Collection; import java.util.Collections; @@ -15,24 +19,29 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditCommand.EditEventDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Comment; import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object */ -public class EditCommandParser implements Parser { +public class EditCommandParser implements Parser + { /** * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. + * and returns an EditCommand + * object for execution. * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_CONTACT, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_VENUE, PREFIX_DATETIME, PREFIX_COMMENT, PREFIX_TAG, PREFIX_ATTENDEE); Index index; @@ -42,26 +51,41 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditEventDescriptor editEventDescriptor = new EditEventDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + editEventDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_CONTACT).isPresent()) { + editEventDescriptor.setContact(ParserUtil.parseContact(argMultimap.getValue(PREFIX_CONTACT).get())); } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + editEventDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editEventDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_VENUE).isPresent()) { + editEventDescriptor.setVenue(ParserUtil.parseVenue(argMultimap.getValue(PREFIX_VENUE).get())); + } + if (argMultimap.getValue(PREFIX_DATETIME).isPresent()) { + editEventDescriptor.setDate(ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get())); } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + if (argMultimap.getValue(PREFIX_COMMENT).isPresent()) { + String defaultComments = "{span}Comment Section{/span}{ol}{/ol}"; + if (ParserUtil.parseComment(argMultimap.getValue(PREFIX_COMMENT).get()).equals(defaultComments)) { + editEventDescriptor.setComment(new Comment(defaultComments)); + } else { + throw new ParseException("C/ needs to be C/" + defaultComments + "to reset the comment section."); + } } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - if (!editPersonDescriptor.isAnyFieldEdited()) { + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editEventDescriptor::setTags); + parseAttendeesForEdit(argMultimap.getAllValues(PREFIX_ATTENDEE)).ifPresent(editEventDescriptor::setAttendees); + + if (!editEventDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(index, editEventDescriptor); } /** @@ -79,4 +103,20 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Parses {@code Collection attendees} into a {@code Set} if {@code attendees} is non-empty. + * If {@code attendees} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero attendees. + */ + private Optional> parseAttendeesForEdit(Collection attendees) throws ParseException { + assert attendees != null; + + if (attendees.isEmpty()) { + return Optional.empty(); + } + Collection attendeeSet = attendees.size() == 1 && attendees.contains("") + ? Collections.emptySet() : attendees; + return Optional.of(ParserUtil.parseAttendees(attendeeSet)); + } + } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/EventManagerParser.java similarity index 59% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/EventManagerParser.java index b7d57f5db86a..f55747590204 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/EventManagerParser.java @@ -7,24 +7,37 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddCommentCommand; +import seedu.address.logic.commands.AttendingCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteCommentCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.ExportCalendarCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.RegisterCommand; +import seedu.address.logic.commands.ReminderCommand; +import seedu.address.logic.commands.RemoveAttendeeCommand; +import seedu.address.logic.commands.ReplyCommentCommand; import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SignupCommand; import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.UnregisterCommand; +import seedu.address.logic.commands.UpdateStatusCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses user input. */ -public class AddressBookParser { +public class EventManagerParser { /** * Used for initial separation of command word and args. @@ -46,8 +59,18 @@ public Command parseCommand(String userInput) throws ParseException { final String commandWord = matcher.group("commandWord"); final String arguments = matcher.group("arguments"); + switch (commandWord) { + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case SignupCommand.COMMAND_WORD: + return new SignupCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); @@ -84,6 +107,36 @@ public Command parseCommand(String userInput) throws ParseException { case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case RegisterCommand.COMMAND_WORD: + return new RegisterCommandParser().parse(arguments); + + case UnregisterCommand.COMMAND_WORD: + return new UnregisterCommandParser().parse(arguments); + + case RemoveAttendeeCommand.COMMAND_WORD: + return new RemoveAttendeeCommandParser().parse(arguments); + + case AttendingCommand.COMMAND_WORD: + return new AttendingCommand(); + + case AddCommentCommand.COMMAND_WORD: + return new AddCommentCommandParser().parse(arguments); + + case DeleteCommentCommand.COMMAND_WORD: + return new DeleteCommentCommandParser().parse(arguments); + + case ReplyCommentCommand.COMMAND_WORD: + return new ReplyCommentCommandParser().parse(arguments); + + case UpdateStatusCommand.COMMAND_WORD: + return new UpdateStatusCommand(); + + case ReminderCommand.COMMAND_WORD: + return new ReminderCommand(); + + case ExportCalendarCommand.COMMAND_WORD: + return new ExportCalendarCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/ExportCalendarCommandParser.java b/src/main/java/seedu/address/logic/parser/ExportCalendarCommandParser.java new file mode 100644 index 000000000000..6980bd387336 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ExportCalendarCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ExportCalendarCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parser for export command + */ +public class ExportCalendarCommandParser implements Parser { + private static final String SPECIAL_CHARACTERS = "!#$%&'+=~^.@-"; + private static final String FILE_NAME_VALIDATION_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; + + /** + * Parse the given {@code arguments} of Export Command + * and return an ExportCalendar object for executions + * @throws ParseException if arguments is invalid + */ + public ExportCalendarCommand parse(String args) throws ParseException { + String filename = args.trim(); + + if (filename.isEmpty() || filename.length() > 255 || !filename.matches(FILE_NAME_VALIDATION_REGEX)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ExportCalendarCommand.MESSAGE_USAGE)); + } + + return new ExportCalendarCommand(filename); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..2f6ff2845762 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,17 +1,32 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEYWORD; +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.logic.parser.CliSyntax.PREFIX_VENUE; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.event.EventContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + private static final List PREFIXES = Arrays.asList(PREFIX_KEYWORD, PREFIX_NAME, PREFIX_CONTACT, + PREFIX_EMAIL, PREFIX_PHONE, PREFIX_VENUE, PREFIX_DATETIME, PREFIX_TAG); /** * Parses the given {@code String} of arguments in the context of the FindCommand @@ -19,15 +34,47 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_KEYWORD, PREFIX_NAME, + PREFIX_CONTACT, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_VENUE, PREFIX_DATETIME, PREFIX_TAG); + + if (!anyPrefixesPresent(argMultimap, PREFIX_KEYWORD, PREFIX_NAME, PREFIX_CONTACT, + PREFIX_PHONE, PREFIX_EMAIL, PREFIX_VENUE, PREFIX_DATETIME, PREFIX_TAG)) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + Map > keywordsMap = new HashMap<>(); + for (Prefix prefix : PREFIXES) { + mapPrefixAndKeywords(keywordsMap, prefix, argMultimap); + } + return new FindCommand( + new EventContainsKeywordsPredicate(keywordsMap)); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + /** + * Map presented prefix with its' list of keywords + * @param keywordMap hash map for prefix and list of keyword + * @param prefix prefix to map + * @param argMultimap to check for prefix present + */ + public void mapPrefixAndKeywords(Map > keywordMap, Prefix prefix, + ArgumentMultimap argMultimap) { + if (argMultimap.getValue(prefix).isPresent()) { + List combineAllSamePrefixKeywordsList = new ArrayList<>(); + for (String singlePrefix : argMultimap.getAllValues(prefix)) { + combineAllSamePrefixKeywordsList.addAll(Arrays.asList(singlePrefix.trim().split("\\s+"))); + } + keywordMap.put(prefix, combineAllSamePrefixKeywordsList); + } else { + keywordMap.put(prefix, null); + } } + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } } diff --git a/src/main/java/seedu/address/logic/parser/LoginCommandParser.java b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java new file mode 100644 index 000000000000..853b82e19e7b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.user.Password; +import seedu.address.model.user.User; +import seedu.address.model.user.Username; + +/** + * Parses input arguments and creates a new LoginCommand object. + */ +public class LoginCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the LoginCommand + * and returns an LoginCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + Username username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + + User user = new User(username, password); + + return new LoginCommand(user); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..727a10be058a 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,6 +1,7 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_LINE_INVALID; import java.util.Collection; import java.util.HashSet; @@ -9,11 +10,16 @@ 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.attendee.Attendee; +import seedu.address.model.event.Contact; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Email; +import seedu.address.model.event.Name; +import seedu.address.model.event.Phone; +import seedu.address.model.event.Venue; import seedu.address.model.tag.Tag; +import seedu.address.model.user.Password; +import seedu.address.model.user.Username; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -35,6 +41,61 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a {@code String username} into {@code Username}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code username} is invalid. + */ + public static Username parseUsername(String username) throws ParseException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new ParseException(Username.MESSAGE_USERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername); + } + + /** + * Parses a {@code String password} into {@code Password}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code password} is invalid. + */ + public static Password parsePassword(String password) throws ParseException { + requireNonNull(password); + String trimmedPassword = password.trim(); + if (!Password.isValidPassword(trimmedPassword)) { + throw new ParseException(Password.MESSAGE_PASSWORD_CONSTRAINTS); + } + return new Password(trimmedPassword); + } + + /** + * 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 int parseLine(String line) throws ParseException { + String trimmedLine = line.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedLine)) { + throw new ParseException(MESSAGE_LINE_INVALID); + } + return Integer.parseInt(trimmedLine); + } + + /** + * Parses a {@code String comment} into an {@code Comment}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code comment} is invalid. + */ + public static String parseComment(String comment) { + requireNonNull(comment); + String trimmedComment = comment.trim(); + return trimmedComment; + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -50,6 +111,21 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code String contact} into a {@code Contact}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code contact} is invalid. + */ + public static Contact parseContact(String contact) throws ParseException { + requireNonNull(contact); + String trimmedContact = contact.trim(); + if (!Contact.isValidContact(trimmedContact)) { + throw new ParseException(Contact.MESSAGE_CONTACT_CONSTRAINTS); + } + return new Contact(trimmedContact); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -66,18 +142,18 @@ public static Phone parsePhone(String phone) throws ParseException { } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String venue} into an {@code Venue}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code venue} is invalid. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + public static Venue parseVenue(String venue) throws ParseException { + requireNonNull(venue); + String trimmedVenue = venue.trim(); + if (!Venue.isValidVenue(trimmedVenue)) { + throw new ParseException(Venue.MESSAGE_VENUE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Venue(trimmedVenue); } /** @@ -95,6 +171,21 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parse a{@code String datetimeAsString into an {@code DateTime}} + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code datetimeAsString} is invalid. + */ + public static DateTime parseDateTime(String datetimeAsString) throws ParseException { + requireNonNull(datetimeAsString); + String trimmedDateTime = datetimeAsString.trim(); + if (!DateTime.isValidDateTime(datetimeAsString)) { + throw new ParseException(DateTime.MESSAGE_DATETIME_CONSTRAINTS); + } + return new DateTime(trimmedDateTime); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +212,31 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String attendee} into a {@code Attendee}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code attendee} is invalid. + */ + public static Attendee parseAttendee(String attendee) throws ParseException { + requireNonNull(attendee); + String trimmedAttendee = attendee.trim(); + if (!Attendee.isValidAttendeeName(trimmedAttendee)) { + throw new ParseException(Attendee.MESSAGE_ATTENDEE_CONSTRAINTS); + } + return new Attendee(trimmedAttendee); + } + + /** + * Parses {@code Collection attendees} into a {@code Set}. + */ + public static Set parseAttendees(Collection attendees) throws ParseException { + requireNonNull(attendees); + final Set attendeeSet = new HashSet<>(); + for (String attendeeName : attendees) { + attendeeSet.add(parseAttendee(attendeeName)); + } + return attendeeSet; + } } diff --git a/src/main/java/seedu/address/logic/parser/RegisterCommandParser.java b/src/main/java/seedu/address/logic/parser/RegisterCommandParser.java new file mode 100644 index 000000000000..5136b32c5cf2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RegisterCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RegisterCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new RegisterCommand object + */ +public class RegisterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RegisterCommand + * and returns an RegisterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RegisterCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new RegisterCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RegisterCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/RemoveAttendeeCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveAttendeeCommandParser.java new file mode 100644 index 000000000000..eda440a9b9d3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemoveAttendeeCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.RemoveAttendeeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.user.Username; + +/** + * Parses input arguments and creates a new RemoveAttendeeCommand object + */ +public class RemoveAttendeeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveAttendeeCommand + * and returns an RemoveAttendeeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveAttendeeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveAttendeeCommand.MESSAGE_USAGE), pe); + } + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveAttendeeCommand.MESSAGE_USAGE)); + } + + Username username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + + return new RemoveAttendeeCommand(index, username); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ReplyCommentCommandParser.java b/src/main/java/seedu/address/logic/parser/ReplyCommentCommandParser.java new file mode 100644 index 000000000000..d61504138a1a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ReplyCommentCommandParser.java @@ -0,0 +1,67 @@ +//@@author Geraldcdx +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_EMPTY_COMMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COMMENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ReplyCommentCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ReplyCommentCommand object + */ +public class ReplyCommentCommandParser implements Parser { + + private int line; + private String comment; + private Index index; + + public int getLine() { + return this.line; + } + + public String getComment() { + return this.comment; + } + + public Index getIndex() { + return this.index; + } + + /** + * Parses the given {@code String} of arguments in the context of the ReplyCommentCommand object + * and returns an EditCommand object for execution. + * @param args arguments to work with + * @throws ParseException if the user input does not conform the expected format + */ + public ReplyCommentCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_LINE, PREFIX_COMMENT, PREFIX_NAME); + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReplyCommentCommand.MESSAGE), pe); + } + + if (!argMultimap.getValue(PREFIX_LINE).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReplyCommentCommand.MESSAGE)); + } + line = ParserUtil.parseLine(argMultimap.getValue(PREFIX_LINE).get()); + if (!argMultimap.getValue(PREFIX_COMMENT).isPresent()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReplyCommentCommand.MESSAGE)); + } + comment = ParserUtil.parseComment(argMultimap.getValue(PREFIX_COMMENT).get()); + if (comment.length() == 0) { + throw new ParseException(String.format(MESSAGE_INVALID_EMPTY_COMMENT, ReplyCommentCommand.MESSAGE)); + } + + return new ReplyCommentCommand(index , line , comment); + } +} diff --git a/src/main/java/seedu/address/logic/parser/SignupCommandParser.java b/src/main/java/seedu/address/logic/parser/SignupCommandParser.java new file mode 100644 index 000000000000..3b2da789afa0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SignupCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.SignupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.user.Password; +import seedu.address.model.user.User; +import seedu.address.model.user.Username; + +/** + * Parses input arguments and creates a new SignupCommand object. + */ +public class SignupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SignupCommand + * and returns an SignupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SignupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SignupCommand.MESSAGE_USAGE)); + } + + Username username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + + User user = new User(username, password); + + return new SignupCommand(user); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnregisterCommandParser.java b/src/main/java/seedu/address/logic/parser/UnregisterCommandParser.java new file mode 100644 index 000000000000..a8d3d3a0027b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnregisterCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.UnregisterCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new RegisterCommand object + */ +public class UnregisterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RegisterCommand + * and returns an RegisterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnregisterCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new UnregisterCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnregisterCommand.MESSAGE_USAGE), pe); + } + } +} 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 7f85c8b9258b..000000000000 --- 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 an 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 updatePerson(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/DateTimeUtil.java b/src/main/java/seedu/address/model/DateTimeUtil.java new file mode 100644 index 000000000000..1525a612bc08 --- /dev/null +++ b/src/main/java/seedu/address/model/DateTimeUtil.java @@ -0,0 +1,37 @@ +package seedu.address.model; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +/** + * This class is utility class for method related to @DateTime field + * and system clock + */ +public class DateTimeUtil { + public static final DateFormat PAGE_DATE_FORMAT = + new SimpleDateFormat("EEEEE dd-MMMMM-yyyy 'at' HH:mm", Locale.US); + + /** + * Utility method to get system current timestamp + * @return current Date + */ + public static Date getCurrentDateTime() { + return new Date(); + } + + /** + * Compare to know how many TimeUnit until or past the date of comparision + * TimeUnit: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS + */ + public static long daysDiff(Date eventDate, Date currentDate, TimeUnit timeUnit) { + requireAllNonNull(eventDate, currentDate, timeUnit); + + return timeUnit.HOURS.convert( + eventDate.getTime() - currentDate.getTime(), timeUnit); + } +} diff --git a/src/main/java/seedu/address/model/EventManager.java b/src/main/java/seedu/address/model/EventManager.java new file mode 100644 index 000000000000..0949f020a3e1 --- /dev/null +++ b/src/main/java/seedu/address/model/EventManager.java @@ -0,0 +1,120 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.event.Event; +import seedu.address.model.event.UniqueEventList; + +/** + * Wraps all data at the event-manager level + * Duplicates are not allowed (by .isSameEvent comparison) + */ +public class EventManager implements ReadOnlyEventManager { + + private final UniqueEventList events; + + /* + * The 'unusual' code block below is an 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. + */ + { + events = new UniqueEventList(); + } + + public EventManager() {} + + /** + * Creates an EventManager using the Events in the {@code toBeCopied} + */ + public EventManager(ReadOnlyEventManager toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the event list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + this.events.setEvents(events); + } + + /** + * Resets the existing data of this {@code EventManager} with {@code newData}. + */ + public void resetData(ReadOnlyEventManager newData) { + requireNonNull(newData); + + setEvents(newData.getEventList()); + } + + //// event-level operations + + /** + * Returns true if a event with the same identity as {@code event} exists in the event manager. + */ + public boolean hasEvent(Event event) { + requireNonNull(event); + return events.contains(event); + } + + /** + * Adds a event to the event manager. + * The event must not already exist in the event manager. + */ + public void addEvent(Event p) { + events.add(p); + } + + /** + * Replaces the given event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the event manager. + * The event identity of {@code editedEvent} must not be the same as another existing event in the event manager. + */ + public void updateEvent(Event target, Event editedEvent) { + requireNonNull(editedEvent); + + events.setEvent(target, editedEvent); + } + + /** + * Removes {@code key} from this {@code EventManager}. + * {@code key} must exist in the event manager. + */ + public void removeEvent(Event key) { + events.remove(key); + } + + //// util methods + + @Override + public String toString() { + return events.asUnmodifiableObservableList().size() + " events"; + // TODO: refine later + } + + @Override + public ObservableList getEventList() { + return events.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventManager // instanceof handles nulls + && events.equals(((EventManager) other).events)); + } + + @Override + public int hashCode() { + return events.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..5bcc9dd2a378 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,76 +3,120 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.event.Event; +import seedu.address.model.user.User; +import seedu.address.model.user.Username; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_EVENTS = unused -> true; /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + void resetData(ReadOnlyEventManager newData); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** Returns the EventManager */ + ReadOnlyEventManager getEventManager(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Creates a new user profile in the Event Manager. */ - boolean hasPerson(Person person); + void createUser(User user); /** - * Deletes the given person. - * The person must exist in the address book. + * Returns true if a user account is registered in the Event Manager. */ - void deletePerson(Person target); + boolean userExists(User user); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Returns true if a user is logged in. */ - void addPerson(Person person); + boolean getLoginStatus(); /** - * 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. + * Returns true if an admin is logged in. */ - void updatePerson(Person target, Person editedPerson); + boolean getAdminStatus(); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Logs user into Event Manager. + */ + void logUser(User user); + + /** + * Returns logged in user. + */ + Username getUsername(); + + /** + * Logs user out of the Event Manager. + */ + void clearUser(); + + /** + * Returns true if a event with the same identity as {@code event} exists in the event manager. + */ + boolean hasEvent(Event event); + + /** + * Deletes the given event. + * The event must exist in the event manager. + */ + void deleteEvent(Event target); + + /** + * Adds the given event. + * {@code event} must not already exist in the event manager. + */ + void addEvent(Event event); + + /** + * Replaces the given event {@code target} with {@code editedEvent}. + * {@code target} must exist in the event manager. + * The event identity of {@code editedEvent} must not be the same as another existing event in the event manager. + */ + void updateEvent(Event target, Event editedEvent); + + /** Returns an unmodifiable view of the filtered event list */ + ObservableList getFilteredEventList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered event list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredEventList(Predicate predicate); + + /** + * Get the current list of event that the current user + * @param currentUser current User + * @return an user registered event list + */ + ObservableList getAttendingEventList(Username currentUser); /** - * Returns true if the model has previous address book states to restore. + * Returns true if the model has previous event manager states to restore. */ - boolean canUndoAddressBook(); + boolean canUndoEventManager(); /** - * Returns true if the model has undone address book states to restore. + * Returns true if the model has undone event manager states to restore. */ - boolean canRedoAddressBook(); + boolean canRedoEventManager(); /** - * Restores the model's address book to its previous state. + * Restores the model's event manager to its previous state. */ - void undoAddressBook(); + void undoEventManager(); /** - * Restores the model's address book to its previously undone state. + * Restores the model's event manager to its previously undone state. */ - void redoAddressBook(); + void redoEventManager(); /** - * Saves the current address book state for undo/redo. + * Saves the current event manager state for undo/redo. */ - void commitAddressBook(); + void commitEventManager(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..dc499b7e1c9c 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,122 +11,185 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.model.EventManagerChangedEvent; +import seedu.address.model.event.AttendanceContainsUserPredicate; +import seedu.address.model.event.Event; +import seedu.address.model.user.User; +import seedu.address.model.user.Username; /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of the event manager data. */ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final VersionedAddressBook versionedAddressBook; - private final FilteredList filteredPersons; + private final VersionedEventManager versionedEManager; + private final FilteredList filteredEvents; + private final UserSession userSession; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given eventManager and userPrefs. + * FilteredEvents will be default to be automatically sorted by DateTime */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyEventManager eventManager, UserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(eventManager, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with event manager: " + eventManager + " and user prefs " + userPrefs); - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + userSession = new UserSession(); + versionedEManager = new VersionedEventManager(eventManager); + filteredEvents = new FilteredList<>(versionedEManager.getEventList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new EventManager(), new UserPrefs()); } @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); + public void resetData(ReadOnlyEventManager newData) { + versionedEManager.resetData(newData); + indicateEManagerChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; + public ReadOnlyEventManager getEventManager() { + return versionedEManager; } /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); + private void indicateEManagerChanged() { + raise(new EventManagerChangedEvent(versionedEManager)); } + //=========== Authentication Accessors ============================================================= + + //@@ jamesyaputra + @Override + public boolean getLoginStatus() { + return userSession.getLoginStatus(); + } + + //@@ jamesyaputra + @Override + public boolean getAdminStatus() { + return userSession.getAdminStatus(); + } + + //@@ jamesyaputra + @Override + public boolean userExists(User user) { + requireNonNull(user); + return userSession.userExists(user); + } + + //@@ jamesyaputra + @Override + public void createUser(User user) { + requireNonNull(user); + userSession.createUser(user); + } + + //@@ jamesyaputra @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); + public void logUser(User user) { + requireNonNull(user); + userSession.logUser(user); } + //@@ jamesyaputra @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); + public Username getUsername() { + return userSession.getUsername(); } + //@@ jamesyaputra @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public void clearUser() { + userSession.clearUser(); } + //=========== Event List Accessors and Modifiers ============================================================= @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public boolean hasEvent(Event event) { + requireNonNull(event); + return versionedEManager.hasEvent(event); + } - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + @Override + public void deleteEvent(Event target) { + versionedEManager.removeEvent(target); + indicateEManagerChanged(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void addEvent(Event event) { + versionedEManager.addEvent(event); + updateFilteredEventList(PREDICATE_SHOW_ALL_EVENTS); + indicateEManagerChanged(); + } + + @Override + public void updateEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + versionedEManager.updateEvent(target, editedEvent); + indicateEManagerChanged(); + } + + //=========== Filtered Event List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * Returns an unmodifiable view of the list of {@code Event} backed by the internal list of + * {@code versionedEManager} */ @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList getFilteredEventList() { + return FXCollections.unmodifiableObservableList(filteredEvents); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredEventList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredEvents.setPredicate(predicate); + } + + //@@ tertium3 + @Override + public ObservableList getAttendingEventList(Username currentUser) { + requireNonNull(currentUser); + updateFilteredEventList(new AttendanceContainsUserPredicate(currentUser)); + + return getFilteredEventList(); } //=========== Undo/Redo ================================================================================= @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); + public boolean canUndoEventManager() { + return versionedEManager.canUndo(); } @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); + public boolean canRedoEventManager() { + return versionedEManager.canRedo(); } @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); + public void undoEventManager() { + versionedEManager.undo(); + indicateEManagerChanged(); } @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); + public void redoEventManager() { + versionedEManager.redo(); + indicateEManagerChanged(); } @Override - public void commitAddressBook() { - versionedAddressBook.commit(); + public void commitEventManager() { + versionedEManager.commit(); } @Override @@ -143,8 +206,9 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + return versionedEManager.equals(other.versionedEManager) + && filteredEvents.equals(other.filteredEvents) + && userSession.equals(other.userSession); } } 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 6ddc2cd9a290..000000000000 --- 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/ReadOnlyEventManager.java b/src/main/java/seedu/address/model/ReadOnlyEventManager.java new file mode 100644 index 000000000000..70ce070b26fa --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyEventManager.java @@ -0,0 +1,17 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.event.Event; + +/** + * Unmodifiable view of an event manager + */ +public interface ReadOnlyEventManager { + + /** + * Returns an unmodifiable view of the events list. + * This list will not contain any duplicate events. + */ + ObservableList getEventList(); + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..c51ad67fedc0 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,7 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path eventManagerFilePath = Paths.get("data" , "eventmanager.xml"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -30,12 +30,12 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getEventManagerFilePath() { + return eventManagerFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setEventManagerFilePath(Path eventManagerFilePath) { + this.eventManagerFilePath = eventManagerFilePath; } @Override @@ -50,19 +50,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath); + && Objects.equals(eventManagerFilePath, o.eventManagerFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, eventManagerFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + eventManagerFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/UserSession.java b/src/main/java/seedu/address/model/UserSession.java new file mode 100644 index 000000000000..b9b8f7faa057 --- /dev/null +++ b/src/main/java/seedu/address/model/UserSession.java @@ -0,0 +1,154 @@ +package seedu.address.model; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.util.PasswordUtil; +import seedu.address.commons.util.StringUtil; +import seedu.address.model.user.Password; +import seedu.address.model.user.User; +import seedu.address.model.user.Username; +import seedu.address.storage.JsonUserStorage; + +//@@ jamesyaputra +/** + * Represents user account authentication + */ +public class UserSession { + + private static final Logger logger = LogsCenter.getLogger(UserSession.class); + + private JsonUserStorage userStorage; + private User user; + private boolean loginStatus; + private boolean adminStatus; + + /** + * The constructor initializes the user with a stub username and password. + */ + public UserSession() { + final Path userFilePath = Paths.get("users.json"); + final Username username = new Username("admin"); + final Password password = new Password("root"); + user = new User(username, password); + loginStatus = false; + adminStatus = false; + + try { + userStorage = new JsonUserStorage(userFilePath); + } catch (IOException e) { + e.printStackTrace(); + } + + createUser(user); + } + + /** + * Returns true if user exists in the JSON file. + */ + public boolean userExists(User user) { + String loggedUsername = user.getUsername().toString(); + Map userAccounts = userStorage.getUserAccounts(); + + return userAccounts.containsKey(loggedUsername); + } + + /** + * Sets the current user. + */ + public void logUser(User user) { + String loggedUsername = user.getUsername().toString(); + String loggedPassword = user.getPassword().toString(); + Map userAccounts = userStorage.getUserAccounts(); + + if (userAccounts.containsKey(loggedUsername)) { + boolean passwordsMatch = false; + String storedPassword = userAccounts.get(loggedUsername); + + try { + passwordsMatch = PasswordUtil.validatePassword(loggedPassword, storedPassword); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + logger.warning("PasswordUtil could not validate password : " + StringUtil.getDetails(e)); + } + + if (passwordsMatch) { + this.user = user; + loginStatus = true; + adminStatus = loggedUsername.equals("admin"); + } + } + } + + /** + * Creates a new user profile in the JSON file. + */ + public void createUser(User user) { + String loggedUsername = user.getUsername().toString(); + String loggedPassword = user.getPassword().toString(); + + try { + String encryptedPassword = PasswordUtil.getEncryptedPassword(loggedPassword); + userStorage.createUser(loggedUsername, encryptedPassword); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + logger.warning("PasswordUtil could not generate encrypted password : " + StringUtil.getDetails(e)); + } catch (FileNotFoundException e) { + logger.warning("users.json not found in file path : " + StringUtil.getDetails(e)); + } catch (IOException e) { + logger.warning("JSONUserStorage could not read or write to users.json : " + StringUtil.getDetails(e)); + } + } + + /** + * Returns logged in user's username. + */ + public Username getUsername() { + return user.getUsername(); + } + + /** + * Clears the current user. + */ + public void clearUser() { + loginStatus = false; + } + + /** + * Returns true if user is logged in. + */ + public boolean getLoginStatus() { + return loginStatus; + } + + /** + * Returns true if admin is logged in. + */ + public boolean getAdminStatus() { + return adminStatus; + } + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof UserSession)) { + return false; + } + + // state check + UserSession other = (UserSession) obj; + return user.equals(other.user) + && loginStatus == other.loginStatus + && adminStatus == other.adminStatus; + } +} diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedEventManager.java similarity index 53% rename from src/main/java/seedu/address/model/VersionedAddressBook.java rename to src/main/java/seedu/address/model/VersionedEventManager.java index 227a335045d7..dbeead409cc9 100644 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ b/src/main/java/seedu/address/model/VersionedEventManager.java @@ -4,69 +4,69 @@ import java.util.List; /** - * {@code AddressBook} that keeps track of its own history. + * {@code EventManager} that keeps track of its own history. */ -public class VersionedAddressBook extends AddressBook { +public class VersionedEventManager extends EventManager { - private final List addressBookStateList; + private final List eventManagerStateList; private int currentStatePointer; - public VersionedAddressBook(ReadOnlyAddressBook initialState) { + public VersionedEventManager(ReadOnlyEventManager initialState) { super(initialState); - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); + eventManagerStateList = new ArrayList<>(); + eventManagerStateList.add(new EventManager(initialState)); currentStatePointer = 0; } /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. + * Saves a copy of the current {@code EventManager} state at the end of the state list. * Undone states are removed from the state list. */ public void commit() { removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); + eventManagerStateList.add(new EventManager(this)); currentStatePointer++; } private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); + eventManagerStateList.subList(currentStatePointer + 1, eventManagerStateList.size()).clear(); } /** - * Restores the address book to its previous state. + * Restores the event manager to its previous state. */ public void undo() { if (!canUndo()) { throw new NoUndoableStateException(); } currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(eventManagerStateList.get(currentStatePointer)); } /** - * Restores the address book to its previously undone state. + * Restores the event manager to its previously undone state. */ public void redo() { if (!canRedo()) { throw new NoRedoableStateException(); } currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(eventManagerStateList.get(currentStatePointer)); } /** - * Returns true if {@code undo()} has address book states to undo. + * Returns true if {@code undo()} has event manager states to undo. */ public boolean canUndo() { return currentStatePointer > 0; } /** - * Returns true if {@code redo()} has address book states to redo. + * Returns true if {@code redo()} has event manager states to redo. */ public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; + return currentStatePointer < eventManagerStateList.size() - 1; } @Override @@ -77,15 +77,15 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { + if (!(other instanceof VersionedEventManager)) { return false; } - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; + VersionedEventManager otherVersionedAddressBook = (VersionedEventManager) other; // state check return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) + && eventManagerStateList.equals(otherVersionedAddressBook.eventManagerStateList) && currentStatePointer == otherVersionedAddressBook.currentStatePointer; } @@ -94,7 +94,7 @@ public boolean equals(Object other) { */ public static class NoUndoableStateException extends RuntimeException { private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); + super("Current state pointer at start of eventManagerState list, unable to undo."); } } @@ -103,7 +103,7 @@ private NoUndoableStateException() { */ public static class NoRedoableStateException extends RuntimeException { private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); + super("Current state pointer at end of eventManagerState list, unable to redo."); } } } diff --git a/src/main/java/seedu/address/model/attendee/Attendee.java b/src/main/java/seedu/address/model/attendee/Attendee.java new file mode 100644 index 000000000000..b46f06a1bf4f --- /dev/null +++ b/src/main/java/seedu/address/model/attendee/Attendee.java @@ -0,0 +1,56 @@ +//@@author cqinkai +package seedu.address.model.attendee; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Attendee in the event manager. + * Guarantees: immutable; attendee name is valid as declared in {@link #isValidAttendeeName(String)} + */ +public class Attendee { + + public static final String MESSAGE_ATTENDEE_CONSTRAINTS = + "Attendee names should only contain alphanumeric characters and spaces"; + public static final String ATTENDEE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String attendeeName; + + /** + * Constructs a {@code Attendee}. + * + * @param attendeeName A valid Attendee name. + */ + public Attendee(String attendeeName) { + requireNonNull(attendeeName); + checkArgument(isValidAttendeeName(attendeeName), MESSAGE_ATTENDEE_CONSTRAINTS); + this.attendeeName = attendeeName; + } + + /** + * Returns true if a given string is a valid attendee name. + */ + public static boolean isValidAttendeeName(String test) { + return test.matches(ATTENDEE_VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Attendee // instanceof handles nulls + && attendeeName.equals(((Attendee) other).attendeeName)); // state check + } + + @Override + public int hashCode() { + return attendeeName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + attendeeName + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/event/AttendanceContainsUserPredicate.java b/src/main/java/seedu/address/model/event/AttendanceContainsUserPredicate.java new file mode 100644 index 000000000000..22c80bf7c7b7 --- /dev/null +++ b/src/main/java/seedu/address/model/event/AttendanceContainsUserPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.event; + +import java.util.function.Predicate; + +import seedu.address.model.attendee.Attendee; +import seedu.address.model.user.Username; + +/** + * Predicate to find user with given name + */ +public class AttendanceContainsUserPredicate implements Predicate { + private final String username; + + public AttendanceContainsUserPredicate(Username userName) { + this.username = userName.value; + } + + @Override + public boolean test (Event event) { + return event.getAttendance().contains(new Attendee(username)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AttendanceContainsUserPredicate // instanceof handles nulls + && username.equals(((AttendanceContainsUserPredicate) other).username)); // state check + } +} diff --git a/src/main/java/seedu/address/model/event/Comment.java b/src/main/java/seedu/address/model/event/Comment.java new file mode 100644 index 000000000000..ed6cd6ac8c65 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Comment.java @@ -0,0 +1,60 @@ +//@@author cqinkai +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's comment in the event manager. + * Guarantees: immutable; is valid as declared in {@link #isValidComment(String)} + */ +public class Comment { + + public static final String MESSAGE_COMMENT_CONSTRAINTS = + "Comment can take any values, and it should not be blank"; + + /* + * The first character of the comment must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String COMMENT_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs a {@code Comment}. + * + * @param comment A valid comment. + */ + public Comment(String comment) { + requireNonNull(comment); + checkArgument(isValidComment(comment), MESSAGE_COMMENT_CONSTRAINTS); + value = comment; + } + + /** + * Returns true if a given string is a valid comment. + */ + public static boolean isValidComment(String test) { + return test.matches(COMMENT_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Comment // instanceof handles nulls + && value.equals(((Comment) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/Contact.java b/src/main/java/seedu/address/model/event/Contact.java new file mode 100644 index 000000000000..626a615fe25f --- /dev/null +++ b/src/main/java/seedu/address/model/event/Contact.java @@ -0,0 +1,60 @@ +//@@author cqinkai +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Event's contact in the event manager. + * Guarantees: immutable; is valid as declared in {@link #isValidContact(String)} + */ +public class Contact { + + public static final String MESSAGE_CONTACT_CONSTRAINTS = + "Contact names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the contact must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String CONTACT_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullContactName; + + /** + * Constructs a {@code Contact}. + * + * @param contact A valid contact. + */ + public Contact(String contact) { + requireNonNull(contact); + checkArgument(isValidContact(contact), MESSAGE_CONTACT_CONSTRAINTS); + fullContactName = contact; + } + + /** + * Returns true if a given string is a valid contact. + */ + public static boolean isValidContact(String test) { + return test.matches(CONTACT_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullContactName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Contact // instanceof handles nulls + && fullContactName.equals(((Contact) other).fullContactName)); // state check + } + + @Override + public int hashCode() { + return fullContactName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/DateTime.java b/src/main/java/seedu/address/model/event/DateTime.java new file mode 100644 index 000000000000..26c20c59b209 --- /dev/null +++ b/src/main/java/seedu/address/model/event/DateTime.java @@ -0,0 +1,76 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents an Event's DateTime field in the event manager. + */ +public class DateTime { + public static final String MESSAGE_DATETIME_CONSTRAINTS = + "Date should have all value and should be in the format\n" + + "dd/MM/yyyy HH:mm with each is a number within\n" + + "Month: 1 - 12\n" + + "Date: 1 - 31\n" + + "Hour: 0-23\n" + + "Minute: 0-59\n"; + private static final DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm"); + + public final Date dateTime; + + //Create DateTime from the input + public DateTime(String dateTimeAsString) { + dateFormat.setLenient(false); + Date dateTime1 = new Date(); + //Check error + requireNonNull(dateTimeAsString); + + try { + dateTime1 = dateFormat.parse(dateTimeAsString); + } catch (ParseException e) { + checkArgument(false, MESSAGE_DATETIME_CONSTRAINTS); + } + dateTime = dateTime1; + } + + /** + * Check if string has correct datetime format + * @param dateTimeAsString + * @return boolean + */ + public static boolean isValidDateTime (String dateTimeAsString) { + if (dateTimeAsString == null) { + throw new NullPointerException(); + } + + try { + dateFormat.setLenient(false); + dateFormat.parse(dateTimeAsString); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public String toString() { + return dateFormat.format(dateTime); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTime // instanceof handles nulls + && dateTime.equals(((DateTime) other).dateTime)); // state check + } + + @Override + public int hashCode() { + return dateTime.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/Description.java b/src/main/java/seedu/address/model/event/Description.java new file mode 100644 index 000000000000..d6c64e9be4a7 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Description.java @@ -0,0 +1,36 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; + +/** + * Event description + */ +public class Description { + public final String description; + + /** + * Constructs a {@code Description}. + */ + public Description(String eventDescription) { + requireNonNull(eventDescription); + description = eventDescription; + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Name // instanceof handles nulls + && description.equals(((Description) other).description)); // state check + } + + @Override + public int hashCode() { + return description.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/event/Email.java similarity index 93% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/event/Email.java index 4a8283a20002..6e41304cad2e 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/event/Email.java @@ -1,15 +1,15 @@ -package seedu.address.model.person; +package seedu.address.model.event; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Event's email in the event manager. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; + private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; public static final String MESSAGE_EMAIL_CONSTRAINTS = "Emails should be of the format local-part@domain " + "and adhere to the following constraints:\n" + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " diff --git a/src/main/java/seedu/address/model/event/Event.java b/src/main/java/seedu/address/model/event/Event.java new file mode 100644 index 000000000000..2083ac88ef16 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Event.java @@ -0,0 +1,196 @@ +package seedu.address.model.event; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import seedu.address.model.attendee.Attendee; +import seedu.address.model.tag.Tag; + +/** + * Represents an Event in the event manager. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Event { + // Identity fields + private final Name name; + private final Contact contact; + private final Phone phone; + private final Email email; + + // Data fields + private final Venue venue; + private final DateTime dateTime; + private final Set tags = new HashSet<>(); + private final Set attendees = new HashSet<>(); + private final Status status; + private final Comment comment; + + /** + * Every field must be present and not null. + */ + public Event(Name name, Contact contact, Phone phone, Email email, Venue venue, DateTime datetime, Status status, + Comment comment, Set tags, Set attendees) { + requireAllNonNull(name, contact, phone, email, venue, datetime, status); + + this.name = name; + this.contact = contact; + this.phone = phone; + this.email = email; + this.venue = venue; + this.dateTime = datetime; + this.tags.addAll(tags); + this.attendees.addAll(attendees); + this.status = status; + this.comment = comment; + } + + public Name getName() { + return name; + } + + public Contact getContact() { + return contact; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Venue getVenue() { + return venue; + } + + public DateTime getDateTime () { + return dateTime; + } + + public Status getStatus () { + return status; + } + + public Comment getComment () { + return comment; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns tags formatted as a string to be passed into Event Page HTML as query string parameter + */ + public String getTagsString() { + List tagsList = new ArrayList<>(); + for (Tag t: tags) { + tagsList.add(t.tagName); + } + String tagsString = String.join(" ", tagsList); + return tagsString; + } + + /** + * Returns an immutable attendee set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getAttendance() { + return Collections.unmodifiableSet(attendees); + } + + /** + * Returns attendee list formatted as a string to be passed into Event Page HTML as query string parameter + */ + public String getAttendanceString() { + TreeSet attendeesSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + for (Attendee a: attendees) { + attendeesSet.add(a.attendeeName); + } + String attendeesString = String.join("
", attendeesSet); + return attendeesString; + } + + /** + * Returns true if both events of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two events. + */ + public boolean isSameEvent(Event otherEvent) { + if (otherEvent == this) { + return true; + } + + return otherEvent != null + && otherEvent.getName().equals(getName()) + && (otherEvent.getPhone().equals(getPhone()) || otherEvent.getEmail().equals(getEmail()) + || otherEvent.getContact().equals(getContact())); + } + + /** + * Returns true if both events have the same identity and data fields. + * This defines a stronger notion of equality between two events. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return otherEvent.getName().equals(getName()) + && otherEvent.getContact().equals(getContact()) + && otherEvent.getPhone().equals(getPhone()) + && otherEvent.getEmail().equals(getEmail()) + && otherEvent.getVenue().equals(getVenue()) + && otherEvent.getDateTime().equals(getDateTime()) + && otherEvent.getTags().equals(getTags()) + && otherEvent.getAttendance().equals(getAttendance()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, contact, phone, email, venue, dateTime, status, comment, tags, attendees); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Contact: ") + .append(getContact()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Venue: ") + .append(getVenue()) + .append(" Time: ") + .append(getDateTime()) + .append(" Comment: ") + .append(getComment()) + .append(" Tags: "); + getTags().forEach(builder::append); + + builder.append(" Attendees: "); + getAttendance().forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java new file mode 100644 index 000000000000..22efbaf99023 --- /dev/null +++ b/src/main/java/seedu/address/model/event/EventContainsKeywordsPredicate.java @@ -0,0 +1,196 @@ +package seedu.address.model.event; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ATTENDEE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEYWORD; +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.logic.parser.CliSyntax.PREFIX_VENUE; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.logic.parser.Prefix; + +/** + * Tests that a {@code Event}'s {@code DateTime, Name or Tags} matches any of the keywords given. + * field find by DateTime, Address, and Name + */ +public class EventContainsKeywordsPredicate implements Predicate { + private final Map > keywords; + + public EventContainsKeywordsPredicate(Map > keywords) { + this.keywords = keywords; + } + + //TODO: update when contact is added + @Override + public boolean test(Event event) { + return checkKeywordsMatchEventData(keywords.get(PREFIX_KEYWORD), event) + && checkNameKeywordsMatchEventName(keywords.get(PREFIX_NAME), event) + && checkContactKeywordsMatchEventContact(keywords.get(PREFIX_CONTACT), event) + && checkEmailKeywordsMatchEventEmail(keywords.get(PREFIX_EMAIL), event) + && checkPhoneKeywordsMatchEventPhone(keywords.get(PREFIX_PHONE), event) + && checkVenueKeywordsMatchEventVenue(keywords.get(PREFIX_VENUE), event) + && checkDateTimeKeywordsMatchEventDateTime(keywords.get(PREFIX_DATETIME), event) + && checkTagKeywordsMatchEventTag(keywords.get(PREFIX_TAG), event) + && checkAttendeeKeywordsMatchEventAttendee(keywords.get(PREFIX_ATTENDEE), event); + } + + /** + * Check if any keyword in list of given keywords are contained in any fields of an event + * @param keywords list of keywords + * @return a boolean + */ + public boolean checkKeywordsMatchEventData (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && (StringUtil.containsWordIgnoreCase(event.getName().fullName, keyword) + || StringUtil.containsWordIgnoreCase(event.getContact().fullContactName, keyword) + || StringUtil.containsWordIgnoreCase(event.getEmail().value, keyword) + || StringUtil.containsWordIgnoreCase(event.getPhone().value, keyword) + || StringUtil.containsWordIgnoreCase(event.getVenue().value, keyword) + || StringUtil.containsWordIgnoreCase(event.getDateTime().toString(), keyword) + || event.getTags().stream() + .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword)) + || event.getAttendance().stream() + .anyMatch(attendee -> StringUtil.containsWordIgnoreCase(attendee.attendeeName, keyword)))); + } + + /** + * To check if any of name prefix keywords match with any of event name + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkNameKeywordsMatchEventName (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getName().fullName, keyword)); + } + + /** + * To check if any of contact prefix keywords match with any of event contact + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkContactKeywordsMatchEventContact (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getContact().fullContactName, keyword)); + } + + /** + * To check if any of email prefix keywords match with any of event email + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkEmailKeywordsMatchEventEmail (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getEmail().value, keyword)); + } + + /** + * To check if any of phone prefix keywords match with any of event phone + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkPhoneKeywordsMatchEventPhone (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getPhone().value, keyword)); + } + + /** + * To check if any of venue prefix keywords match with any of event venue + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkVenueKeywordsMatchEventVenue (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getVenue().value, keyword)); + } + + /** + * To check if any of datetime prefix keywords match with any of event datetime + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkDateTimeKeywordsMatchEventDateTime (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && StringUtil.containsWordIgnoreCase(event.getDateTime().toString(), keyword)); + } + + /** + * To check if any of tag prefix keywords match with any of event tags + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public boolean checkTagKeywordsMatchEventTag (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && event.getTags().stream() + .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword))); + } + + /** + * To check if any of attendee prefix keywords match with any of event attendees + * @param keywords list of keywords + * @param event event to compare + * @return a boolean indicate matching + */ + public static boolean checkAttendeeKeywordsMatchEventAttendee (List keywords, Event event) { + if (keywords == null) { + return true; + } + + return keywords.stream().anyMatch(keyword -> !keyword.isEmpty() + && event.getAttendance().stream() + .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.attendeeName, keyword))); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EventContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EventContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/event/Name.java similarity index 94% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/event/Name.java index 9982393dabb5..48ba37838de9 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/event/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.event; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Event's name in the event manager. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/event/Phone.java similarity index 93% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/event/Phone.java index a22e51653835..84ac7e078afc 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/event/Phone.java @@ -1,18 +1,19 @@ -package seedu.address.model.person; +package seedu.address.model.event; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Event's phone number in the event manager. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { - public static final String MESSAGE_PHONE_CONSTRAINTS = "Phone numbers should only contain numbers, and it should be at least 3 digits long"; + public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; + public final String value; /** diff --git a/src/main/java/seedu/address/model/event/Status.java b/src/main/java/seedu/address/model/event/Status.java new file mode 100644 index 000000000000..801f5f50a87f --- /dev/null +++ b/src/main/java/seedu/address/model/event/Status.java @@ -0,0 +1,74 @@ +//@@author cqinkai +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Date; + +/** + * Represents an Event's status in the event manager. + */ +public class Status { + public static final String MESSAGE_STATUS_CONSTRAINTS = + "Status should either be 'UPCOMING', 'COMPLETED' or 'NULL'."; + + public final String currentStatus; + + /** + * Constructs a {@code Status}. + * + * @param status A valid status. + */ + public Status(String status) { + requireNonNull(status); + checkArgument(isValidStatus(status), MESSAGE_STATUS_CONSTRAINTS); + currentStatus = status; + } + + /** + * Returns true if a given string is a valid status. + */ + public static boolean isValidStatus(String test) { + return (test.equals("UPCOMING") || test.equals("COMPLETED") || test.equals("NULL")); + } + + /** + * Gets the status of the event based on current date {@code Date()}. + * + * //@param datetime Datetime of event. + */ + public static final String setStatus(DateTime datetime) { + requireNonNull(datetime); + Date currentDate = new Date(); + Date eventDate = datetime.dateTime; + String currentStatus; + + if (eventDate.before(currentDate)) { + currentStatus = "COMPLETED"; + } else if (eventDate.after(currentDate)) { + currentStatus = "UPCOMING"; + } else { + currentStatus = "NULL"; + } + + return currentStatus; + } + + @Override + public String toString() { + return currentStatus; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Status // instanceof handles nulls + && currentStatus.equals(((Status) other).currentStatus)); // state check + } + + @Override + public int hashCode() { + return currentStatus.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/event/UniqueEventList.java b/src/main/java/seedu/address/model/event/UniqueEventList.java new file mode 100644 index 000000000000..f0dcb31061d5 --- /dev/null +++ b/src/main/java/seedu/address/model/event/UniqueEventList.java @@ -0,0 +1,151 @@ +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.comparators.DateTimeComparator; +import seedu.address.commons.comparators.NameComparator; +import seedu.address.model.event.exceptions.DuplicateEventException; +import seedu.address.model.event.exceptions.EventNotFoundException; + +/** + * A list of events that enforces uniqueness between its elements and does not allow nulls. + * A event is considered unique by comparing using {@code Event#isSameEvent(Event)}. As such, adding and updating of + * persons uses Event#isSameEvent(Event) for equality so as to ensure that the event being added or updated is + * unique in terms of identity in the UniqueEventList. However, the removal of a event uses Event#equals(Object) so + * as to ensure that the event with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Event#isSameEvent(Event) + */ +public class UniqueEventList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent event as the given argument. + */ + public boolean contains(Event toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameEvent); + } + + /** + * Adds a event to the list. + * The event must not already exist in the list. + */ + public void add(Event toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateEventException(); + } + internalList.add(toAdd); + sortEventList(); + } + + /** + * Replaces the event {@code target} in the list with {@code editedEvent}. + * {@code target} must exist in the list. + * The event identity of {@code editedEvent} must not be the same as another existing event in the list. + */ + public void setEvent(Event target, Event editedEvent) { + requireAllNonNull(target, editedEvent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new EventNotFoundException(); + } + + if (!target.isSameEvent(editedEvent) && contains(editedEvent)) { + throw new DuplicateEventException(); + } + + internalList.set(index, editedEvent); + sortEventList(); + } + + /** + * Removes the equivalent event from the list. + * The event must exist in the list. + */ + public void remove(Event toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new EventNotFoundException(); + } + sortEventList(); + } + + public void setEvents(UniqueEventList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + sortEventList(); + } + + /** + * Replaces the contents of this list with {@code events}. + * {@code events} must not contain duplicate events. + */ + public void setEvents(List events) { + requireAllNonNull(events); + if (!eventsAreUnique(events)) { + throw new DuplicateEventException(); + } + + internalList.setAll(events); + sortEventList(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + /** + * Sort the internal events list by DateTime then by Name + * implement here to keep the the binding list as internal list + */ + //Todo: check if list is sorted by DateTime then Name by default + public void sortEventList() { + internalList.sort(new DateTimeComparator().thenComparing(new NameComparator())); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueEventList // instanceof handles nulls + && internalList.equals(((UniqueEventList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code events} contains only unique events. + */ + private boolean eventsAreUnique(List events) { + for (int i = 0; i < events.size() - 1; i++) { + for (int j = i + 1; j < events.size(); j++) { + if (events.get(i).isSameEvent(events.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/event/Venue.java b/src/main/java/seedu/address/model/event/Venue.java new file mode 100644 index 000000000000..07897939e3e1 --- /dev/null +++ b/src/main/java/seedu/address/model/event/Venue.java @@ -0,0 +1,59 @@ +//@@author cqinkai +package seedu.address.model.event; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents an Event's venue in the event manager. + * Guarantees: immutable; is valid as declared in {@link #isValidVenue(String)} + */ +public class Venue { + + public static final String MESSAGE_VENUE_CONSTRAINTS = + "Venues can take any values, and it should not be blank"; + + /* + * The first character of the venue must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VENUE_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Venue}. + * + * @param venue A valid venue. + */ + public Venue(String venue) { + requireNonNull(venue); + checkArgument(isValidVenue(venue), MESSAGE_VENUE_CONSTRAINTS); + value = venue; + } + + /** + * Returns true if a given string is a valid venue. + */ + public static boolean isValidVenue(String test) { + return test.matches(VENUE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Venue // instanceof handles nulls + && value.equals(((Venue) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java new file mode 100644 index 000000000000..b99d72d162d1 --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/DuplicateEventException.java @@ -0,0 +1,11 @@ +package seedu.address.model.event.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateEventException extends RuntimeException { + public DuplicateEventException() { + super("Operation would result in duplicate events"); + } +} diff --git a/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java new file mode 100644 index 000000000000..5117db006eaa --- /dev/null +++ b/src/main/java/seedu/address/model/event/exceptions/EventNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.event.exceptions; + +/** + * Signals that the operation is unable to find the specified event. + */ +public class EventNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, 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 ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427ca..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * 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); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 5856aa42e6b5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,135 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return FXCollections.unmodifiableObservableList(internalList); - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca73..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 8cdff2773ac9..bdfc1ba72368 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -4,12 +4,12 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the event manager. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_TAG_CONSTRAINTS = "Tag names should be alphanumeric"; public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; diff --git a/src/main/java/seedu/address/model/user/Password.java b/src/main/java/seedu/address/model/user/Password.java new file mode 100644 index 000000000000..3eddf06dddaa --- /dev/null +++ b/src/main/java/seedu/address/model/user/Password.java @@ -0,0 +1,53 @@ +package seedu.address.model.user; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@ jamesyaputra +/** + * Represents a user's password credential. + */ +public class Password { + + private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.@-"; + public static final String MESSAGE_PASSWORD_CONSTRAINTS = + "Passwords should only contain alphanumeric characters and these special characters, excluding " + + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n"; + private static final String PASSWORD_VALIDATION_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; + public final String value; + + /** + * Constructs an {@code Password}. + * + * @param password A valid password. + */ + public Password(String password) { + requireNonNull(password); + checkArgument(isValidPassword(password), MESSAGE_PASSWORD_CONSTRAINTS); + value = password; + } + + /** + * Returns true if a given string is a valid password. + */ + public static boolean isValidPassword(String test) { + return test.matches(PASSWORD_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Password // instanceof handles nulls + && value.equals(((Password) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/user/User.java b/src/main/java/seedu/address/model/user/User.java new file mode 100644 index 000000000000..2704f29b801d --- /dev/null +++ b/src/main/java/seedu/address/model/user/User.java @@ -0,0 +1,42 @@ +package seedu.address.model.user; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +//@@ jamesyaputra +/** + * Represents a user account. + */ +public class User { + + private final Username username; + private final Password password; + + public User(Username username, Password password) { + requireAllNonNull(username, password); + this.username = username; + this.password = password; + } + + public Username getUsername() { + return username; + } + + public Password getPassword() { + return password; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof User)) { + return false; + } + + User user = (User) other; + return this.username.equals(user.getUsername()) + && this.password.equals(user.getPassword()); + } +} diff --git a/src/main/java/seedu/address/model/user/Username.java b/src/main/java/seedu/address/model/user/Username.java new file mode 100644 index 000000000000..531c4806b85e --- /dev/null +++ b/src/main/java/seedu/address/model/user/Username.java @@ -0,0 +1,57 @@ +package seedu.address.model.user; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@ jamesyaputra +/** + * Represents a user's username credential. + */ +public class Username { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String USERNAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String value; + + /** + * Constructs an {@code Username}. + * + * @param username A valid username. + */ + public Username(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_USERNAME_CONSTRAINTS); + value = username; + } + + /** + * Returns true if a given string is a valid username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Username // instanceof handles nulls + && value.equals(((Username) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..5b3720bac8ad 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -4,46 +4,63 @@ 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.EventManager; +import seedu.address.model.ReadOnlyEventManager; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Contact; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Email; +import seedu.address.model.event.Event; +import seedu.address.model.event.Name; +import seedu.address.model.event.Phone; +import seedu.address.model.event.Status; +import seedu.address.model.event.Venue; import seedu.address.model.tag.Tag; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code EventManager} 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 Event[] getSampleEvents() { + return new Event[] { + new Event(new Name("Basketball Session"), new Contact("Alex Yeoh"), new Phone("87438807"), + new Email("alexyeoh@example.com"), new Venue("Blk 30 Geylang Street 29, #06-40"), + new DateTime(new String("25/11/2018 11:30")), new Status("UPCOMING"), + new Comment("{span}Comment Section{/span}" + "{ol}{/ol}"), + getTagSet("friends"), getAttendeeSet("Alex Yeoh")), + new Event(new Name("Night Study Session"), new Contact("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@example.com"), new Venue("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new DateTime(new String("10/10/2018 11:30")), new Status("COMPLETED"), + new Comment("{span}Comment Section{/span}" + "{ol}{/ol}"), + getTagSet("colleagues", "friends"), getAttendeeSet("Bernice Yu")), + new Event(new Name("House Themed Dinner"), new Contact("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), new Venue("Blk 11 Ang Mo Kio Street 74, #11-04"), + new DateTime(new String("20/1/2018 11:30")), new Status("COMPLETED"), + new Comment("{span}Comment Section{/span}" + "{ol}{/ol}"), + getTagSet("neighbours"), getAttendeeSet("Charlotte Oliveiro")), + new Event(new Name("Cooking Club Tryouts"), new Contact("David Li"), new Phone("91031282"), + new Email("lidavid@example.com"), new Venue("Blk 436 Serangoon Gardens Street 26, #16-43"), + new DateTime(new String("28/2/2018 11:30")), new Status("COMPLETED"), + new Comment("{span}Comment Section{/span}" + "{ol}{/ol}"), + getTagSet("family"), getAttendeeSet("David Li")), + new Event(new Name("Migrant Workers Rights Sharing Session"), new Contact("Irfan Ibrahim"), + new Phone("92492021"), new Email("irfan@example.com"), + new Venue("Blk 47 Tampines Street 20, #17-35"), new DateTime(new String("5/7/2018 16:30")), + new Status("COMPLETED"), new Comment("{span}This is a comment{/span}"), + getTagSet("classmates"), getAttendeeSet("Irfan Ibrahim")), + new Event(new Name("Ultimate Frisbee Session"), new Contact("Roy Balakrishnan"), new Phone("92624417"), + new Email("royb@example.com"), new Venue("Blk 45 Aljunied Street 85, #11-31"), + new DateTime(new String("5/7/2018 4:30")), new Status("COMPLETED"), + new Comment("{span}Comment Section{/span}" + "{ol}{/ol}"), getTagSet("colleagues"), + getAttendeeSet("Roy Balakrishnan")) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + public static ReadOnlyEventManager getSampleEventManager() { + EventManager sampleAb = new EventManager(); + for (Event sampleEvent : getSampleEvents()) { + sampleAb.addEvent(sampleEvent); } return sampleAb; } @@ -57,4 +74,13 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a attendee set containing the list of strings given. + */ + public static Set getAttendeeSet(String... strings) { + return Arrays.stream(strings) + .map(Attendee::new) + .collect(Collectors.toSet()); + } + } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f92..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Comments.java b/src/main/java/seedu/address/storage/Comments.java new file mode 100644 index 000000000000..86d7b6b05cb2 --- /dev/null +++ b/src/main/java/seedu/address/storage/Comments.java @@ -0,0 +1,79 @@ +package seedu.address.storage; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; + +/** + * Adds comments section. + */ +public class Comments { + + private File file = new File("src/main/resources/comments/"); + private final String commentsPath = file.getAbsolutePath(); + private int fileName = getLastFile(file); + + private int getLastFile(File folder) { + File[] listOfFiles = folder.listFiles(); + if (listOfFiles.length == 0) { + return 0; + } + String lastFile = listOfFiles[listOfFiles.length - 1].getName(); + return Integer.parseInt(lastFile.substring(0, lastFile.length() - 5)); + } + public String getFilePath() { + return this.commentsPath; + } + public int getFileName() { + return this.fileName; + } + public File getFile() { + return this.file; + } + + /** + * Makes a Html file to store comments. + */ + public void createHtml(String commentsDirectoryString, int index) { + FileWriter fWriter = null; + BufferedWriter writer = null; + try { + File f = new File(commentsDirectoryString + "/" + Integer.toString(index) + ".html"); + if (f.exists()) { + this.fileName++; + createHtml(commentsDirectoryString, this.fileName); + return; + } + fWriter = new FileWriter(commentsDirectoryString + "/" + Integer.toString(index) + ".html"); + writer = new BufferedWriter(fWriter); + writer.write("Comment Section"); + writer.newLine(); //this is not actually needed for html files - can make your code more readable though + writer.close(); //make sure you close the writer object + } catch (Exception e) { + //catch any exceptions here + System.out.println("failed"); + } + } + + /** + * Deletes the Html file associated with the event + */ + public void deleteHtml(File folder, String commentsDirectory, int index) { + File[] listOfFiles = folder.listFiles(); + if (index > listOfFiles.length) { + System.out.println("error"); + //put exception here or a try catch + } + File file = new File(commentsDirectory + "/" + listOfFiles[index].getName()); + file.delete(); + } + + /** + * Test code, to do create a function to view comments by displaying Html file. + */ + public static void main(String[] args) { + Comments comment = new Comments(); + //comment.deleteHtml(comment.getFile(), comment.getFilePath(),7); + //comment.createHtml(comment.getFilePath(), comment.getFileName()); + } +} diff --git a/src/main/java/seedu/address/storage/EventManagerStorage.java b/src/main/java/seedu/address/storage/EventManagerStorage.java new file mode 100644 index 000000000000..cf08817c3a8e --- /dev/null +++ b/src/main/java/seedu/address/storage/EventManagerStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.EventManager; +import seedu.address.model.ReadOnlyEventManager; + +/** + * Represents a storage for {@link EventManager}. + */ +public interface EventManagerStorage { + + /** + * Returns the file path of the data file. + */ + Path getEventManagerFilePath(); + + /** + * Returns EventManager data as a {@link ReadOnlyEventManager}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readEventManager() throws DataConversionException, IOException; + + /** + * @see #getEventManagerFilePath() + */ + Optional readEventManager(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyEventManager} to the storage. + * @param eventManager cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveEventManager(ReadOnlyEventManager eventManager) throws IOException; + + /** + * @see #saveEventManager(ReadOnlyEventManager) + */ + void saveEventManager(ReadOnlyEventManager eventManager, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonUserStorage.java b/src/main/java/seedu/address/storage/JsonUserStorage.java new file mode 100644 index 000000000000..4faccc0a3a4d --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonUserStorage.java @@ -0,0 +1,92 @@ +package seedu.address.storage; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; + +//@@ jamesyaputra +/** + * A class to access UserSession stored in the hard disk as a JSON file + */ +public class JsonUserStorage implements UserStorage { + + private String filePathString; + private Map userAccounts; + + public JsonUserStorage(Path filePath) throws IOException { + filePathString = "./" + filePath.toString(); + + if (Files.notExists(filePath)) { + createUserFile(); + } + + setUserAccounts(); + } + + /** + * Adds a new property in the JSON file. + */ + @Override + public void createUser(String username, String password) throws IOException { + JsonObject jsonObject = getJsonObject(); + jsonObject.addProperty(username, password); + + writeJson(new Gson(), jsonObject); + setUserAccounts(); + } + + /** + * Returns the user accounts as a map. + */ + @Override + public Map getUserAccounts() { + return userAccounts; + } + + /** + * Sets the user account. + */ + private void setUserAccounts() throws IOException { + Type type = new TypeToken>(){}.getType(); + userAccounts = new Gson().fromJson(new FileReader(filePathString), type); + } + + /** + * Returns the user accounts as a JSON Object. + */ + private JsonObject getJsonObject() throws FileNotFoundException { + JsonParser parser = new JsonParser(); + JsonElement jsonElement = parser.parse(new FileReader(filePathString)); + + return jsonElement.getAsJsonObject(); + } + + /** + * Creates a user account JSON file. + */ + private void createUserFile() throws IOException { + JsonObject jsonObject = new JsonObject(); + writeJson(new Gson(), jsonObject); + } + + /** + * Writes to the User JSON. + */ + private void writeJson(Gson gson, JsonObject jsonObject) throws IOException { + String json = gson.toJson(jsonObject); + FileWriter file = new FileWriter(filePathString); + file.write(json); + file.flush(); + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 28791127999b..692e1090a1e7 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -4,16 +4,16 @@ import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.EventManagerChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEventManager; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends EventManagerStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -22,18 +22,18 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(UserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getEventManagerFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readEventManager() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveEventManager(ReadOnlyEventManager eventManager) throws IOException; /** - * Saves the current version of the Address Book to the hard disk. + * Saves the current version of the Event Manager to the hard disk. * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); + void handleEManagerChangedEvent(EventManagerChangedEvent abce); } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b0df908a76a7..ddad08c69ae7 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -9,25 +9,25 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.EventManagerChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEventManager; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of EventManager data in local storage. */ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private EventManagerStorage eventManagerStorage; private UserPrefsStorage userPrefsStorage; - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(EventManagerStorage eventManagerStorage, UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.eventManagerStorage = eventManagerStorage; this.userPrefsStorage = userPrefsStorage; } @@ -49,42 +49,42 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ EventManager methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getEventManagerFilePath() { + return eventManagerStorage.getEventManagerFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readEventManager() throws DataConversionException, IOException { + return readEventManager(eventManagerStorage.getEventManagerFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readEventManager(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return eventManagerStorage.readEventManager(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveEventManager(ReadOnlyEventManager eventManager) throws IOException { + saveEventManager(eventManager, eventManagerStorage.getEventManagerFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveEventManager(ReadOnlyEventManager eventManager, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + eventManagerStorage.saveEventManager(eventManager, filePath); } @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleEManagerChangedEvent(EventManagerChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { - saveAddressBook(event.data); + saveEventManager(event.data); } catch (IOException e) { raise(new DataSavingExceptionEvent(e)); } diff --git a/src/main/java/seedu/address/storage/UserStorage.java b/src/main/java/seedu/address/storage/UserStorage.java new file mode 100644 index 000000000000..949e3fd999f9 --- /dev/null +++ b/src/main/java/seedu/address/storage/UserStorage.java @@ -0,0 +1,24 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.util.Map; + +import seedu.address.model.UserSession; + +//@@ jamesyaputra +/** + * Represents a storage for {@link UserSession}. + */ +public interface UserStorage { + + /** + * Creates a new User account. + */ + void createUser(String username, String password) throws IOException; + + /** + * Returns a JsonObject containing user accounts. + */ + Map getUserAccounts() throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedAttendee.java b/src/main/java/seedu/address/storage/XmlAdaptedAttendee.java new file mode 100644 index 000000000000..fbbf7d180070 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedAttendee.java @@ -0,0 +1,63 @@ +//@@author cqinkai +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.attendee.Attendee; + +/** + * JAXB-friendly adapted version of the Attendee. + */ +public class XmlAdaptedAttendee { + + @XmlValue + private String attendeeName; + + /** + * Constructs an XmlAdaptedAttendee. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAttendee() {} + + /** + * Constructs a {@code XmlAdaptedAttendee} with the given {@code attendeeName}. + */ + public XmlAdaptedAttendee(String attendeeName) { + this.attendeeName = attendeeName; + } + + /** + * Converts a given Attendee into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedAttendee(Attendee source) { + attendeeName = source.attendeeName; + } + + /** + * Converts this jaxb-friendly adapted attendee object into the model's Attendee object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Attendee toModelType() throws IllegalValueException { + if (!Attendee.isValidAttendeeName(attendeeName)) { + throw new IllegalValueException(Attendee.MESSAGE_ATTENDEE_CONSTRAINTS); + } + return new Attendee(attendeeName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAttendee)) { + return false; + } + + return attendeeName.equals(((XmlAdaptedAttendee) other).attendeeName); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedEvent.java b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java new file mode 100644 index 000000000000..9a327bfed61f --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedEvent.java @@ -0,0 +1,216 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.attendee.Attendee; +import seedu.address.model.event.Comment; +import seedu.address.model.event.Contact; +import seedu.address.model.event.DateTime; +import seedu.address.model.event.Email; +import seedu.address.model.event.Event; +import seedu.address.model.event.Name; +import seedu.address.model.event.Phone; +import seedu.address.model.event.Status; +import seedu.address.model.event.Venue; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Event. + */ +public class XmlAdaptedEvent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Event's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String contact; + @XmlElement(required = true) + private String phone; + @XmlElement(required = true) + private String email; + @XmlElement(required = true) + private String venue; + @XmlElement(required = true) + private String dateTime; + @XmlElement(required = true) + private String status; + @XmlElement(required = true) + private String comment; + + @XmlElement + private List tagged = new ArrayList<>(); + @XmlElement + private List attending = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedEvent. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedEvent() {} + + /** + * Constructs an {@code XmlAdaptedEvent} with the given event details. + */ + public XmlAdaptedEvent(String name, String contact, String phone, String email, String venue, String datetime, + String status, String comment, List tagged, + List attending) { + + this.name = name; + this.contact = contact; + this.phone = phone; + this.email = email; + this.venue = venue; + this.dateTime = datetime; + this.status = status; + this.comment = comment; + + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + if (attending != null) { + this.attending = new ArrayList<>(attending); + } + } + + /** + * Converts a given Event into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedEvent + */ + public XmlAdaptedEvent(Event source) { + name = source.getName().fullName; + contact = source.getContact().fullContactName; + phone = source.getPhone().value; + email = source.getEmail().value; + venue = source.getVenue().value; + dateTime = source.getDateTime().toString(); + status = source.getStatus().currentStatus; + comment = source.getComment().value; + + tagged = source.getTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + attending = source.getAttendance().stream() + .map(XmlAdaptedAttendee::new) + .collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted event object into the model's Event object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted event + */ + public Event toModelType() throws IllegalValueException { + final List eventTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + eventTags.add(tag.toModelType()); + } + + final List eventAttendees = new ArrayList<>(); + for (XmlAdaptedAttendee attendee : attending) { + eventAttendees.add(attendee.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (contact == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Contact.class.getSimpleName())); + } + if (!Contact.isValidContact(contact)) { + throw new IllegalValueException(Contact.MESSAGE_CONTACT_CONSTRAINTS); + } + final Contact modelContact = new Contact(contact); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (venue == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Venue.class.getSimpleName())); + } + if (!Venue.isValidVenue(venue)) { + throw new IllegalValueException(Venue.MESSAGE_VENUE_CONSTRAINTS); + } + final Venue modelVenue = new Venue(venue); + + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATETIME_CONSTRAINTS); + } + final DateTime modelDateTime = new DateTime(dateTime); + + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Status.class.getSimpleName())); + } + /*if (!Status.isValidStatus(status)) { + throw new IllegalValueException(Status.MESSAGE_STATUS_CONSTRAINTS); + }*/ + final Status modelStatus = new Status(status); + + if (comment == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Comment.class.getSimpleName())); + } + if (!Comment.isValidComment(comment)) { + throw new IllegalValueException(Comment.MESSAGE_COMMENT_CONSTRAINTS); + } + final Comment modelComment = new Comment(comment); + + final Set modelTags = new HashSet<>(eventTags); + final Set modelAttendees = new HashSet<>(eventAttendees); + return new Event(modelName, modelContact, modelPhone, modelEmail, modelVenue, modelDateTime, modelStatus, + modelComment, modelTags, modelAttendees); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedEvent)) { + return false; + } + + XmlAdaptedEvent otherEvent = (XmlAdaptedEvent) other; + return Objects.equals(name, otherEvent.name) + && Objects.equals(contact, otherEvent.contact) + && Objects.equals(phone, otherEvent.phone) + && Objects.equals(email, otherEvent.email) + && Objects.equals(venue, otherEvent.venue) + && Objects.equals(dateTime, otherEvent.dateTime) + && Objects.equals(status, otherEvent.status) + && Objects.equals(comment, otherEvent.comment) + && tagged.equals(otherEvent.tagged) + && attending.equals(otherEvent.attending); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index c03785e5700f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; - -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; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = source.getTags().stream() - .map(XmlAdaptedTag::new) - .collect(Collectors.toList()); - } - - /** - * Converts this jaxb-friendly adapted person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..5bf8b1862039 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -38,7 +38,7 @@ public XmlAdaptedTag(Tag source) { /** * Converts this jaxb-friendly adapted tag object into the model's Tag 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 event */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlEManagerStorage.java similarity index 57% rename from src/main/java/seedu/address/storage/XmlAddressBookStorage.java rename to src/main/java/seedu/address/storage/XmlEManagerStorage.java index 8af60be0dc5d..defe1d607d88 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlEManagerStorage.java @@ -13,45 +13,45 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEventManager; /** - * A class to access AddressBook data stored as an xml file on the hard disk. + * A class to access EventManager data stored as an xml file on the hard disk. */ -public class XmlAddressBookStorage implements AddressBookStorage { +public class XmlEManagerStorage implements EventManagerStorage { - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(XmlEManagerStorage.class); private Path filePath; - public XmlAddressBookStorage(Path filePath) { + public XmlEManagerStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getEventManagerFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); + public Optional readEventManager() throws DataConversionException, IOException { + return readEventManager(filePath); } /** - * Similar to {@link #readAddressBook()} + * Similar to {@link #readEventManager()} * @param filePath location of the data. Cannot be null * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException, + public Optional readEventManager(Path filePath) throws DataConversionException, FileNotFoundException { requireNonNull(filePath); if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); + logger.info("EventManager file " + filePath + " not found"); return Optional.empty(); } - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); + XmlSerializableEManager xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); try { return Optional.of(xmlAddressBook.toModelType()); } catch (IllegalValueException ive) { @@ -61,20 +61,21 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveEventManager(ReadOnlyEventManager eventManager) throws IOException { + saveEventManager(eventManager, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * Similar to {@link #saveEventManager(ReadOnlyEventManager)} + * @param eventManager * @param filePath location of the data. Cannot be null */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); + public void saveEventManager(ReadOnlyEventManager eventManager, Path filePath) throws IOException { + requireNonNull(eventManager); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); + XmlFileStorage.saveDataToFile(filePath, new XmlSerializableEManager(eventManager)); } } diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index d8f65dc036ab..65df4abe5a95 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -9,13 +9,13 @@ import seedu.address.commons.util.XmlUtil; /** - * Stores addressbook data in an XML file + * Stores event manager data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given event manager data to the specified file. */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(Path file, XmlSerializableEManager addressBook) throws FileNotFoundException { try { XmlUtil.saveDataToFile(file, addressBook); @@ -25,12 +25,12 @@ public static void saveDataToFile(Path file, XmlSerializableAddressBook addressB } /** - * Returns address book in the file or an empty address book + * Returns address book in the file or an empty event manager */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, + public static XmlSerializableEManager loadDataFromSaveFile(Path file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableEManager.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b85fa4a8f07e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - @XmlElement - private List persons; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - return persons.equals(((XmlSerializableAddressBook) other).persons); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableEManager.java b/src/main/java/seedu/address/storage/XmlSerializableEManager.java new file mode 100644 index 000000000000..a55770883d1d --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlSerializableEManager.java @@ -0,0 +1,71 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.EventManager; +import seedu.address.model.ReadOnlyEventManager; +import seedu.address.model.event.Event; + +/** + * An Immutable EventManager that is serializable to XML format + */ +@XmlRootElement(name = "eventmanager") +public class XmlSerializableEManager { + + public static final String MESSAGE_DUPLICATE_EVENT = "Events list contains duplicate event(s)."; + + @XmlElement + private List events; + + /** + * Creates an empty XmlSerializableEManager. + * This empty constructor is required for marshalling. + */ + public XmlSerializableEManager() { + events = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableEManager(ReadOnlyEventManager src) { + this(); + events.addAll(src.getEventList().stream().map(XmlAdaptedEvent::new).collect(Collectors.toList())); + } + + /** + * Converts this event manager into the model's {@code EventManager} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedEvent}. + */ + public EventManager toModelType() throws IllegalValueException { + EventManager eventManager = new EventManager(); + for (XmlAdaptedEvent p : events) { + Event event = p.toModelType(); + if (eventManager.hasEvent(event)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_EVENT); + } + eventManager.addEvent(event); + } + return eventManager; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableEManager)) { + return false; + } + return events.equals(((XmlSerializableEManager) other).events); + } +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index b43de90a2b9f..be882bbd5170 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -1,19 +1,24 @@ package seedu.address.ui; +import static seedu.address.model.DateTimeUtil.PAGE_DATE_FORMAT; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; import javafx.application.Platform; -import javafx.event.Event; import javafx.fxml.FXML; import javafx.scene.layout.Region; import javafx.scene.web.WebView; import seedu.address.MainApp; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.ui.EventSelectionChangedEvent; +import seedu.address.model.event.Event; /** * The Browser Panel of the App. @@ -21,8 +26,8 @@ public class BrowserPanel extends UiPart { public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; + private static final String EVENT_PAGE_URL = "https://cs2113-ay1819s1-t12-1.github.io/main/EventSearchPage.html"; + private static final String FXML = "BrowserPanel.fxml"; @@ -35,14 +40,63 @@ public BrowserPanel() { super(FXML); // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); + getRoot().setOnKeyPressed(javafx.event.Event::consume); loadDefaultPage(); registerAsAnEventHandler(this); } - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); + /** + * Gets the URL without parameters + */ + public static String getEventPageUrl() { + return EVENT_PAGE_URL; + } + + /** + * Translates a string into {@code application/x-www-form-urlencoded} + * format using UTF_8 encoding scheme. Spaces are replaced with '%20' instead of '+'. + * Returns an empty string if UnsupportedEncodingException is encountered. + */ + public String encodeString(String arg) { + try { + String encodedString = URLEncoder.encode(arg, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"); + return encodedString; + } catch (UnsupportedEncodingException uee) { + logger.warning("Encoding not supported."); + return ""; + } + } + + /** + * Formats HTML file path into string + */ + private String formatEventPageUrl(Event event) { + String queryString = "?name=" + encodeString(event.getName().toString()) + + "&contact=" + encodeString(event.getContact().toString()) + + "&phone=" + encodeString(event.getPhone().toString()) + + "&email=" + encodeString(event.getEmail().toString()) + + "&venue=" + encodeString(event.getVenue().value) + + "&dateTime=" + encodeString(PAGE_DATE_FORMAT.format(event.getDateTime().dateTime)) + + "&status=" + encodeString(event.getStatus().toString()) + + "&tags=" + encodeString(event.getTagsString()) + + "&attendance=" + encodeString(event.getAttendanceString()) + + "&comment=" + encodeString(event.getComment().toString().replace("{", "<").replace("}", ">")); + + return EVENT_PAGE_URL + queryString; + } + + /** + * Loads a HTML file with data from event passed into it + */ + private void loadEventPage(Event event) { + try { + URL searchPage = new URL(formatEventPageUrl(event)); + loadPage(searchPage.toExternalForm()); + } catch (MalformedURLException mue) { + logger.warning("Event page has invalid parameters. Default page will be loaded."); + loadDefaultPage(); + } } public void loadPage(String url) { @@ -65,8 +119,8 @@ public void freeResources() { } @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { + private void handleEventSelectionChangedEvent(EventSelectionChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); + loadEventPage(event.getNewSelection()); } } diff --git a/src/main/java/seedu/address/ui/EventCard.java b/src/main/java/seedu/address/ui/EventCard.java new file mode 100644 index 000000000000..158e9c59b0ea --- /dev/null +++ b/src/main/java/seedu/address/ui/EventCard.java @@ -0,0 +1,85 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.event.Event; + +/** + * An UI component that displays information of a {@code Event}. + */ +public class EventCard extends UiPart { + + private static final String FXML = "EventListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on EventManager level 4 + */ + + public final Event event; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label contact; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label venue; + @FXML + private Label email; + @FXML + private Label dateTime; + @FXML + private FlowPane tags; + @FXML + private Label status; + @FXML + private Label comment; + @FXML + private FlowPane attendance; + + public EventCard(Event event, int displayedIndex) { + super(FXML); + this.event = event; + id.setText(displayedIndex + ". "); + name.setText(event.getName().fullName); + contact.setText(event.getContact().fullContactName); + phone.setText(event.getPhone().value); + venue.setText(event.getVenue().value); + email.setText(event.getEmail().value); + comment.setText(event.getComment().value); + dateTime.setText(event.getDateTime().toString()); + status.setText(event.getStatus().currentStatus); + event.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + event.getAttendance().forEach(attendee -> attendance.getChildren().add(new Label(attendee.attendeeName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EventCard)) { + return false; + } + + // state check + EventCard card = (EventCard) other; + return id.getText().equals(card.id.getText()) + && event.equals(card.event); + } +} diff --git a/src/main/java/seedu/address/ui/EventListPanel.java b/src/main/java/seedu/address/ui/EventListPanel.java new file mode 100644 index 000000000000..bba264677f76 --- /dev/null +++ b/src/main/java/seedu/address/ui/EventListPanel.java @@ -0,0 +1,83 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.ui.EventSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.model.event.Event; + +/** + * Panel containing the list of events. + */ +public class EventListPanel extends UiPart { + private static final String FXML = "EventListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(EventListPanel.class); + + @FXML + private ListView eventListView; + + public EventListPanel(ObservableList eventList) { + super(FXML); + setConnections(eventList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList eventList) { + eventListView.setItems(eventList); + eventListView.setCellFactory(listView -> new EventListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + eventListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in event list panel changed to : '" + newValue + "'"); + raise(new EventSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code EventCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + eventListView.scrollTo(index); + eventListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Event} using a {@code EventCard}. + */ + class EventListViewCell extends ListCell { + @Override + protected void updateItem(Event event, boolean empty) { + super.updateItem(event, empty); + + if (empty || event == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new EventCard(event, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..5bd117064ea4 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -35,7 +35,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private EventListPanel eventListPanel; private Config config; private UserPrefs prefs; private HelpWindow helpWindow; @@ -50,7 +50,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane eventListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -122,13 +122,13 @@ void fillInnerParts() { browserPanel = new BrowserPanel(); browserPlaceholder.getChildren().add(browserPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + eventListPanel = new EventListPanel(logic.getFilteredEventList()); + eventListPanelPlaceholder.getChildren().add(eventListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getEventManagerFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -187,8 +187,8 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public EventListPanel getEventListPanel() { + return eventListPanel; } void releaseResources() { diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.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 address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 80080adb4305..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..9093fabe74c1 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -14,7 +14,7 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.EventManagerChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -74,10 +74,10 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleEventManagerChangedEvent(EventManagerChangedEvent emce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); - logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); + logger.info(LogsCenter.getEventHandlingLogMessage(emce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); } } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..7057ce3d0676 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -14,6 +14,7 @@ import seedu.address.commons.core.Config; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.storage.DataSavingExceptionEvent; +import seedu.address.commons.events.ui.SendEventReminder; import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.model.UserPrefs; @@ -117,4 +118,15 @@ private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { showFileOperationAlertAndWait(FILE_OPS_ERROR_DIALOG_HEADER_MESSAGE, FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE, event.exception); } + + //@@author cqinkai + @Subscribe + private void handleSendEventReminder(SendEventReminder ser) { + String title = "Reminder for " + ser.eventName; + String header = "You registered for " + ser.eventName + "\non " + ser.eventDate; + String content = "\t\t" + ser.timeTillEvent + " hours to event."; + + showAlertDialogAndWait(AlertType.INFORMATION, title, header, content); + logger.info(LogsCenter.getEventHandlingLogMessage(ser, "Reminder sent")); + } } diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml index 31670827e3da..a3e87d3a8d35 100644 --- a/src/main/resources/view/BrowserPanel.fxml +++ b/src/main/resources/view/BrowserPanel.fxml @@ -2,7 +2,6 @@ - diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 70bd59ab3215..4f9bfe90b8e8 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -2,7 +2,6 @@ - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..de8065b3af04 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -349,3 +349,21 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +.status_label_completed { + -fx-text-fill: white; + -fx-background-color: #ba072e; + -fx-padding: 2 4 2 4; + -fx-border-radius: 4; + -fx-background-radius: 4; + -fx-font-size: 13; +} + +.status_label_upcoming { + -fx-text-fill: white; + -fx-background-color: #06d845; + -fx-padding: 2 4 2 4; + -fx-border-radius: 4; + -fx-background-radius: 4; + -fx-font-size: 13; +} diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/EventListCard.fxml similarity index 75% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/EventListCard.fxml index f08ea32ad558..4912bb46821d 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/EventListCard.fxml @@ -8,7 +8,6 @@ - @@ -27,10 +26,15 @@ +