diff --git a/.gitignore b/.gitignore index 5e59b862ba4..c429be3c74e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ src/main/resources/docs/ /*.iml # Storage/log files -/data/ +/data/*.json /config.json /preferences.json /*.log.* diff --git a/README.adoc b/README.adoc index d34211c9341..7ca660f7ed5 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 3) += ELISA ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&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/AY1920S1-CS2103T-T10-3/main[image:https://travis-ci.org/AY1920S1-CS2103T-T10-3/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/blimyj/main/branch/master[image:https://ci.appveyor.com/api/projects/status/o281h6kwxi79gf2u/branch/master?svg=true[Build status]] +https://coveralls.io/github/AY1920S1-CS2103T-T10-3/main[image:https://coveralls.io/repos/github/AY1920S1-CS2103T-T10-3/main/badge.svg[Coverage Status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,9 +13,11 @@ 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). +* Extremely Loud and Intelligent Student Assistant (ELISA) is for students who *want to have an intelligent companion that keeps track of tasks and give friendly reminders on when to take breaks*. +* More importantly, ELISA 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, ELISA can get your tasks done faster than traditional GUI apps. * 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. +* Interested? Jump to the <> to get started. Enjoy! == Site Map @@ -29,6 +29,7 @@ endif::[] == Acknowledgements +* Built on AddressBook-Level3 project created by SE-EDU initiative at https://se-education.org * 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://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] diff --git a/build.gradle b/build.gradle index 93029ef8262..6a3adcaa13b 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.Main' +mainClassName = 'seedu.elisa.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -43,7 +43,7 @@ test { dependencies { String jUnitVersion = '5.4.0' - String javaFxVersion = '11' + String javaFxVersion = '11.0.1' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' @@ -57,6 +57,9 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -67,7 +70,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'ELISA.jar' destinationDir = file("${buildDir}/jar/") } diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..4951fd8402c 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,54 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 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.}_ + +ELISA _(Exceptionally Loud and Intelligent Student Assistant)_ - Was developed by the https://github.com/AY1920S1-CS2103T-T10-3[T10-3] team. + +ELISA is a student assistant made for college students who need constant reminders to complete their daily tasks. More importantly, ELISA 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). + {empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +We are a student team 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]] [<>] +=== Bryan Lim +image::blimyj.png[width="150", align="left"] +{empty}[http://github.com/blimyj[github]] [<>] -Role: Project Advisor +Role: Developer + +Responsibilities: Commons. Management of issue tracker, team repo and team. ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Hema +image::lrchema.png[width="150", align="left"] +{empty}[http://github.com/lrchema[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: Functionality ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Lim Yu Hui +image::mannggoo.png[width="150", align="left"] +{empty}[http://github.com/mannggoo[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Logic ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Low Cheng Yi +image::Icesiolz.png[width="150", align="left"] +{empty}[http://github.com/Icesiolz[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: UI ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Ng Siang Hwee +image::sianghwee.png[width="150", align="left"] +{empty}[http://github.com/sianghwee[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Model ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..050bc1fa6c1 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,7 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/AY1920S1-CS2103T-T10-3/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `holmescordelia8 [at] gmail.com` + diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc index 2aa5a6bc0c1..791ef0f81f7 100644 --- a/docs/DevOps.adoc +++ b/docs/DevOps.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Dev Ops += ELISA - Dev Ops :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S1-CS2103T-T10-3/main == Build Automation @@ -34,14 +34,14 @@ When a pull request has changes to asciidoc files, you can use https://www.netli Here are the steps to create a new release. -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. +. Update the version number in link:{repoURL}/main/blob/master/src/main/java/seedu/elisa/MainApp.java[`MainApp.java`]. . Generate a JAR file <>. . Tag the repo with the version number. e.g. `v0.1` . https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. == Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON 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, ELISA depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives: [loweralpha] . Include those libraries in the repo (this bloats the repo size) diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..54f7efdb27e 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += ELISA - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -6,15 +6,16 @@ :sectnums: :imagesDir: images :stylesDir: stylesheets +:icons-cdn: https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css :xrefstyle: full ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S1-CS2103T-T10-3/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `AY1920S1-CS2103T-T10-3`      Since: `Sep 2019`      Licence: `MIT` == Setting up @@ -39,11 +40,26 @@ Refer to the <> to learn how to create and * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup method where necessary. +// tag::commons[] <> represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level: +* `Item` : Used by classes to represent a task, event, reminder or any combination of the three. +* `Task` : Used by classes to store, access and modify details regarding tasks. +* `Events` : Used by classes to store, access and modify details regarding events. +* `Reminders` : Used by classes to store, access and modify details regarding reminders. * `LogsCenter` : Used by many classes to write log messages to the App's log file. +.Class Diagram of the Item +image::ItemClassDiagram.png[] +* An `Item` can contain an task, event or reminder. However it must contain at least one of the three. +* The `ItemBuilder` class ensures the validation of the above rule when `build()` is called.. +* An `Item` is immutable in order to support thread safety. +* An `Item` is created via the ItemBuilder class. +* Setter methods of `Item` create a new `Item` through `ItemBuilder` to ensure immutability. + +// end::commons[] + The rest of the App consists of four components. * <>: The UI of the App. @@ -56,10 +72,9 @@ Each of the four components * Defines its _API_ in an `interface` with the same name as the Component. * Exposes its functionality using a `{Component Name}Manager` class. -For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. +For example, the `Logic` component (see the class diagram <>) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. + -.Class Diagram of the Logic Component -image::LogicClassDiagram.png[] [discrete] ==== How the architecture components interact with each other @@ -79,7 +94,7 @@ image::UiClassDiagram.png[] *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` 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`, `TaskListPanel` 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`] @@ -88,48 +103,46 @@ The `UI` component, * Executes user commands using the `Logic` component. * Listens for changes to `Model` data so that the UI can be updated with the modified data. +//tag::logic[] [[Design-Logic]] === Logic component [[fig-LogicClassDiagram]] .Structure of the Logic Component -image::LogicClassDiagram.png[] +image::LogicComponentUML.png[] *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 `ElisaParser` 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). +. The command execution can affect the `ItemModel` (e.g. adding an Item). . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. . In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. +. More instructions for the `Ui` can be given through implementing `ScrollCommand` -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeleteSequenceDiagram.png[] - -NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +//end::logic[] +//tag::model[] [[Design-Model]] === Model component - .Structure of the Model Component -image::ModelClassDiagram.png[] +image::ModelClassDiagram.png[500,500] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/elisa/model/ItemModel.java[`ItemModel.java`] -The `Model`, +The `ItemModel`, * 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. -* does not depend on any of the other three components. +* contains the `ItemStorage` that stores all the data for ELISA. +* exposes four observable lists that can be viewed by the `Ui` and will cause an update in the `Ui` when it is updated. -[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. + - + -image:BetterModelClassDiagram.png[] +==== Design Consideration +The original implementation was to have one single observable list and modifying it whenever the view changes. We decided against it as we believe that this will result in O(n) time complexity for switching the different views as we need to iterate through all the items to find the relevant items. + +Using the four visualization list, we only need to change the pointer when we want to change the view of the viewing panel making it an O(1) time complexity solution. However, this makes it more complicated to maintain the different lists. +//end::model[] [[Design-Storage]] === Storage component @@ -142,7 +155,7 @@ image::StorageClassDiagram.png[] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* can save the Item Storage data in json format and read it back. [[Design-Commons]] === Common classes @@ -153,95 +166,563 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +//tag::add[] +=== Add Task/Event feature +Task, Events and Reminders are all Items and can be added using the *same* command. Adding of Items is facilitated by ItemModel#add(Item). + + +==== Implementation +A Task with a deadline flag `-d` will be considered an Event. A Task with a reminder flag `-r` will be considered a Reminder. + +The following activity diagram shows the how a task can be added, depending on the flags present: + +.Activity Diagram of adding a Task +image::AddTaskActivity.png[200, 600] + +This shows how we can easily add Task, Event and Reminder with a _single_ command. +However, in this section, we will only show how Task and Event is added. Adding of Reminders is shown in a separate section as it includes other steps. + +==== Internal workings of the command +Given below is an example usage scenario of how add behaves at each step: + + +Step 1. The user enters the command `"task shower -d 1.hour.later"`. + +Step 2. The LogicManager creates an AddressBookParser to parse the user input. + +Step 3. AddressBookParser creates a AddTaskCommandParser which parses the input and returns an AddCommand. + +Step 4. LogicManager will execute the AddCommand. AddCommand will then invoke `ItemModel#add(Item)`, which adds Task to its TaskList and Event to its EventList. + +Step 5. AddCommand will also trigger a change in view by calling `ItemModel#setVisualList(taskList)` +Step 6. Upon the successful execution of AddCommand, a CommandResult is returned to the LogicManager, which will then be returned to the Ui to render the appropriate view. + +The figures below shows the sequence diagram on what happens from a simple execution of `task shower -d 1.min.later` user command: + + +.Call execute in LogicManager to create an AddTaskCommand +image::AddCommandPart_1.png[] + +This diagram shows how `execute` is carried out in the `Logic` component. +The following diagram shows how the same command is continued onto the `Model` component: + +.LogicManager executes the AddTaskCommand and returns CommandResult +image::AddCommandPart_2.png[] + +This shows how `execute(model)` affects the `Model` component. It then returns a `CommandResult` r, which is the result of calling LogicManager#execute("task shower -d 1.hour.later"). + +==== Design considerations +The design considerations for the classes are shown below: +|=== +|Alternatives: |Pros: |Cons: +|1. Placing all fields into an Item object and retrieving the specific fields when needed. | Easy to implement. Reduce dependencies between classes as everything is in one class. Editing can be done all in one object.| Does not separate out the different functionalities of Task, Event and Reminder. +|2. Having separate classes for Task, Event, Reminder | Reduce dependencies as well as having functionalities separated. | Some attributes and methods overlap. Repetition of code for same functionality. Have to add all three objects individually. Editing of an item would require searching, obtaining and individually editing all 3 objects. +|3. *(Current)* Having a general Item class which comprise of `Optional<>` fields Task, Event, Reminder | Similar fields such as description and priority can be placed in Item. This makes adding an Item more convenient. Related task/event/reminder can have access to each other. | Increase coupling and dependency amongst Task, Event, Reminder classes. +|=== +As of now, these are the considered designs and the current design seems to work well for our purpose. However, there could be better designs which are unexplored that could mitigate our cons and we welcome them. + + +This is end of the section of adding a Task and Event. As mentioned above, adding of Reminder will be shown in a separate section due it having extra features. Do look out for it if you're interested. +//end::add[] + +//tag::autoreschedule[] +=== Rescheduling of recurring Events automatically +This section talks about how the `autoReschedule` function is implemented. This feature aims to provide users with greater convenience when scheduling events that occur periodically (eg. weekly deadlines, monthly appointments). + +==== Implementation +To reschedule a task, we need a deadline as we need to be able to calculate the next date. Recall that any Task with a deadline is considered an Event. As such, *only Events can be rescheduled.* + +To automatically reschedule an Event, when creating the Event, include the `-auto` flag along with its reschedule period (eg `-auto day` for daily rescheduling) + +The accepted parameters for `-auto` is `day`, `week`, `month` and the format of `10.min.later`. + +The following diagram shows the process of adding an Event with -auto flag: + +[Cols="2,2"] +|=== +|| +a|.Activity diagram of AutoReschedule +image:AutoRescheduleActivity.png[100, 200] +|In the diagram, when we add the Event initially, we would check the start time of the Event and update it accordingly. +However, this is not the only place where rescheduling occurs. + + +*3 places where rescheduling can occur:* + + +1) When the event is created, as shown in the diagram above. + +2) While the app is running, the Event's start time will be continuously updated when it has passed. + +This is done using `Timer` and `TimerTask`, using `Timer#scheduleAtFixedRate()`. + +3) When the app is started and Events are loaded from the storage. The stored Event time might already be over, as such the time has to be updated to the latest upcoming one. +|=== + + +To illustrate how they work, first we need to know what classes are involved before we can understand the sequence of actions carried out. + +The classes involved in the above rescheduling are: + + +* `AutoReschedulePeriod` -- Represents the period of every reschedule (eg day/week/month) +* `RescheduleTask` -- Represents the action to perform when rescheduling its associated event. +* `AutoRescheduleManager` -- Manages all the rescheduling tasks. There is only one of such manager. + +To better understand its underlying structure, we can look at the class diagram below: + +.Class Diagram of classes involved in AutoReschedule function +image::AutoRescheduleClassDiagram.png[100, 500] + +==== Internal workings of the command +Now we are ready to look at the sequence of actions. Given below is an example usage scenario of how add behaves at each step: + + +Step 1. The user enters the command `event CS2103T Quiz -d 23/09/2019 2359 -auto week`. + +Step 2. The Event is created, following the sequence of steps in the section _Adding Task/Event_. However there are now some extra steps from Step 3 onwards that occur concurrently from the object creation of Event. + +Step 3a. The presence of the `-auto week` creates an AutoReschedulePeriod, which is stored in the Event created. This can be seen in the Class Diagram above. + +Step 3b. If the start time of this Event is already over, the Event's start time will be modified to show the next start time, using this Event's AutoReschedulePeriod. +Step 4. When `LogicManager#execute(model)` is called, the presence of AutoReschedulePeriod in the Event triggers the creation of a RescheduleTask, which represents the task of rescheduling this Event. + +Step 5. This RescheduleTask is added to an AutoRescheduleManager, which manages all RescheduleTasks. + +Step 6. When the start time of this Event has passed, AutoRescheduleManager will call `RescheduleTask#run()`, and this updates the Event start time, which will be reflected in the Ui. + + + +The following diagrams show how the command `event Quiz -d 10.hour.later -auto week` is executed from the Logic component. +The first diagram shows the adding of an Event, which may appear familiar as it has a sequence similar to the adding of task in <>. However, there are some minor differences due to the presence of `-auto` which should be noted. + +.Call execute in LogicManager and create an AddEventCommand +image::AutoRescheduleSequence_1.png[] + +As mentioned, the key points to take note of in the diagram above is `Event#setAutoReschedule(true)` and `Event#setReschedulePeriod(period)`. + +The significance of these methods will be shown in the continuing diagram below: + +.LogicManager executing AddEventCommand and create task for AutoRescheduleManager +image::AutoRescheduleSequence_2.png[100, 700] + +From the above diagram, we can see that the presence of `AutoReschedulePeriod` in Event results in the creation of `RescheduleTask` which would be queued into the Timer managed by `AutoRescheduleManager`. + +==== Design considerations +The design considerations for the classes are shown below: + + +*Alternative 1:* Creating a AutoRescheduleManager for every RescheduleTask + +*Pros*: Easy for the Timer in AutoRescheduleManager to keep track of its TimerTask. + +*Cons*: There could potentially be many Timer threads. + +*Alternative 2: (Current)* Singleton pattern for AutoRescheduleManager + +*Pros*: Ensure that only one instance can be instantiated as there should only be one manager for all the RescheduleTask. If there are multiple managers, it would be hard to keep track of all of them and it would be difficult to coordinate all the tasks. + +*Cons*: Difficult to create tests for AutoRescheduleManager. Could have many hidden dependencies, which makes code harder to maintain. + +//end::autoreschedule[] + + +=== Game feature + +This section talks about how the `game` function is implemented. This feature aims to encourage users to take a break by playing the traditional Nokia (phone) game: Snake. + +==== Implementation +The game screen appears after the user enters the command `game` into the command box. A separate scene handled by a separate thread is created to run the game so that Elisa's scenes and threads are not overloaded. The following activity diagram shows how the game screen is launched. + +.Activity diagram for game mode +image::GameActivityDiagram.png[500, 600] + +==== Internal workings of the Game + +Given below is an example usage scenario of how add behaves at each step: + + + +Step 1. The user enters the command "game". + +Step 2. The LogicManager creates an ElisaParser to parse the user input. + +Step 3. ElisaParser creates a GameCommandParser to parse the user input. + +Step 4. LogicManager will execute the GameCommand. GameCommand will create a GameCommandResult. + +Step 5. MainWindow will call its own method startgame(). + + + +Step 6. startgame() calls the method resetgame(), which creates a Grid and GameLoop. + +Step 7. startgame() calls the method gameCheck on the canvas to check the keys that are pressed during the game. + +Step 8. startgame() creates a new scene and sets primaryStage's scene to this new scene. + +Step 9. startgame() creates a new thread to run the game. + + +The figure below shows the sequence diagram on what happens after the startgame() method is called. + +.Sequence Diagram for game creation +image::CreatingGameSequenceDiagram.png[500, 600] + +==== Game considerations +The considerations for the choice of game is shown below: +|=== +|Alternatives: |Pros: |Cons: +|1. Minesweeper | It is a well-known popular desktop game. | It is difficult to understand the implied rules to this game and this game requires a long time to complete. +|2. Sudoku | It is a well-known popular pen-and-paper game. | It is difficult to understand the implied rules to this game and this game requires a long time to complete. +|3. *(Current)* Snake | It is a well-known popular Nokia (phone) game. The rules are simple to understand. The duration for each game is short. | It is addictive when users try to break their high score. +|=== + +==== Design considerations +The design considerations for the placement of the game is shown below: +|=== +|Alternatives: |Pros: |Cons: +|1. Placing the game in one of the tabs (beside the Calendar) such that `show g` will switch tab to the game tab. | Users are able to see the chatbox while in the game. | Requires users to use the mouse (away from CLI) to switch between typing in the command box and hitting the keys on the keyboard. +|2. *(Current)* Creating a separate scene such that the original scene (with ELISA) is hidden when game mode is entered. | Allows users to hit the keys on the keyboard without typing into the command box. | Users are unable to see the chatbox from Elisa. +|=== + +// tag::undobyreverse[] +=== [Implemented] Undo/Redo Feature +==== Current Implementation Logic + +The undo function uses the revert command method without using states and history, unlike the proposed method. +This is because an issue was encountered with referencing lists and firing reminders multiple times when the state history method was used. + +In this implementation, the commands that can be undone; that is, all the commands except `UndoCommand`, `ExitCommand`, +`UpCommand` and `DownCommand` now extend from an abstract class `UndoableCommand`, which is a subclass of Command. +Subclasses of `UndoableCommand` must implement a method `reverse(ItemModel model)`, which should do the exact opposite +of the `execute(ItemModel model)` in that Command. + +The command execution history is stored in a stack, which is maintained in `ElisaCommandHistory`. + +The activity diagram below shows the flow of the undo command. + +image::UndoActivity.png[][,250] + +Below is a possible usage scenario and the app behaviour. + +Step 1. The user executes `task eat`. A task with description "eat" is added and then the command is pushed into the commands stack. + +image::UndoStackStep1.png[][,250] + +Step 2. The user realises that adding the task was a mistake, and decides to undo by entering `undo` into ELISA. The `undo` command +will pop the `AddTaskCommand` from the stack and reverse the effects of that command, in this case by deleting the task "eat" from the `TaskList`. + +image::UndoStackStep2.png[][,450] + +Step 3. After successful execution of the `UndoCommand` a confirmation message is displayed in the chat box. + +Of course, the undo feature has its counterpart, the redo command. The commands to be redone are stored in an additional stack within `ElisaCommandHistory`, +and when the redo is done, it executes the command again, which reapplies the most recent change. Every time a new `UndoableCommand` is executed, the redo stack is cleared. +That is, the redo stack is only non empty when the latest executed `Command`(s) is/are `UndoCommand`(s). + +The activity diagram below shows the flow of the redo command. + +image::RedoActivity.png[][,250] + +// end::undobyreverse[] + // tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +=== [Previous] Undo/Redo feature +==== Previous Implementation [Has since been refactored] -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 `ElisaStateHistory`. +It stores a stack of type `ElisaState`, which keeps track of the app data with a deep copy of the `ItemStorage` and the current displayed list with a deep copy of `VisualizeList`. 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. - -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +* `ElisaStateHistory#push()` -- Saves the current ELISA state in its history. +* `ElisaStateHistory#pop()` -- Removes and returns the latest state as long as there is more than 1 item in the stack. This 1 item is the original application state that is saved on startup. +* `ElisaStateHistory#size()` -- Returns the number of states in the stack. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Given below is an example usage scenario and how the undo 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 `ElisaStateHistory` will be initialized with the initial ELISA state -image::UndoRedoState0.png[] +image::UndoState0.png[][, 500] -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 `task eat` command to add a task with a description "eat". Upon execution of any command except `undo` and `show`, the `Logic` calls `ItemModel#updateState()` to save the latest state to `ElisaStateHistory`. -image::UndoRedoState1.png[] +image::UndoState1.png[][, 500] -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 `task jogging` to add another task with description "jogging". This command also calls `ItemModel#updateState()`, causing another modified ELISA state to be saved into the `ElisaStateHistory`. -image::UndoRedoState2.png[] +image::UndoState2.png[][, 500] [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 `ItemModel#updateState()`, so the ELISA state will not be saved. -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 task "jogging" was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `ElisaStateHistory#popCommand()`, which will delete the last added state, and `Logic` will update the application state to the one at the top of the stack after command execution. -image::UndoRedoState3.png[] +image::UndoState3.png[][, 500] [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 size of `ElisaStateHistory` is 1, that is, only containing the initial state, then there are no previous Elisa states to restore. The `undo` command checks for this case within its `execute()` method. 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::UndoSequenceDiagram.png[] +image::UndoStateSequence.png[] -NOTE: The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +==== Design Considerations + +===== Aspect: How undo & redo executes + +* **Alternative 1 (initial choice):** Saves the entire Elisa state. +** Pros: Easy to implement. +** Cons: May have performance issues in terms of memory usage, especially when a large number of items are added. +** Another Con found after implementation: Reminders fire multiple times due to storing multiple references to items +* **Alternative 2 (current choice):** Individual command knows how to undo/redo by itself. +** Pros: Will use less memory (e.g. for `delete`, just save the item being deleted). +** Cons: We must ensure that the implementation of each individual command are correct. + +// end::undoredo[] + +// tag::priority[] +=== Priority Mode + +==== Implementation + +The priority mode is used to aid the user in focusing on the most pressing task that they have especially when they have many tasks in their list. As priority mode is only for clearing of tasks, the priority mode can only be activated at the task pane of the application. + +The priority mode is mainly controlled in the ```ItemModelManager``` and the following are the methods it uses within the ```ItemModelManager```: + +* ```ItemModelManager#togglePriorityMode()``` - Toggle the priority mode depending on whether it is on or off. +* ```ItemModelManager#toggleOnPriorityMode()``` - Helper function to toggle on the priority mode. +* ```ItemModelManager#toggleOffPriorityMode()``` - Helper function to toggle off the priority mode. + +There are two variants to the priority mode, a normal priority mode and a focus mode. The focus mode is more restrictive than the normal priority mode, preventing the user from doing any operations that are not relevant to the task list, such as adding a new event. This is currently implemented by having a separate `Parser` when ELISA is in focus mode. (Refer to <> for more details) + +There are two ways to trigger priority mode, a normal priority mode that is controlled fully by the user and a scheduled priority mode that is triggered by the user but is scheduled to turn off after a specific amount of time. In addition to the above three methods, the scheduled priority mode also uses the following method: + +* ```ItemModelManager#startTimer(LocalDateTime)``` - Starts a timer to turn off the priority mode. + +==== Example run of priority mode + +In this section, we will show a run of the priority mode and a overview of the mechanism at each step. In particular, we will be showing how the ```ScheduledPriorityCommand``` works as it has a more complicated implementation than the normal ```PriorityCommand```. + +. The user opens his Task panel and types in `priority 30.min.later`. -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 incomplete tasks are added to a ```PriorityQueue``` where they are ranked by their priority. + +. Once all the items are added into the ```PriorityQueue```, ELISA will peek the first task from the queue and present it to the user. + +. The user can type ```done 1``` when he is done with the current task to retrieve the next task. This carries on until there is no more undone task left to do in the ```PriorityQueue```. This is shown in the activity diagram below. + +.Activity diagram for priority mode +image::PriorityModeActivityDiagram.png[300, 300] + +[start=5] +. ELISA will automatically disable the priority mode after 30 minutes and show *all* the task that the user have in his task list currently. + +==== Internal working of the command + +The figure below shows the sequence diagram on what happens from a simple execution of the ```priority 30.min.later``` command. We will go through the internal mechanism of the execution of the ```ScheduledPriorityCommand```. + +.Sequence diagram for priority mode +image::PriorityMode.png[] + +. When the user types in the command, the ```LogicManager``` takes in the command as a string and pass it to the ```ELISAParser``` + +. The ```ELISAParser``` parses the string and determine whether the command is that of a normal ```PriorityCommand``` or a ```ScheduledPriorityCommand```. In this case, a new ```ScheduledPriorityCommand``` is created and is passed back to the ```LogicManager```. [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. +The following steps (except step 4) are also applicable to ```PriorityCommand```. -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. +[start=3] +. Within the ```LogicManager```, the ```ScheduledPriorityCommand#execute()``` method is called and the command is executed. -image::UndoRedoState4.png[] +. The ```ScheduledPriorityCommand``` calls the ```ItemModel#scheduleOffPriorityMethod()``` which creates a new ```Timer``` object and a new ```TimerTask``` object. The ```TimerTask``` object will be scheduled to fire off at a specific time, which in this case is 30 minutes later (as defined by the user). -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. +. The ```SchedulePriorityCommand``` then calls ```ItemModel#togglePriorityMode()``` which calls the private method ```ItemModel#toggleOnPriorityMode()``` (since the current state of the priority mode is false). -image::UndoRedoState5.png[] +. This creates a new ```TaskList``` which will have the task with the highest priority added to it. This ```TaskList``` will be displayed to the user. -The following activity diagram summarizes what happens when a user executes a new command: +. A ```CommandResult``` is passed to the ```ScheduledPriorityCommand``` and then back to the ```LogicManager``` to be passed into the ```Ui```, informing the user that the priority mode is activate. -image::CommitActivityDiagram.png[] +[NOTE] +A normal ```PriorityCommand``` will end at this point and will only be deactivated by the user's input of `priority` again. -==== Design Considerations +.Sequence diagram for the scheduled turning off of priority mode +image::PriorityMode2.png[300, 300] -===== Aspect: How undo & redo executes +[start=8] +. As the ```Timer``` within the ```ItemModelManager``` is still running on a separate thread, it will trigger the ```TimerTask#run()``` when the user defined time is reached. -* **Alternative 1 (current choice):** Saves the entire address book. -** 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). -** Cons: We must ensure that the implementation of each individual command are correct. +. The ```TimerTask``` will call ```ItemModelManager#toggleOffPriorityMode()``` which will cancel the ```Timer``` and destroy the ```Timer```. This is to ensure proper cleanup of the thread. -===== Aspect: Data structure to support the undo/redo commands +. All the items are added back into the ```TaskList``` and shown to the user. The priority mode is deactivated. -* **Alternative 1 (current choice):** Use a list to store the history of address book 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`. -* **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[] +[[Priority-Design-Consideration]] +==== Design Consideration +*Aspect: How to restrict commands for focus mode* + +* Alternative 1: Storing a boolean within the ``ItemModelManager`` to check if the application is in focus mode or normal mode. Commands that are not allowed to be called in focus mode will check against this boolean to determine if the command is allowed. + +|=== +|Pros |Cons + +|This implementation will contain the changes within the class of the ```Command``` itself and will ensure that they do not interfere with each other. This will make it easier to maintain the code. +|This implementation is not scalable as each new ```Command``` that is added will need to be checked to see if they are allowed in focus mode. There is also the additional overhead of checking the state of the ```ItemModelManager``` at every call of ```Command#execute()```. +|=== + + +* Alternative 2 (Current implementation): Create a new ```FocusElisaParser``` that extends from the current ```ElisaParser``` but prevent the parsing of commands that are not allowed in focus mode. + +|=== +|Pros |Cons + +|This implementation stops the creation of the ```Command``` at the ```Parser``` level which will reduce the computational cost to the application. + +|There might be difficulty in maintaining the ```Parser#parse()``` method of the two ```Parser```. +|=== + +Both methods are not scalable in the long run, but at this moment, alternative 2 is favoured as it prevents the command from even being parsed or created, which saves the computing time. At the same time, it is easier to maintain as one only needs to edit the ```Parser#parse()``` method instead of having an if-else loop in all the command that are banned. + +*Aspect: How to turn off the priority mode after a fixed time* + +* Alternative 1: Storing the timer within the ```ScheduledPriorityCommand```. + +|=== +|Pros |Cons + +|Adheres to the SLAP principle with each class having it's own implementation of the ```Command#execute(ItemModel)```. It is easier to maintain the code and prevents overloading the ```ItemModelManager```. +|There is no way to end the schedule priority mode prematurely as the timer is kept within the command and so cannot be referenced after the execution of the command. +|=== + +* Alternative 2 (Current implementation): Storing the timer within the ```ItemModelManager```. + +|=== +|Pros|Cons + +|The timer can be referenced from the ```ItemModelManager``` and so it can be cancelled prematurely if the user chooses to do so. +|This implementation will clutter the ```ItemModelManager``` further and make it harder for maintaining the code. +|=== + +Alternative 2 was chosen as we believe that the ability to cancel a scheduled priority mode prematurely is more important than the maintainability of the code at the moment. + +==== Possible extension + +At the moment, the user is not able to keep track of the amount of time that he has before the schedule priority mode is over. This can be improved by including a countdown timer in the Ui when the user toggles on the scheduled priority mode. + +// end::priority[] // tag::dataencryption[] -=== [Proposed] Data Encryption +=== Add Task/Event feature +Task, Events and Reminders are all Items and can be added using the *same* command. Adding of Items is facilitated by ItemModel#add(Item). + + +==== Implementation +A Task with a deadline flag `-d` will be considered an Event. A Task with a reminder flag `-r` will be considered a Reminder. + +The following activity diagram shows the how a task can be added, depending on the flags present: + +.Activity Diagram of adding a Task +image::AddTaskActivity.png[200, 600] + +This shows how we can easily add Task, Event and Reminder with a _single_ command. +However, in this section, we will only show how Task and Event is added. Adding of Reminders is shown in a separate section as it includes other steps. + +==== Internal workings of the command +Given below is an example usage scenario of how add behaves at each step: + + +Step 1. The user enters the command `"task shower -d 1.hour.later"`. + +Step 2. The LogicManager creates an ELISAParser to parse the user input. + +Step 3. ELISAParser creates a AddTaskCommandParser which parses the input and returns an AddCommand. + +Step 4. LogicManager will execute the AddCommand. AddCommand will then invoke `ItemModel#add(Item)`, which adds Task to its TaskList and Event to its EventList. + +Step 5. AddCommand will also trigger a change in view by calling `ItemModel#setVisualList(taskList)` +Step 6. Upon the successful execution of AddCommand, a CommandResult is returned to the LogicManager, which will then be returned to the Ui to render the appropriate view. + +The figures below shows the sequence diagram on what happens from a simple execution of `task shower -d 1.min.later` user command: + + +.Call execute in LogicManager to create an AddTaskCommand +image::AddCommandPart_1.png[] + +This diagram shows how `execute` is carried out in the `Logic` component. +The following diagram shows how the same command is continued onto the `Model` component: + +.LogicManager executes the AddTaskCommand and returns CommandResult +image::AddCommandPart_2.png[] + +This shows how `execute(model)` affects the `Model` component. It then returns a `CommandResult` r, which is the result of calling LogicManager#execute("task shower -d 1.hour.later"). + +==== Design considerations +The design considerations for the classes are shown below: +|=== +|Alternatives: |Pros: |Cons: +|1. Placing all fields into an Item object and retrieving the specific fields when needed. | Easy to implement. Reduce dependencies between classes as everything is in one class. Editing can be done all in one object.| Does not separate out the different functionalities of Task, Event and Reminder. +|2. Having separate classes for Task, Event, Reminder | Reduce dependencies as well as having functionalities separated. | Some attributes and methods overlap. Repetition of code for same functionality. Have to add all three objects individually. Editing of an item would require searching, obtaining and individually editing all 3 objects. +|3. *(Current)* Having a general Item class which comprise of `Optional<>` fields Task, Event, Reminder | Similar fields such as description and priority can be placed in Item. This makes adding an Item more convenient. Related task/event/reminder can have access to each other. | Increase coupling and dependency amongst Task, Event, Reminder classes. +|=== +As of now, these are the considered designs and the current design seems to work well for our purpose. However, there could be better designs which are unexplored that could mitigate our cons and we welcome them. + + +This is end of the section of adding a Task and Event. As mentioned above, adding of Reminder will be shown in a separate section due it having extra features. Do look out for it if you're interested. + +=== Rescheduling of recurring Events automatically +This section talks about how the `autoReschedule` function is implemented. This feature aims to provide users with greater convenience when scheduling events that occur periodically (eg. weekly deadlines, monthly appointments). + +==== Implementation +To reschedule a task, we need a deadline as we need to be able to calculate the next date. Recall that any Task with a deadline is considered an Event. As such, *only Events can be rescheduled.* + +To automatically reschedule an Event, when creating the Event, include the `-auto` flag along with its reschedule period (eg `-auto day` for daily rescheduling) + +The accepted parameters for `-auto` is `day`, `week`, `month` and the format of `10.min.later`. + +The following diagram shows the process of adding an Event with -auto flag: + +.Activity diagram of adding an Event with -auto flag +image::AutoRescheduleActivity.png[100, 400] + +In the diagram, when we add the Event initially, we would check the start time of the Event and update it accordingly. +However, this is not the only place where rescheduling occurs. + +*3 places where rescheduling can occur:* + -_{Explain here how the data encryption feature will be implemented}_ +. When the event is created, as shown in the diagram above. +. While the app is running, the Event's start time will be continuously updated when it has passed. + +This is done using `Timer` and `TimerTask`, using `Timer#scheduleAtFixedRate()`. +. When the app is started and Events are loaded from the storage. The stored Event time might already be over, as such the time has to be updated to the latest upcoming one. + +To illustrate how they work, first we need to know what classes are involved before we can understand the sequence of actions carried out. + +The classes involved in the above rescheduling are: + + +* `AutoReschedulePeriod` -- Represents the period of every reschedule (eg day/week/month) +* `RescheduleTask` -- Represents the action to perform when rescheduling its associated event. +* `AutoRescheduleManager` -- Manages all the rescheduling tasks. There is only one of such manager. + +To better understand its underlying structure, we can look at the class diagram below: + +.Class Diagram of classes involved in AutoReschedule function +image::AutoRescheduleClassDiagram.png[100, 500] + +==== Internal workings of the command +Now we are ready to look at the sequence of actions. Given below is an example usage scenario of how add behaves at each step: + + +Step 1. The user enters the command `event CS2103T Quiz -d 23/09/2019 2359 -auto week`. + +Step 2. The Event is created, following the sequence of steps in the section _Adding Task/Event_. However there are now some extra steps from Step 3 onwards that occur concurrently from the object creation of Event. + +Step 3a. The presence of the `-auto week` creates an AutoReschedulePeriod, which is stored in the Event created. This can be seen in the Class Diagram above. + +Step 3b. If the start time of this Event is already over, the Event's start time will be modified to show the next start time, using this Event's AutoReschedulePeriod. +Step 4. When `LogicManager#execute(model)` is called, the presence of AutoReschedulePeriod in the Event triggers the creation of a RescheduleTask, which represents the task of rescheduling this Event. + +Step 5. This RescheduleTask is added to an AutoRescheduleManager, which manages all RescheduleTasks. + +Step 6. When the start time of this Event has passed, AutoRescheduleManager will call `RescheduleTask#run()`, and this updates the Event start time, which will be reflected in the Ui. + + + +The following diagrams show how the command `event Quiz -d 10.hour.later -auto week` is executed from the Logic component. +The first diagram shows the adding of an Event, which may appear familiar as it has a sequence similar to the adding of task in <>. However, there are some minor differences due to the presence of `-auto` which should be noted. + +.Call execute in LogicManager and create an AddEventCommand +image::AutoRescheduleSequence_1.png[] + +As mentioned, the key points to take note of in the diagram above is `Event#setAutoReschedule(true)` and `Event#setReschedulePeriod(period)`. + +The significance of these methods will be shown in the continuing diagram below: + +.LogicManager executing AddEventCommand and create task for AutoRescheduleManager +image::AutoRescheduleSequence_2.png[] + +From the above diagram, we can see that the presence of `AutoReschedulePeriod` in Event results in the creation of `RescheduleTask` which would be queued into the Timer managed by `AutoRescheduleManager`. + + +==== Design considerations +The design considerations for the classes are shown below: +|=== +|Alternatives: |Pros: |Cons: +|Creating a AutoRescheduleManager for every RescheduleTask | Easy for the Timer in AutoRescheduleManager to keep track of its TimerTask. | There could potentially be many Timer threads. +|*(Current)* Singleton pattern for AutoRescheduleManager |Ensure that only one instance can be instantiated as there should only be one manager for all the RescheduleTask. If there are multiple managers, it would be hard to keep track of all of them and it would be difficult to coordinate all the tasks. | Difficult to create tests for AutoResheduleManager. Could have many hidden dependencies, which makes code harder to maintain. +|=== // end::dataencryption[] +// tag::addreminder[] +=== Add Reminder feature + +Step 1. The user enters the command `"reminder John's Birthday -r 3.day.later"`. + +Step 2. The LogicManager creates an ELISAParser to parse the user input. + +Step 3. ELISAParser creates a AddReminderCommandParser which parses the input and returns an ReminderCommand. + +Step 4. LogicManager will execute the ReminderCommand. AddCommand will then invoke `ItemModel#add(Item)`, which adds the Item that contains the reminder to the `RemindersList` and the `futureRemindersList`. + +Step 5. Each time an Item is added to the `futureRemindersList`, the Items in futureRemindersList is sorted according to the `occurrenceDateTime` of its Reminder in order of recency. + +Step 6. AddCommand will also trigger a change in view by calling `ItemModel#setVisualList(taskList)` + +Step 7. Upon the successful execution of AddCommand, a CommandResult is returned to the LogicManager, which will then be returned to the Ui to render the appropriate view. + + +When the application is launched, the LogicManager also creates a separate thread using the `ScheduledThreadPoolExecutor` that constantly runs checkTask every 5 seconds. + +CheckTask is a runnable that monitors the ItemModelManager to see if there are any reminders in `futureRemindersList` that have an `occurrenceDateTime` after `LocalDateTime#now()`. + +If there is, it transfers those reminders to `activeRemindersList` which is an ObservableList. + + +Meanwhile, the MainWindow binds a listener `activeRemindersList` in order to observe if any Items with Reminders have been added to it. + +If so, it will add `ReminderDialogBox` with the appropriate details for the added Items to the `resultDisplay`. + +// end::addreminder[] + +// tag::snoozereminder[] +=== Snooze Reminder feature +We can see how snooze is performed through the activity diagram below. + +.Activity Diagram to snooze a reminder +image::SnoozeActivityDiagram.png[] + +From the image above, we can see that there 2 guard conditions. + +The first checks whether there is a reminder to snooze (either via an index or recently occurred reminder). If there isn't, an exception is thrown. + +The second checks if the `newReminderOccurrenceIsValid` (i.e. reminder does not occur in the past). Otherwise, it throws an exception. + +If the duration of the reminder is not specified, then the default duration for the snooze is 5 minutes. + +// end::snoozereminder[] === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -262,6 +743,7 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + == Documentation Refer to the guide <>. @@ -279,75 +761,256 @@ Refer to the guide <>. *Target user profile*: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast +* has a need to manage a large number of tasks +* needs reminders to take breaks or move on to finish all their work +* prefers to be more organized with their time and tasks +* prefer desktop apps over other types of apps * prefers typing over mouse input +* can type fast * is reasonably comfortable using CLI apps -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*Value proposition*: manage time and tasks more efficiently than a typical mouse/GUI driven app +// tag::userstories[] [appendix] == User Stories Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +[cols="20%,<20%,<25%,<35%",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 +|`* * *` |user |add a new task | record tasks that need to be done 'some day' -|`* * *` |user |add a new person | +|`* * *` |user |mark a task as done | keep track of my remaining tasks -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |user |delete a task |remove tasks that I no longer need -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |student |add deadline to a task |remember my task deadlines -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* * *` |user |find upcoming tasks |decide what needs to be done -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* * *` |user |find a task by description |find only the tasks that are relevant to me at that point in time + +|`* * *` |new user |view more information about a command |learn how to use various commands + +|`* * *` |forgetful student |be reminded of deadlines |remember to complete them before they are due + +|`* * *` |user |type my commands in the text |use the app without needing the mouse + +|`* * *` |user |use the undo function |reverse any changes I made by mistake + +|`* *` |busy student |see my reminders as notifications|be reminded of them even in other applications + +|`* *` |user with many tasks |sort tasks by priority |identify which tasks require my immediate attention + +|`* *` |student |turn on priority mode |focus on only one pressing issue at a time + +|`* *` |user |have a software that saves after every action |will not lose information even if I close the program by accident + +|`* *` |user |look at a summary of all deadlines in the calendar |see when I am free + +|`* *` |user |edit the date of a deadline |fix my mistakes if I type the wrong command + +|`*` |stressed student |ask ELISA to tell a joke |feel less stressed when my assistant has a sense of humour + +|`*` |user |colour code my calendar events |easily categorise and differentiate between them +|======================================================================= +// end::userstories[] _{More to be added}_ [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is `ELISA` and the *Actor* is the `user`, unless specified otherwise) [discrete] -=== Use case: Delete person - +=== Use Case 001: Marking a task as done *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 enters the command to show the task list. +2. ELISA shows the task list to the user. +3. User marks the task as done by using the index of the task. +4. ELISA updates the task list. +5. ELISA shows the updated task list to the user. + Use case ends. *Extensions* [none] -* 2a. The list is empty. +* 2a. The task list is empty. + Use case ends. * 3a. The given index is invalid. + -[none] -** 3a1. AddressBook shows an error message. +** 3a1. ELISA shows an error message. + Use case resumes at step 2. +[discrete] +=== Use Case 002: Adding a task +*MSS* + +1. User creates a new task with a description. +2. ELISA informs the user that the task has been added. ++ +Use case ends. + +*Extensions* + +* 1a. User can add deadline. ++ +** 1a1. ELISA informs the user that a deadline has been added to that task. ++ +Use case ends + +* 1b. User can add a reminder date. ++ +** 1b1. ELISA informs the user that a reminder has been added to that task. ++ +Use case ends + +* 1c. User enters an empty description. ++ +** 1c1. ELISA informs the user that the description cannot be empty. ++ +** 1c2. User enters a non-empty description ++ +Use case resumes at step 2 + +* a. At any time, User can view the task from the task list or the calendar view. +* b. At any time, User can add a deadline to the task +* c. At any time, User can add a reminder to the task + +[discrete] +=== Use Case 003: Deleting a task + +*MSS* + +1. User enters the command to show the task list. +2. ELISA shows the task list. +3. User request to delete a task based on its index. +4. ELISA deletes the task from the task list. +5. ELISA shows the updated task list. + ++ +Use case ends. + +*Extensions* + +* 2a. The task list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +** 3a1. ELISA shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use Case 004: Snooze a reminder + +*MSS* + +1. User enters the snooze command. +2. ELISA snoozes the reminder. +3. ELISA reminds the user of that reminder after the specified duration. + +*Extensions* + +* 1a. There are no recent reminders to be snooze ++ +** 1a1. ELISA displays an error message. ++ +Use case ends. + +* 1b. The user does not which reminder to snooze. ++ +** 1b1. ELISA snoozes the most recent reminder. + +Use case resumes at step 2. + +* 1c. The user does not specify how long to snooze. ++ +** 1c1. ELISA snoozes the reminder for a default duration. + +Use case resumes at step 2. + +[discrete] +=== Use Case 005: Find upcoming reminders. + +*MSS* + +1. User enters the command to show the reminder list. +2. ELISA shows the reminder list. +3. User enters command to sort reminders by date and time. +4. ELISA shows the updated list of reminders sorted by date and time. +// tag::usecaselrchema[] + +[discrete] +=== Use Case 006: Search for a task by its description + +*MSS* + +1. User enters the command to show the task list. +2. ELISA shows the reminder list. +3. User enters command to find all matching tasks with the given search term(s) +4. ELISA shows a list of tasks with descriptions matching the search term(s) + +*Extensions* + +* 4a. There are no matching tasks ++ +** 4a1. ELISA shows that there are 0 items listed ++ +Use case ends. + +[discrete] +=== Use Case 007: Undo the last command + +*MSS* + +1. User enters the undo command. +2. ELISA reverts the last executed command. +3. ELISA displays a confirmation message. + +*Extensions* + +* 2a. There are no commands to be undone ++ +** 2a1. ELISA displays an error message. ++ +Use case ends. + +[discrete] +=== Use Case 008: Using Priority Mode + +*MSS* + +1. User enters the command to enter priority mode. +2. ELISA hides all tasks except the one with the highest priority. +3. User enters command to set that task as done once they finish it. +4. ELISA shows the next highest priority task. + +*Extensions* + +* 1a. User is not viewing the task list ++ +** 1a1. ELISA displays an error message ++ +Use case ends. +// end::usecaselrchema[] _{More to be added}_ [appendix] == Non Functional Requirements . Should work on any <> as long as it has Java `11` or above 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 tasks / events / reminders (combined) 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}_ @@ -392,7 +1055,7 @@ These instructions only provide a starting point for testers to work on; testers .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + Expected: Shows the GUI with an empty set of tasks. The window size may not be optimum. . Saving window preferences @@ -400,26 +1063,200 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +=== Adding a task + +. Adding a task while on the task list panel +.. Prerequisites: Switch to the task list panel using the `show T` command. +.. Test case: `task testcase` + +Expected: A task with the description "testcase" is added to the task list panel. The priority of the task is medium (the default value). +.. Test case: `task testcase 2 -p high` + +Expected: A task with the description "testcase 2" and of high priority is added to the task list panel. + +. Adding a task while not on the task list panel +.. Prerequisites: Switch to the event list panel using the `show E` command. +.. Test case: `task testcase 3 -p low -d 1.min.later` + +Expected: ELISA will automatically switch to the task list panel and a new task with the description "testcase 3" and priority low should be added to the task list panel. -=== Deleting a person +. Adding a task that already exists +.. Prerequisites: The task list panel must already be populated and a task on the list is already know. +.. Test case: `task testcase` (in this case, we are using one of the earlier test case) + +Expected: Elisa will prompt you that the task already exist in the list and cannot be added. +.. As an additional test case, you can mark a current task as done and then try adding that task into ELISA again. ELISA will still flag it as a repeated object despite the different state of completeness. -. Deleting a person while all persons are listed +[NOTE] +Even though this is for task, the same tests can be used for testing events and reminders. + +=== Deleting a task + +. Deleting a task while all tasks are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: Switch to the task list panel using the `show T` command. There should be multiple tasks 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. + Expected: First task is deleted from the list. Details of the deleted task will show in the result display. .. Test case: `delete 0` + - Expected: No person 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: No task is deleted. Error details shown in the result display. +.. Other incorrect delete commands to try: `delete`, `delete x` (where x is greater than the size of the list), `delete y` (where y is a negative index) + Expected: Similar to previous. +[NOTE] +Even though this is for task, the same tests can be used for testing events and reminders. + +=== Adding a reminder +. Adding a reminder +.. Prerequisites: Know the current date and time. Volume of computer is audible to tester. + +.. Test case: `reminder John's Birthday -r 1.min.later` + + Expected: View is switched to reminder list panel (if not already there). A reminder is added to the reminder list. Details of the reminder will be shown in the result display. After 1 minute, a chime is played and a red dialog box with the details of the reminder will appear in the result display. +.. Test case: `reminder John's Birthday -r x` (where x is the date and time 1 minute later from now in the format `dd/MM/yyyy HHmm`. E.g. `reminder John's Birthday` -r 15/11/2019 1800) + Expected: Similar to previous test case. (Since x is 1 minute from now.) + +.. Test case: `reminder John's Birthday -r y` (where y is the date and time 1 minute later from now in the format `yyyy-MM-ddTHHmm`. E.g. `reminder John's Birthday` -r 2019-11-15T18:00) + Expected: Similar to previous test case. (Since y is 1 minute from now.) + +.. Test case: `undo` (used after one of the above has taken place) + Expected: ELISA will reverse the addition of the reminder and no reminder will occur later on. + +.. Test case: `redo` (used after a valid undo command has taken place) + Expected: ELISA will reverse the undo of the addition of the reminder and a reminder will occur at the appropriate time. Note that if the time for the reminder to occur has already passed, the reminder will not occur. + +.. Test case: `reminder John's Birthday -r z` (where z is the date and time 1 minute before now in the format `dd/MM/yyyy HHmm`. E.g. `reminder John's Birthday` -r 15/11/2019 1358) + Expected: No reminder is added. Error details shown in result display. (Since z is 1 minute before now.) + +=== Snoozing a reminder +. Snoozing a reminder +.. Prerequisites: Reminder has been added. Know the current date and time. Volume of computer is audible to tester. + +.. Test case: `snooze 1` + + Expected: First reminder of the reminder list will occur again five minutes from now. +.. Test case: `snooze 1 -s 1.min.later` + + Expected: First reminder of the reminder list will occur again one minute from now. +.. Test case: `snooze 1 -s x` (where x is the date and time 1 minute later from now in the format `dd/MM/yyyy HHmm`. E.g. `reminder John's Birthday` -r 15/11/2019 1800) + Expected: Similar to previous test case. (Since x is 1 minute from now.) + +.. Test case: `snooze John's Birthday -s y` (where y is the date and time 1 minute later from now in the format `yyyy-MM-ddTHHmm`. E.g. `reminder John's Birthday` -s 2019-11-15T18:00) + Expected: Similar to previous test case. (Since y is 1 minute from now.) + +.. Test case: `undo` (used after one of the above has taken place) +Expected: ELISA will reverse the snoozing of the reminder and reminder's occurrence date time will revert to it its state before the snooze. + +.. Test case: `redo` (used after a valid undo command has taken place) +Expected: ELISA will reverse the undo of the snoozing of the reminder and a reminder will occur at the appropriate time. Note that if the time for the reminder to occur has already passed, the reminder will not occur. + +.. Test case: `snooze John's Birthday -s z` (where z is the date and time 1 minute before now in the format `dd/MM/yyyy HHmm`. E.g. `reminder John's Birthday` -s 15/11/2019 1358) + Expected: No reminder is snoozed. Error details shown in result display. (Since z is 1 minute before now.) + +=== Editing a task + +. Editing a task while all tasks are listed + +.. Prerequisite: Switch to the task list panel using the `show T` command. There should be at least one task in the list +.. Test case: `edit 1 -p high` + +Expected: The priority of the first task is changed to high (if the original priority of the task is already high, try other priority). +.. Test case: `edit 1 -desc testcase` + +Expected: The description of the first task should change to "testcase". +.. Test case: `edit 1 -d 1.min.later` + +Expected: A new event should be created for the task. You can verify by switching to the event panel or opening the task using the `open 1` command. +.. Test case: `edit 1 -p high -d 2.min.later` + +Expected: A new event should be created for the task (if it does not already exist) or the time of the event will be updated. The priority of the task will also change. +.. Test case: Try different variation of the edit command to ensure that it works. + +. Editing a task to another task that is in the list +.. Prerequisite: Switch to the task list panel using the `show T` command. There should be at least two task in the list. +.. Test case: Attempt to edit the first task such that it is the same as the second task. For example, if the second task has a description "testcase" and a priority of high, run `edit 1 -desc testcase -p high`. + +Expected: ELISA will feedback that the task already exists and will prevent you from editing. + +. Editing a task to remove the task +.. Prerequisite: Switch to the task list panel using the `show T` command. There should be at least one task in the list. +.. Test case: `edit 1 --tk` when the task has an event and/or reminder. + +Expected: ELISA will remove the task from the task list but the event and/or reminder will still be viewable in the event/reminder panel. +.. Test case: `edit 1 --tk` when the task has no event and no reminder. +Expected: ELISA will prompt that you cannot edit an item such that it has no task, event or reminder and the task will not be removed from the task list. -_{ more test cases ... }_ +[NOTE] +Even though this is for task, the same tests can be used for testing events and reminders. + +=== Undoing/Redoing previous commands + +. Undoing when no `UndoableCommand`(s) have been executed + +.. Prerequisites: Open the application and do not execute any add, edit, delete, priority or show commands +.. Test case: `undo` + + Expected: No changes to be undone. Error message shown in chat box. +.. Test case: `redo` + + Expected: No undo commands to be redone. Error message shown in chat box. + +. Undoing when `UndoableCommand`(s) _have_ been executed + +.. Prerequisites: Open the application and execute some commands like adding/removing items, changing done/not done of tasks, modifying dates/descriptions +.. Test case: `undo` + + Expected: Last executed change should be reverted. +.. Test case: `redo` + + Expected: Last undone change should be re-applied +.. Other test cases: You can `undo` and `redo` multiple times in either direction to remove and apply changes. + +. Redoing when previous `UndoCommand`(s) exist but are not the last executed + +.. Prerequisites: Execute at least one `UndoableCommand`, followed by one or more `UndoCommand`(s), and then another `UndoableCommand` +.. Test case: `redo` + + Expected: Should not be able to redo as the undo/redo command history has changed. Error message shown in chat box. +=== Game mode testing + +.Entering game mode (easy) +image::SnakeGame.PNG[500,600] + +.. Test case: `game` + +Expected: Enters easy mode of game (Refer to figure above) +.. Test case: `game e` + +Expected: Same as above. +.. Test case: `game EASY` +Expected: Same as above. + +.Entering game mode (hard) +image::snakegamehard.PNG[500,600] + +.. Test case: `game H` + +Expected: Enters hard mode of game (Refer to figure above) +.. Test case: `game hard` + +Expected: Same as above. + +=== Change theme testing + +. Changing theme of ELISA +.. Prerequisites: You must be in a different theme from the theme you are switching to before you can see the change. +.. Test case: `theme white` +Expected: Change ELISA's theme to white. + +=== Priority mode testing +. Toggling on priority mode +.. Prerequisites: Switch to the task list panel using the `show T` command. There should be at least 1 incomplete task in the list and priority mode should be disabled at the moment. +.. Test case: `priority` + +Expected: The ELISA icon will turn red and only one task will be shown on the task list panel. You can try switching a panel (`show E`) or adding an event to make sure it works. +.. Test case: `priority -f` + +Expected: Same as above, except this time switching of panel or adding an event is not allowed. +.. Test case: `priority 1.min.later -f` + +Expected: Same as above, except this time priority mode will deactivate after 1 minute. +.. Test case: `priority -d` +Expected: Flag not recognized and ELISA will reject the command. + +. Toggling off priority mode +.. Prerequisites: Switch to the task list panel using the `show T` command. Priority mode is already activated. +.. Test case: `priority` + +Expected: Deactivate priority mode and show you all your tasks. +.. Test case: `priority 1.min.later`, `priority -f` + +Expected: Same as above. + +. Manipulate list while in priority mode +.. Prerequisites: Switch to the task list panel using the `show T` command. Priority mode is already activated. +.. Test case: `done 1` + +Expected: ELISA will mark the task as done and generate the next task. If there is no next task, ELISA will automatically take you out of priority mode. +.. Test case: `delete 1` + +Expected: ELISA will delete the task and generate the next task. If there is no next task, ELISA will automatically take you out of priority mode. +.. Test case: `edit 1 -p low` (most effective for a task list with more than 1 task and the current task priority is high/medium) + +Expected: ELISA will edit the priority of the current task to low (any other priority can be used). A new task with a higher priority will be generated. +.. Test case: `undo` (used after one of the above has taken place) + +Expected: ELISA will reverse the previous process and the old task will be generated again. +.. Test case: `redo` (used after an undo command) + +Expected: ELISA will reverse the undo command and a new task is generated. +.. Other test cases: You can try doing all of these commands in focus mode `priority -f` to ensure that they work. When in focus mode, you can try some of the banned command like `show`, `game` etc to make sure that it does not work. === Saving data +. Checking that ELISA saves the data properly + +.. Copy `ELISA.jar` to a new directory and open it. This is to ensure that there is no old data file. An empty ELISA will be displayed. Type some sample data into ELISA and you will notice that a data file has been created at the path `data/itemstorage.json`. You can open the file to see that the data has been added. . Dealing with missing/corrupted data files -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +.. To simulate a missing data file, start up ELISA. ELISA will create a data file for you as `data/itemstorage.json` (this is the default name of the storage file). Close ELISA and delete the itemstorage.json file. Re-open ELISA and ELISA will start with a new and empty data file. Verify it at `data/itemstorage.json`. + +.. To simulated a corrupted data file, start up ELISA. Type some test input into ELISA. Close ELISA. Open the `data/itemstorage.json` file and edit some of the fields there. Open ELISA again. ELISA will realize that the data is corrupted and will generate a new data file for you. _{ more test cases ... }_ diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..12503e70e32 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += ELISA - Documentation :site-section: DeveloperGuide :toc: :toc-title: diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..b3a9becc62d 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += ELISA - Setting Up :site-section: DeveloperGuide :toc: :toc-title: diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..1c0c2548d17 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += ELISA - Testing :site-section: DeveloperGuide :toc: :toc-title: @@ -35,11 +35,11 @@ See <> for more info on how to run tests using G We have three types of tests: . _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `seedu.elisa.commons.core.IndexTest` . _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `seedu.elisa.storage.StorageManagerTest` . Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `seedu.elisa.logic.LogicManagerTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..9831223ba8b 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,3 @@ -= AddressBook Level 3 - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,134 +11,623 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/ay1920s1-cs2103t-t10-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` += ELISA - User Guide + +image::ug_title.png[] + +By: `AY1920S1-CS2103T-T10-3` +Since: `Sep 2019` +Licence: `NUS` == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 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, AB3 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +image::ug_introduction.png[] + +Hello, I am an Extremely Loud and Intelligent Student Assistant (ELISA)! + +I am here to be *your intelligent companion that keeps track of your tasks and remind you to take breaks*. + +Although I am *optimized for those who prefer to work with a Command Line Interface* (CLI), I also have a Graphical User Interface (GUI). If you can type fast, I can get your tasks done faster than traditional GUI apps. + +Enjoy! == Quick Start -. Ensure you have Java `11` or above 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. -. Double-click the file to start the app. The GUI should appear in a few seconds. +image::ug_quickstart.png[] + +. Have you installed Java `11` ? +. Find me, `ELISA.jar` link:{repoURL}/releases[here]. +. Double-click me to start the app. See you in a few seconds. + -image::Ui.png[width="790"] +.Main window of ELISA +image::MainWindow.png[width="790"] + -. Type the command in the command box and press kbd:[Enter] to execute it. + -e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +. Tell me what to do in the command box! +. Want to know more about me? A detailed explanation of me is at <> -* *`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 -* *`exit`* : exits the app +== Legend + +image::ug_legend.png[] +Here are the icons that will be used in the app: + +image:Completed.PNG[Done, title="Done"] shows that you've completed this task. + +image:Uncompleted.PNG[Not done, title="Not done"] shows that you have yet to complete this task. + + +.Difference between Task, Event, Reminder +image::Legend_difference.png[] + +.Legend description +|=== +|What it creates: |Example command: +|1) Task only | task shower +|2) Event only | event christmas -d 25/12/2019 0000 +|3) Reminder only | reminder shower -r 10.min.later +|4) Task and Reminder | task shower -r 10.min.later +|5) Event and Reminder | event christmas -d 25/12/2019 0000 -r 24/12/2019 0000 +|6) Task and Event | task CS2103T Quiz -d 11/11/2019 2359 +|7) Task, Event, Reminder | task CS2103T Quiz -d 11/11/2019 2359 -r 10/11/2019 1200 +|=== -. Refer to <> for details of each command. [[Features]] == Features +Now we'll be presenting what you've been waiting for... our features! + +But hold on, to fully appreciate our features, you might want to go through some of the command guidelines first. + +These are some mistakes that our users(and even us) would make initially, and they can be really unpleasant, so we hope that you won't have to go though them. +For your viewing pleasure, we've collated it nicely into a quick starter pack below, let's start! + +image::ug_features.png[] + +This table shows the basic rules for the formatting the input: + +.Command Guidelines +|=== +|Guidelines: |Description: |Correct usage: +|Words in UPPER_CASE are the parameters to be supplied by the user | If given `task DESCRIPTION` description is to be provided. |`task shower` +|All flags *MUST* have a _space_ after them | `task shower -d10.min.later` is *incorrect*. It should be `task shower -d 10.min.later` | `task shower -d 10.min.later` +|Flags in square brackets are optional | If given `task DESCRIPTION [-t TAG]` tag is optional a| `task shower` + + `task shower -t clean` -==== -*Command Format* +|Flags can be in any order, given that they are accepted by the command | If given `task DESCRIPTION [-d DATETIME] [-r REMINDER]` -r can come before -d a| `task shower -d 1.hour.later -r 30.min.later` + + `task shower -r 30.min.later -d 1.hour.later` +|Flags must not be repeated in the same input, unless it is a tag flag | `task shower -p high -p low` is *incorrect* as -p appears twice. However, `task bathe -t fresh -t hygiene` is accepted| `task shower -p high` +|For the parser of mm.min.later, hh.hour.later, dd.day.later, the maximum allowed is 100 | `101.min.later` is not accepted, however `100.min.later` or `99.day.later` are both accepted. | `task study -d 99.day.later` +|=== -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{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. -==== -=== Viewing help : `help` +This is a quick summary of all our flags. + -Format: `help` +For *first time* users: + -=== Adding a person: `add` +* You can choose to skip through this first as it may seem overwhelming now. +* But as you go along the different sections and see new flags, it might be useful to refer to this as it can help you understand it's accepted parameters. -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +For *more experienced* users: + + +* This can be your best buddy! We understand that sometimes it can be troublesome to look through the entire guide just to find the flag you want. + +.Flags Overview +[cols="1,2,3,2,2"] +|=== +|Flag: |Parameter: |Parameter Format: |Possible Parameters: | Example Usage: +|-d | DATETIME | yyyy-MM-ddTHHmm or dd/MM/yyyy HHmm or mm.min.later or HH.hour.later or dd.day.later |2019-09-25T19:34 or 25/09/2019 1934 or 10.min.later| -d 25/09/2019 1934 +|-r | REMINDER |yyyy-MM-ddTHHmm or dd/MM/yyyy HHmm or mm.min.later or HH.hour.later or dd.day.later |2019-09-25T19:34 or 25/09/2019 1934 or 10.min.later| -r 25/09/2019 1934 +|-p | PRIORITY |high, medium, low| HIGH, medium, LOW | -p high +|-t | TAG |alphanumeric only | happy123 | -t happy123 +|-desc | DESCRIPTION | alphanumeric and all symbols *except* "-" | drink 5 litres of water | -desc drink water! +|-auto| PERIOD | day or month or week or mm.min.later or HH.hour.later or dd.day.later | day or week or month or 10.min.later | -auto 10.min.later +|--tk | _none_ | _none_ | _none_ | --tk +|--e | _none_ | _none_ | _none_ | --e +|--r | _none_ | _none_ | _none_ | --r +|=== + +There are three other flags that do not take in any parameters and are only used for the edit command. They are listed below: + +.Additional flags +[cols="1,9"] +|=== +|Flag:| Meaning: +|--tk | Used to delete a task attached to an item +|--e | Used to delete an event attached to an item +|--r | Used to delete a reminder attached to an item +|=== + +[NOTE] +Not all tags work for every command. Please check the specific command for more details. + +[WARNING] +All time dependent elements such as deadline, reminder and calendar time are dependent on the *system time* + +If you face any issues, please check the <>. If it is not mentioned there, then feel free to bring it up to us! + + +Now we are ready to jump into the features itself! + +=== CLI Features + +==== Adding a task: `task` + +Adds a task to the task list + +Format: `task DESCRIPTION [-d DATETIME] [-r REMINDER] [-p PRIORITY] [-t TAG]` [TIP] -A person can have any number of tags (including 0) +To create a task quickly, just include the description as Task can have no flags (ie `task shower`). + +Examples: + +* `task eat my vitamins` +* `task eat my vitamins -r 5.hour.later` +* `task eat my vitamins -d 10.hour.later -p low -t healthy` + +==== Adding an event: `event` + +Adds an event to events list and calendar + +Format: `event DESCRIPTION -d DATETIME [-r REMINDER] [-p PRIORITY] [-t TAG]` 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` +* `event John’s Birthday -d 20/09/2019 1800` +* `event John’s Birthday -d 20/09/2019 1800 -r 19/09/2019 1800` +* `event John’s Birthday -d 3.day.later -r 2.day.later -p high -t friend` -=== Listing all persons : `list` +[NOTE] +It is currently not possible to set a reminder for events with an autoreschedule flag. We intend to include this in v2.0 . -Shows a list of all persons in the address book. + -Format: `list` +==== Editing an item : `edit` -=== Editing a person : `edit` +This command is used to edit any of the items that you might have. + -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +===== *Limitations* + + +* You are only able to edit the item currently shown in your view. For example, when you're on the task list, you can only edit the tasks shown. You will not be able to edit events or reminders. +* The new item with the edited fields must not already exist. Don't worry if you can't remember that, ELISA will remind you! + +Format: `edit INDEX [-desc DESCRIPTION] [-d DATETIME] [-r REMINDER] [-p PRIORITY] [--tk] [--e] [--r] [-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 task at the specified `INDEX`. The index refers to the index number shown in currently viewed 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. +**** + +Now, you might wonder, what does `--tk`, `--e`, `--r` do? + +Haven't you thought of how you would remove just a reminder of a task, or remove just the deadline of a task, without deleting the entire item? + +Well this is just what you are looking for! + + +For an item with a task, event and reminder, `--tk` removes the task portion while `--e` removes the event portion while `--r` removes the reminder portion. + +For example, here is an item that is a task, with an event and a reminder: + +image::edit-1.png[] + +After you enter `edit 1 --e --r`, this is how the item would look: + +image::edit-2.png[] + +Convenient isn't it? As you can see, the event and reminder details are removed, which is precisely what we want. + +But wait! The resultant item should not be totally empty! It just doesn't make sense to have an empty item just hanging there in your list. There should have at least a task, event or reminder. + +Well, if you can't remember what fields can or cannot be removed, fret not. ELISA will be sure to tell you that: + +image::edit-3.png[] + +Also, if you decide to edit something and remove the part in the same command, ELISA would just remove it as she believes that your command to remove it is more important. + +For example, you decide to edit the reminder time and also remove the reminder with the command `edit 1 -r 8.hour.later --r`: + +image::edit-4.png[] + +As you can see, the reminder part is removed. + +Here are some example commands for edit: + +* `edit 1 -desc read books -d 3.day.later -p low` + +Edits item 1 of the current list. Changes the description to `read books`, deadline to `3.day.later` and priority to `low`. +* `edit 3 -desc CS2103 team meeting -r 3.hour.later -p high` + +Edits item 2 of the current list. Changes the description to `CS2103 team meeting`, reminder to `3.hour.later` and priority to `high`. +//end::edit[] + +//tag::delete[] +==== Deleting a reminder/task/event : `delete` + +Deletes the reminder/task/event from ELISA. + +Format: `delete INDEX` + +**** +* Deletes the item at the specified `INDEX` of the current list shown. +* The index refers to the index number shown in the list. +* The index *must be a positive integer* 1, 2, 3, ... +* No flags should be given with this command. **** 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. +* `show r` + +`delete 2` + +Deletes the 2nd reminder in the shown reminder list. -=== Locating persons by name: `find` +==== Completing a task: `done` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Mark a task at the specific index as done. + +Format: `done INDEX` + +Examples: + +* `done 1` - Marks the task at index 1 as done. **** -* 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` +* This command can only be used in the task list panel. + +* The index refers to the index number shown in the list. + +* The index *must be a positive integer* (1, 2, 3 ...) + +* No flags should be given with this command. + **** +==== Continuing with a task: `continue` + +Mark a task at the specific index as undone. + +Format: `continue INDEX` + Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `continue 1` - Marks the task at index 1 as undone. -// tag::delete[] -=== Deleting a person : `delete` +* `cont 1` - Short version of the above command -Deletes the specified person from the address book. + -Format: `delete INDEX` +**** +* This command can only be used in the task list panel. + +* The index refers to the index number shown in the list. + +* The index *must be a positive integer* (1, 2, 3 ...) + +* No flags should be given with this command. **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... + +//tag::autoreschedule[] +=== AutoReschedule Feature + +Do you have to go to the supermarket weekly? Complete a quiz weekly? Jog daily? Well this would definitely make your life much easier! + +Your events can now update its own time when it is already over! All you need to do is to add a `-auto` flag, along with a time period. + +Here is how you can do it. You might have weekly quizzes due on the same day every week, the command you can enter is `event CS2103T Quiz -d 23/11/2019 2359 -auto week`. + +This creates an event whose date will change every week. +Convenient isn't it! Now you no longer have to manually change their dates after it is over. + +That's not it! If you add an AutoReschedule event with a date that is already over, ELISA would automatically update its date to show the upcoming one. + +This is what I mean. If you add an event that is already over, eg. `event quiz 11/08/2019 2359 -auto week`: + +image::edit-5.png[100, 500] +ELISA immediately changes it to show the upcoming date for this week. ELISA shows 10 NOV as it is 9 NOV at this point. + +Also, are you worried that the events wouldn't be updated if you leave the app? No worries as ELISA's got you covered! + +When you come back again, you would see that all the events that have AutoReschedule would show the upcoming date. + +[NOTE] +But there is a small drawback... AutoReschedule currently does not work with reminders. That means that if you add an `-auto` flag for an event, you would not be able to add a `-r` flag for reminders. +This also means that reminders cannot be created with the `-auto` flag. But you can look forward to this in V2.0 of ELISA! + +For the rescheduling period, you can use `hour`, `day`, `week` or the `10.min.later` format. + +Here are some interesting commands you can try: + +* `event grocery shopping -d 9/9/2019 1200 -auto week` +* `event jog -d 23/11/2019 0800 -auto day` +* `event smile -d 1.min.later -auto 1.min.later` (it's good to be happy) +//end::autoreschedule[] + + +// tag::reminder[] +=== Reminder Feature + +So focused on your work that you forget the other tasks in your life? +Don't worry! You can ask ELISA to remind you to do what you need to do, when you need to do it! + +==== Adding a reminder: `reminder` + +Simply add your reminder with this command: +`reminder John’s Birthday -r 11/11/2019 1440` + + and ELISA will carry it out! + +image::BeforeReminderCommand.PNG[] + +Once it's time for you to be reminded, ELISA will remind you! +Best of all, ELISA will play a chime so you won't need to have ELISA open in order to be reminded! + +image::AfterReminderCommand.PNG[] + +More specifically, the reminder command format is: `reminder DESCRIPTION -r REMINDER [-t TAG]` + +Here are some other ways you could use this command!: + +* `reminder Finish UG -r 11/10/2019 1400` +* `reminder Get John’s Birthday gift -r 2019-09-19T14:00 -t friend` +* `reminder Study for 2103T Exam -r 3.day.later` +[NOTE] +1. It is currently not possible to set a reminder for events with an autoreschedule flag. We intend to include this in v2.0 . + +2. It is not possible to set a reminder in the past. + +image::FailReminderCommand.PNG[] +// end::reminder[] + +// tag::snooze[] +==== Snoozing a reminder: `snooze` + +If you're in the groove of working but still want to be reminded again of a reminder that just occurred, you can use the snooze command! + +`snooze` will snooze the most recent reminder for a duration of time (5 minutes by default) + +image::BeforeSnoozeCommand.PNG[] + +And ELISA will remind you again once it's time! + +image::AfterSnoozeCommand.PNG[] + +More specifically the format of this command is: 'snooze [INDEX] [-s SNOOZE_DURATION]' + + +1. This snoozes a reminder. + +2. The index refers to the it's index in the reminder list. + +3. If an index is not provided, the most recently occurred reminder will be snoozed. + +4. If a snooze duration is not specified, the reminder will be snoozed at the default duration of 5 min. + +Here are some examples you can try out! +* `snooze 1` +* `snooze 3 -s 10.min.later` +* `snooze -s 10/10/2020 1400` + +[NOTE] +1. It is possible to snooze the same reminder multiple times if you wish. + +2. It is not possible to snooze (without specifying index) if no reminder has occurred yet. + +However, if you use the snooze command incorrectly, ELISA will first attempt to correct your usage of the snooze command. +// end::snooze[] + +=== Navigation Features + +==== Show the specified category : `show` + +Shows the specified category by switching the view to the given list. + +Format: `show [t] [e] [c] [r] (Exactly *one* flag must be used with this command)'' + +[NOTE] +The flag can be in either lowercase or uppercase. Eg. `show E`. + +==== Scrolling the list : `up` / `down` + +Scrolls the list up/down + +=== Visual Features + +==== Expanding an item in the view: `open` & `close` +Want to see all the details of an item at one glance? Lazy to go to event list or reminder list to find them? We got you! + +You can now open up an item to see all its details, regardless of what list you are on. +But remember to close it before opening another item! + +Open format: `open INDEX` + +Close format: `close` + +Example sequence: + +* `show T` - to switch to the task view +* `open 2` - opens the second item on the list +* `close` - closes the item +* `open 3` - opens the third item on the list (Note: If you forgot to close previously, no worries as ELISA will prompt you to!) +* `close` - closes the item + +[WARNING] +Although, the ESC key can close too, we do not advise doing so as ELISA may be confused later on :( + + +==== Changing the theme : `theme` + +Screen is too dark? Switch up the theme by typing `theme white`! + +.White theme of ELISA +image::themewhite.PNG[] + +Examples: + +* `theme white` +* `theme black` + +==== Clearing the screen : `clearscreen` and `clear` + +Chat box getting too cluttered? Type `clearscreen` to clear it! + +* `clear` - clears all lists +* `clearscreen` - clears the chat box + +=== Calendar Feature + +Sick of looking through lists of events? Type `show c` in the command box and your events will be magically placed into a calendar! + +.Calendar feature of ELISA +image::calendar.PNG[] + +=== Entertainment Features + +Too stressed from all the work? Check out these features to take a short break so that you can go further! + +==== Play a game: `game` + +`game` switches your list of tasks into a game of Snakes! + +There are 2 modes to the game: `easy` and `hard`. Type `game hard` to enter the hard mode of the game! + +Controls: `UP`, `DOWN`, `LEFT`, `RIGHT` + +.Snake game `EASY` + +image::SnakeGame.PNG[Snake, 600, 600] + + +`EASY` + +New to the game? Try out the `easy` mode, where all you have to do is collect the food, which is indicated by the pink box. Biting your own tail will result in GAME OVER! Be careful! + +.Snake game `HARD` + +image::snakegamehard.PNG[SnakeHard, 600, 600] + + +`HARD` + +Find the game too easy? Enter the `hard` mode and there will be 20 random walls (indicated by blue box) placed all around the game. Colliding with any of these walls will result in a GAME OVER! You will need to navigate through these walls to collect your food! + + +`EXIT` + +Realised that you have played for too long and want to get back to work? Hitting the `ESC` key will exit the game and return you to your list of tasks! + +// tag::joke[] +=== Asking ELISA for a joke : `joke` +Need to amuse yourself for a bit? Simply use the `joke` command and ELISA will select a joke from her database for you to enjoy! + +**** +* Note that each joke is randomly selected from the database and may repeat. +**** + +Example: + +* `joke` + + +// end::joke[] + +=== Undo/Redo Feature + +// tag::undo[] +==== Undoing the latest modification(s) : `undo` + +Reverts the latest commands given on the ELISA. + +Format: `undo` + +**** +* `undo` can only be done if commands have been executed **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `undo` + +Undoes the last command + +// end::undo[] -// end::delete[] -=== Clearing all entries : `clear` +// tag::redo[] +==== Redoing the latest undone command(s) : `redo` -Clears all entries from the address book. + -Format: `clear` +Re-executes the latest undone commands given on the ELISA. + +Format: `redo` + +**** +* `redo` can only be done if undo has already been executed +* After `undo`, if a new command is executed then `redo` cannot be executed +**** + +Examples: + +* `redo` + + +//end::redo[] + +=== Sort/Find/Priority Feature + +//tag::sort[] +==== Sorting ELISA out: `sort` + +Is ELISA getting too cluttered and disorganized? Do you want to sort your task by their priority? Or the events by their start date? + +ELISA comes in-built with a sort feature that allows you to sort your items within the different panel. And the best part of it? YOU can decide how you want to sort it. + +The simple sort command on the different panel sorts the list differently (just another sign of how smart ELISA is) and they are as follows: + +* task panel - tasks are sorted from those that are incomplete to those that are completed. Within the two groups, they are sorted based on their priority, from high to medium and to low. + +* event panel - events are sorted based on their start date and time. + +* reminder - reminders are sorted based on their firing off date and time. + +But why stop there? You are also able to sort by priority and description within all the panels and even combine different sorting together! You are only limited by your imagination (and the items within your lists). + +Format: `sort` or `sort ` + +Examples: + +* `sort` - a simple sort that follows the criteria mentioned above for the different panel. +* `sort pri` - sorts the item within the panel by their priority (from high to low). +* `sort desc` - sorts the item within the panel by their description (lexicographic order) +//end::sort[] + +//tag::find[] +==== Finding a task: `find` + +Is your task list getting too long and you are not able to find what you are looking for? Introducing the `find` function which will allow you to find what you want within the specific panel. Just type `find` and the keywords that you want to search for within the command box and ELISA will find the relevant items for you. + +.ELISA before find command +image::BeforeFind.PNG[] + +.ELISA after find command +image::AfterFind.PNG[] + + +Format: `find ` + +Examples: + +* `find CS2101` - find all items that have CS2101 in their description within that panel + +* `find CS2101 CS2103` - find items that contains either CS2101 or CS2103 within their description. +//end::find[] + +//tag::priority[] +==== Entering priority mode: `priority` +Overwhelmed by the number of tasks to complete? Priority mode will help you narrow your focus down to the most pressing task to complete. + +.ELISA before priority mode with a long list of tasks +image::BeforePriority.PNG[500,500] + + +By simply typing `priority` into the command box, you will be given one single task of the highest priority among your task list. This task is chosen by ELISA base on priority and the order in which the task was added to the list. + + +.ELISA in priority mode +image::AfterPriority.PNG[500,500] + +Notice that the ELISA icon turns red to signify that you are in priority mode and your task list has shrunk from 5 tasks down to the 1 most important task. + +When you are done with your current task, just tell ELISA you are done by simply typing `done 1` and ELISA will generate the next task for you. + +.ELISA after completing the first task +image::AfterDone.PNG[500,500] + +To go back to the normal task view, simply type `priority` again. Or even better, complete all your tasks and you will be automatically brought out of priority mode. + + +.ELISA after all tasks are completed +image::AllDone.PNG[500,500] + +Feeling lazy? You can also opt to turn off priority mode at a specific time by typing `priority dd/mm/yyyy hhmm` and ELISA will turn it off at that specific time for you. + +For the easily distracted, there is an extreme focus mode available. Simply tell ELISA that you want to enter the extreme focus mode by attaching a `-f` flag to the back of the command. In the extreme focus mode, commands such as `show`, `sort`, `find`, `game`, `event` and `reminder` are banned. + +Format: `priority [DATETIME] [-f]` + +Examples: + +* `priority` - activates or deactivates the priority mode +* `priority 30/10/2019 1200` - activates the priority mode and ask ELISA to turn it off on 30/10/2019 at noon +* `priority 2.hour.later -f` - activates the focus mode and ask ELISA to turn it off 2 hours later + +**** +* Note that this command can only be called in the task panel and when you have incomplete tasks to be completed. +* Note that if priority mode is currently on, any variation of `priority` will turn it off. +* Note that all command such as `edit`, `undo` and `redo` still works in priority mode. However, if a `done` or `delete` command takes you out of priority mode and then you `undo` it, it will only `undo` the command but will not take you back into priority mode. +**** +//end::priority[] === Exiting the program : `exit` @@ -147,31 +635,19 @@ 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. + +ELISA saves the data 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]` - -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +== Additional Notes +=== Input +* Currently, an empty input can be entered, but ELISA will show an invalid command. -== FAQ +=== Calendar -*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. +* Currently, it only shows *1 event* on each date due to limited space on the calendar. If there are more events on that date, to find them all, you can go to the event list and sort through it. +* Currently, it only shows the *current* month. We are still working to bring the next month to you. +* Please keep a full screen. If you resize, we are unable to guarantee the view of the calendar. -== Command Summary +== Coming in V2.0 -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` +* Events with AutoReschedule can have reminders that will be rescheduled along with it. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000000..f980e760b6f --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index d1e2ae93675..f09e0c5270d 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -13,13 +13,13 @@ activate ui UI_COLOR ui -[UI_COLOR]> logic : execute("delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteItem(index) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveItemStorage(itemStorage, filepath) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 7f8fe407f89..025eba8eac7 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -10,6 +10,7 @@ if () then ([command commits AddressBook]) :Save AddressBook to addressBookStateList; else ([else]) + :wqeqw; endif stop @enduml diff --git a/docs/diagrams/ItemClassDiagram.puml b/docs/diagrams/ItemClassDiagram.puml new file mode 100644 index 00000000000..d4ae7b5dd4e --- /dev/null +++ b/docs/diagrams/ItemClassDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor ITEM_COLOR_T4 +skinparam classBackgroundColor ITEM_COLOR + +package Item { +class ItemBuilder +class Item { + - Task : task + - Event : event + - Reminder : reminder + - Priority : priority + - Hashset : tags + - ItemDescription : itemDescription + +} +class Task +class Event +class Reminder +class Task +class Tag +ItemBuilder .right.> Item: creates > +Item "0..1 " .up.-o Task +Item " 0..1" .up.--o Event +Item "\n\n0..1" .up.-o Reminder +enum Priority { + HIGH + MEDIUM + LOW +} +Item " 1" .left.--* ItemDescription +Item "1" .left.--* Priority +Item " \n\n*" .up.---o Tag +@enduml diff --git a/docs/diagrams/LogicComponentUML.png b/docs/diagrams/LogicComponentUML.png new file mode 100644 index 00000000000..8c5cd249296 Binary files /dev/null and b/docs/diagrams/LogicComponentUML.png differ diff --git a/docs/diagrams/PriorityMode.puml b/docs/diagrams/PriorityMode.puml new file mode 100644 index 00000000000..b7fb2109b30 --- /dev/null +++ b/docs/diagrams/PriorityMode.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "p:ScheduledPriorityCommand" as ScheduledPriorityCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "timer:Timer" as Timer MODEL_COLOR +participant ":TimerTask" as Task MODEL_COLOR +end box +[-> LogicManager : execute(priority 30.min.later) +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(priority 30.min.later) +activate AddressBookParser + +create ScheduledPriorityCommand +AddressBookParser -> ScheduledPriorityCommand +activate ScheduledPriorityCommand + +ScheduledPriorityCommand --> AddressBookParser +deactivate ScheduledPriorityCommand + +AddressBookParser --> LogicManager : p +deactivate AddressBookParser + +LogicManager -> ScheduledPriorityCommand : execute() +activate ScheduledPriorityCommand + +ScheduledPriorityCommand -> Model : scheduleOffPriorityMode(LocalDateTime) +activate Model + +create Timer +Model -> Timer : new Timer() +activate Timer +Timer --> Model +deactivate Timer + +create Task +Model -> Task : new TimerTask() +activate Task + +Task --> Model +deactivate Task + +Model -> Timer : schedule(TimerTask, LocalDateTime) +activate Timer +deactivate Model + +ScheduledPriorityCommand -> Model : togglePriorityMode() +activate Model + +Model -> Model: toggleOnPriorityMode() +activate Model + +deactivate Model +Model -> ScheduledPriorityCommand: CommandResult +deactivate Model + +ScheduledPriorityCommand --> LogicManager: CommandResult +destroy ScheduledPriorityCommand + +[<--LogicManager : CommandResult +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/PriorityMode2.puml b/docs/diagrams/PriorityMode2.puml new file mode 100644 index 00000000000..2aae6a83663 --- /dev/null +++ b/docs/diagrams/PriorityMode2.puml @@ -0,0 +1,21 @@ +@startuml +!include style.puml + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant "timer:Timer" as Timer MODEL_COLOR +participant ":TimerTask" as Task MODEL_COLOR +end box + +activate Timer +Timer -> Task : run() +activate Task + +Task -> Model : toggleOffPriorityMode() +activate Model +destroy Task +Model -> Timer : cancel() +destroy Timer + +deactivate Model +@enduml diff --git a/docs/diagrams/RedoActivity.puml b/docs/diagrams/RedoActivity.puml new file mode 100644 index 00000000000..11b1cfc095c --- /dev/null +++ b/docs/diagrams/RedoActivity.puml @@ -0,0 +1,17 @@ +@startuml +start +:User enters redo command; + +if () then ([at least 1 undo command has +been executed prior]) + :Pop the last + undone command; + :Execute that command to + reapply the last undone change; +else ([else]) + :Show error that no + changes to be redone; +endif + :Return CommandResult to UI; +stop +@enduml diff --git a/docs/diagrams/SnoozeActivityDiagram.puml b/docs/diagrams/SnoozeActivityDiagram.puml new file mode 100644 index 00000000000..24117b598e5 --- /dev/null +++ b/docs/diagrams/SnoozeActivityDiagram.puml @@ -0,0 +1,35 @@ +@startuml +start +:User snoozes reminder; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if (hasIndex) then ([Reminder is retrieved]) + :Retrieve reminder + with specified index; + +else () + if (Reminder has occurred recently) then ([yes]) + :Retrieve most recent reminder; + else ([no]) + :Throw exception; + end + endif +endif + +if (durationIsSpecified) then ([yes]) + :**newReminderOccurrence** = now + durationSpecified; +else ([no]) + :**newReminderOccurrence** = now + 5min; +endif + +if (newReminderOccurrenceIsValid) then ([yes]) + :Snooze reminder till newReminderOccurrence; +else ([no]) + :Throw exception; + end +endif + +stop +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..13d00ac072d 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -9,11 +9,15 @@ Interface Ui <> Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow -Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard -Class StatusBarFooter +Class TaskListPanel +Class TaskListCard +Class EventListPanel +Class EventListCard +Class ReminderListPanel +Class ReminderListCard +Class ElisaDialogBox +Class UserDialogBox Class CommandBox } @@ -30,31 +34,37 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> MainWindow -MainWindow --> HelpWindow + MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel -MainWindow *-down-> StatusBarFooter +MainWindow *-down-> TaskListPanel +MainWindow *-down-> EventListPanel +MainWindow *-down-> ReminderListPanel + +ResultDisplay *-down-> ElisaDialogBox +ResultDisplay *-down-> UserDialogBox + +TaskListPanel -down-> TaskListCard +EventListPanel -down-> EventListCard +ReminderListPanel -down-> ReminderListCard -PersonListPanel -down-> PersonCard -MainWindow -left-|> UiPart +MainWindow --|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart -StatusBarFooter --|> UiPart -HelpWindow -down-|> UiPart +TaskListPanel --|> UiPart +TaskListCard --|> UiPart +EventListPanel --|> UiPart +EventListCard --|> UiPart +ReminderListPanel --|> UiPart +ReminderListCard --|> UiPart -PersonCard ..> Model -UiManager -right-> Logic -MainWindow -left-> Logic +TaskListCard ..> Model +EventListCard ..> Model +ReminderListCard ..> Model -PersonListPanel -[hidden]left- HelpWindow -HelpWindow -[hidden]left- CommandBox -CommandBox -[hidden]left- ResultDisplay -ResultDisplay -[hidden]left- StatusBarFooter +UiManager -right-> Logic +MainWindow -right-> Logic -MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UndoActivity.puml b/docs/diagrams/UndoActivity.puml new file mode 100644 index 00000000000..70b038fd055 --- /dev/null +++ b/docs/diagrams/UndoActivity.puml @@ -0,0 +1,17 @@ +@startuml +start +:User enters undo command; + +if () then ([undoable commands have +previously been executed]) + :Pop the last + executed command; + :Call the reverse + of that Command; +else ([else]) + :Show error that no + commands to be undone; +endif + :Return CommandResult to UI; +stop +@enduml diff --git a/docs/diagrams/UndoStateSequence.puml b/docs/diagrams/UndoStateSequence.puml new file mode 100644 index 00000000000..06243ab6ad9 --- /dev/null +++ b/docs/diagrams/UndoStateSequence.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "u:UndoCommand" as UndoCommand LOGIC_COLOR +participant ":UndoCommand" as UndoCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":ElisaStateHistory" as ElisaStateHistory MODEL_COLOR +participant ":ItemModel" as ItemModel MODEL_COLOR +end box + +[-> LogicManager : execute(undo) +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(undo) +activate AddressBookParser + +create UndoCommand +AddressBookParser -> UndoCommand +activate UndoCommand + +UndoCommand --> AddressBookParser +deactivate UndoCommand + +AddressBookParser --> LogicManager : u +deactivate AddressBookParser + +LogicManager -> UndoCommand : execute() +activate UndoCommand + +UndoCommand -> ElisaStateHistory : popCommand() +activate ElisaStateHistory + +ElisaStateHistory --> UndoCommand : +deactivate ElisaStateHistory + +UndoCommand --> LogicManager : result +deactivate UndoCommand +UndoCommand -[hidden]-> LogicManager : result +destroy UndoCommand + +LogicManager -> ItemModel : setToCurrState() +activate ItemModel +ItemModel -> ItemModel : setState(currentState) +ItemModel --> LogicManager +deactivate ItemModel + +LogicManager -> ItemModel : updateModelLists() +activate ItemModel +ItemModel -> ItemModel : emptyLists() +ItemModel --> LogicManager +deactivate ItemModel + +[<--LogicManager : result +deactivate LogicManager +@enduml diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml index fad8b0adeaa..659e89151c5 100644 --- a/docs/diagrams/style.puml +++ b/docs/diagrams/style.puml @@ -31,6 +31,13 @@ !define STORAGE_COLOR_T3 #806600 !define STORAGE_COLOR_T2 #544400 +!define ITEM_COLOR #3333C4 +!define ITEM_COLOR_T1 #C8C8FA +!define ITEM_COLOR_T2 #6A6ADC +!define ITEM_COLOR_T3 #1616B0 +!define ITEM_COLOR_T4 #101086 + + !define USER_COLOR #000000 skinparam BackgroundColor #FFFFFFF diff --git a/docs/images/AddCommandPart_1.png b/docs/images/AddCommandPart_1.png new file mode 100644 index 00000000000..770fb0e3e64 Binary files /dev/null and b/docs/images/AddCommandPart_1.png differ diff --git a/docs/images/AddCommandPart_2.png b/docs/images/AddCommandPart_2.png new file mode 100644 index 00000000000..4b1e8c8a3a3 Binary files /dev/null and b/docs/images/AddCommandPart_2.png differ diff --git a/docs/images/AddTaskActivity.png b/docs/images/AddTaskActivity.png new file mode 100644 index 00000000000..b06070a701b Binary files /dev/null and b/docs/images/AddTaskActivity.png differ diff --git a/docs/images/AddTaskEventSequenceDiagram.png b/docs/images/AddTaskEventSequenceDiagram.png new file mode 100644 index 00000000000..3268d8af0ed Binary files /dev/null and b/docs/images/AddTaskEventSequenceDiagram.png differ diff --git a/docs/images/AfterDone.PNG b/docs/images/AfterDone.PNG new file mode 100644 index 00000000000..e753dd72705 Binary files /dev/null and b/docs/images/AfterDone.PNG differ diff --git a/docs/images/AfterFind.PNG b/docs/images/AfterFind.PNG new file mode 100644 index 00000000000..3059c73aae5 Binary files /dev/null and b/docs/images/AfterFind.PNG differ diff --git a/docs/images/AfterPriority.PNG b/docs/images/AfterPriority.PNG new file mode 100644 index 00000000000..dbf9ad342d2 Binary files /dev/null and b/docs/images/AfterPriority.PNG differ diff --git a/docs/images/AfterReminderCommand.PNG b/docs/images/AfterReminderCommand.PNG new file mode 100644 index 00000000000..4b202349b2c Binary files /dev/null and b/docs/images/AfterReminderCommand.PNG differ diff --git a/docs/images/AfterSnoozeCommand.PNG b/docs/images/AfterSnoozeCommand.PNG new file mode 100644 index 00000000000..d3db529ab2a Binary files /dev/null and b/docs/images/AfterSnoozeCommand.PNG differ diff --git a/docs/images/AllDone.PNG b/docs/images/AllDone.PNG new file mode 100644 index 00000000000..ca64caa2ab5 Binary files /dev/null and b/docs/images/AllDone.PNG differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..fba500f433b 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/AutoRescheduleActivity.png b/docs/images/AutoRescheduleActivity.png new file mode 100644 index 00000000000..d94628461ce Binary files /dev/null and b/docs/images/AutoRescheduleActivity.png differ diff --git a/docs/images/AutoRescheduleClassDiagram.png b/docs/images/AutoRescheduleClassDiagram.png new file mode 100644 index 00000000000..191dd9a1fa9 Binary files /dev/null and b/docs/images/AutoRescheduleClassDiagram.png differ diff --git a/docs/images/AutoRescheduleSequence_1.png b/docs/images/AutoRescheduleSequence_1.png new file mode 100644 index 00000000000..ae9126d2173 Binary files /dev/null and b/docs/images/AutoRescheduleSequence_1.png differ diff --git a/docs/images/AutoRescheduleSequence_2.png b/docs/images/AutoRescheduleSequence_2.png new file mode 100644 index 00000000000..69a68cb34b9 Binary files /dev/null and b/docs/images/AutoRescheduleSequence_2.png differ diff --git a/docs/images/BeforeFind.PNG b/docs/images/BeforeFind.PNG new file mode 100644 index 00000000000..21d8f6e19cd Binary files /dev/null and b/docs/images/BeforeFind.PNG differ diff --git a/docs/images/BeforePriority.PNG b/docs/images/BeforePriority.PNG new file mode 100644 index 00000000000..157ce5d0fe1 Binary files /dev/null and b/docs/images/BeforePriority.PNG differ diff --git a/docs/images/BeforeReminderCommand.PNG b/docs/images/BeforeReminderCommand.PNG new file mode 100644 index 00000000000..62039758e87 Binary files /dev/null and b/docs/images/BeforeReminderCommand.PNG differ diff --git a/docs/images/BeforeSnoozeCommand.PNG b/docs/images/BeforeSnoozeCommand.PNG new file mode 100644 index 00000000000..3f885e9e2fc Binary files /dev/null and b/docs/images/BeforeSnoozeCommand.PNG differ diff --git a/docs/images/Completed.PNG b/docs/images/Completed.PNG new file mode 100644 index 00000000000..fa7efce5bbe Binary files /dev/null and b/docs/images/Completed.PNG differ diff --git a/docs/images/CreatingGameSequenceDiagram.png b/docs/images/CreatingGameSequenceDiagram.png new file mode 100644 index 00000000000..db1f65011cf Binary files /dev/null and b/docs/images/CreatingGameSequenceDiagram.png differ diff --git a/docs/images/EventIcon.PNG b/docs/images/EventIcon.PNG new file mode 100644 index 00000000000..8a852383dcd Binary files /dev/null and b/docs/images/EventIcon.PNG differ diff --git a/docs/images/FailReminderCommand.PNG b/docs/images/FailReminderCommand.PNG new file mode 100644 index 00000000000..45b488b05e3 Binary files /dev/null and b/docs/images/FailReminderCommand.PNG differ diff --git a/docs/images/GameActivityDiagram.png b/docs/images/GameActivityDiagram.png new file mode 100644 index 00000000000..fcc44424da2 Binary files /dev/null and b/docs/images/GameActivityDiagram.png differ diff --git a/docs/images/Icesiolz.png b/docs/images/Icesiolz.png new file mode 100644 index 00000000000..bd3004cc2eb Binary files /dev/null and b/docs/images/Icesiolz.png differ diff --git a/docs/images/ItemClassDiagram.png b/docs/images/ItemClassDiagram.png new file mode 100644 index 00000000000..40ae5fd4ded Binary files /dev/null and b/docs/images/ItemClassDiagram.png differ diff --git a/docs/images/Legend_difference.png b/docs/images/Legend_difference.png new file mode 100644 index 00000000000..249a16a84a8 Binary files /dev/null and b/docs/images/Legend_difference.png differ diff --git a/docs/images/LogicComponentUML.png b/docs/images/LogicComponentUML.png new file mode 100644 index 00000000000..cf80d86eef3 Binary files /dev/null and b/docs/images/LogicComponentUML.png differ diff --git a/docs/images/MainWindow.png b/docs/images/MainWindow.png new file mode 100644 index 00000000000..8c810c1ebe4 Binary files /dev/null and b/docs/images/MainWindow.png differ diff --git a/docs/images/ModelClassDiagram.bkup b/docs/images/ModelClassDiagram.bkup new file mode 100644 index 00000000000..280064118cf Binary files /dev/null and b/docs/images/ModelClassDiagram.bkup differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..5f360073286 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PriorityMode.png b/docs/images/PriorityMode.png new file mode 100644 index 00000000000..1fc906f8ef7 Binary files /dev/null and b/docs/images/PriorityMode.png differ diff --git a/docs/images/PriorityMode2.png b/docs/images/PriorityMode2.png new file mode 100644 index 00000000000..a2389d142fc Binary files /dev/null and b/docs/images/PriorityMode2.png differ diff --git a/docs/images/PriorityModeActivityDiagram.png b/docs/images/PriorityModeActivityDiagram.png new file mode 100644 index 00000000000..668972fca66 Binary files /dev/null and b/docs/images/PriorityModeActivityDiagram.png differ diff --git a/docs/images/RedoActivity.png b/docs/images/RedoActivity.png new file mode 100644 index 00000000000..8fbfe29b490 Binary files /dev/null and b/docs/images/RedoActivity.png differ diff --git a/docs/images/SnakeGame.PNG b/docs/images/SnakeGame.PNG new file mode 100644 index 00000000000..6c4c636f076 Binary files /dev/null and b/docs/images/SnakeGame.PNG differ diff --git a/docs/images/SnoozeActivityDiagram.png b/docs/images/SnoozeActivityDiagram.png new file mode 100644 index 00000000000..ac98304acc4 Binary files /dev/null and b/docs/images/SnoozeActivityDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..e5a3150de84 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 7b4b3dbea45..d7d017b7d89 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/Uncompleted.PNG b/docs/images/Uncompleted.PNG new file mode 100644 index 00000000000..827db4a8384 Binary files /dev/null and b/docs/images/Uncompleted.PNG differ diff --git a/docs/images/UndoActivity.png b/docs/images/UndoActivity.png new file mode 100644 index 00000000000..280be7d1e06 Binary files /dev/null and b/docs/images/UndoActivity.png differ diff --git a/docs/images/UndoStackStep1.png b/docs/images/UndoStackStep1.png new file mode 100644 index 00000000000..0dce58a6fa1 Binary files /dev/null and b/docs/images/UndoStackStep1.png differ diff --git a/docs/images/UndoStackStep2.png b/docs/images/UndoStackStep2.png new file mode 100644 index 00000000000..360d8ce52cb Binary files /dev/null and b/docs/images/UndoStackStep2.png differ diff --git a/docs/images/UndoState0.png b/docs/images/UndoState0.png new file mode 100644 index 00000000000..92eaba25598 Binary files /dev/null and b/docs/images/UndoState0.png differ diff --git a/docs/images/UndoState1.png b/docs/images/UndoState1.png new file mode 100644 index 00000000000..f8997c33c00 Binary files /dev/null and b/docs/images/UndoState1.png differ diff --git a/docs/images/UndoState2.png b/docs/images/UndoState2.png new file mode 100644 index 00000000000..6fddc46e74a Binary files /dev/null and b/docs/images/UndoState2.png differ diff --git a/docs/images/UndoState3.png b/docs/images/UndoState3.png new file mode 100644 index 00000000000..a4c6107dadf Binary files /dev/null and b/docs/images/UndoState3.png differ diff --git a/docs/images/UndoStateSequence.png b/docs/images/UndoStateSequence.png new file mode 100644 index 00000000000..e3487678c45 Binary files /dev/null and b/docs/images/UndoStateSequence.png differ diff --git a/docs/images/blimyj.png b/docs/images/blimyj.png new file mode 100644 index 00000000000..757f6904b56 Binary files /dev/null and b/docs/images/blimyj.png differ diff --git a/docs/images/blimyjUI.png b/docs/images/blimyjUI.png new file mode 100644 index 00000000000..5716b922f75 Binary files /dev/null and b/docs/images/blimyjUI.png differ diff --git a/docs/images/calendar.PNG b/docs/images/calendar.PNG new file mode 100644 index 00000000000..d6f2c77c736 Binary files /dev/null and b/docs/images/calendar.PNG differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 12754388389..00000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/edit-1.png b/docs/images/edit-1.png new file mode 100644 index 00000000000..5841677c122 Binary files /dev/null and b/docs/images/edit-1.png differ diff --git a/docs/images/edit-2.png b/docs/images/edit-2.png new file mode 100644 index 00000000000..e18cc20aa8a Binary files /dev/null and b/docs/images/edit-2.png differ diff --git a/docs/images/edit-3.png b/docs/images/edit-3.png new file mode 100644 index 00000000000..5e8148bc599 Binary files /dev/null and b/docs/images/edit-3.png differ diff --git a/docs/images/edit-4.png b/docs/images/edit-4.png new file mode 100644 index 00000000000..23b706fd61b Binary files /dev/null and b/docs/images/edit-4.png differ diff --git a/docs/images/edit-5.png b/docs/images/edit-5.png new file mode 100644 index 00000000000..e6e0541d9ac Binary files /dev/null and b/docs/images/edit-5.png differ diff --git a/docs/images/icesiolz/DG1.png b/docs/images/icesiolz/DG1.png new file mode 100644 index 00000000000..4491099f29a Binary files /dev/null and b/docs/images/icesiolz/DG1.png differ diff --git a/docs/images/icesiolz/DG2.png b/docs/images/icesiolz/DG2.png new file mode 100644 index 00000000000..4491099f29a Binary files /dev/null and b/docs/images/icesiolz/DG2.png differ diff --git a/docs/images/icesiolz/DG3.png b/docs/images/icesiolz/DG3.png new file mode 100644 index 00000000000..13e7834612d Binary files /dev/null and b/docs/images/icesiolz/DG3.png differ diff --git a/docs/images/icesiolz/DG4.png b/docs/images/icesiolz/DG4.png new file mode 100644 index 00000000000..50b38657da0 Binary files /dev/null and b/docs/images/icesiolz/DG4.png differ diff --git a/docs/images/icesiolz/UG1.png b/docs/images/icesiolz/UG1.png new file mode 100644 index 00000000000..091e348396d Binary files /dev/null and b/docs/images/icesiolz/UG1.png differ diff --git a/docs/images/icesiolz/UG2.png b/docs/images/icesiolz/UG2.png new file mode 100644 index 00000000000..8ca4899ed98 Binary files /dev/null and b/docs/images/icesiolz/UG2.png differ diff --git a/docs/images/icesiolz/UG3.png b/docs/images/icesiolz/UG3.png new file mode 100644 index 00000000000..0488a8140f5 Binary files /dev/null and b/docs/images/icesiolz/UG3.png differ diff --git a/docs/images/icesiolz/UG4.png b/docs/images/icesiolz/UG4.png new file mode 100644 index 00000000000..4765ebd0c19 Binary files /dev/null and b/docs/images/icesiolz/UG4.png differ diff --git a/docs/images/icesiolz/UG5.png b/docs/images/icesiolz/UG5.png new file mode 100644 index 00000000000..491e8902f50 Binary files /dev/null and b/docs/images/icesiolz/UG5.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5..00000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/lrchema.png b/docs/images/lrchema.png new file mode 100644 index 00000000000..f7a5ad3e7ce Binary files /dev/null and b/docs/images/lrchema.png differ diff --git a/docs/images/lrchema/elisaAfter.PNG b/docs/images/lrchema/elisaAfter.PNG new file mode 100644 index 00000000000..55cd1a44e5f Binary files /dev/null and b/docs/images/lrchema/elisaAfter.PNG differ diff --git a/docs/images/lrchema/elisaBefore.PNG b/docs/images/lrchema/elisaBefore.PNG new file mode 100644 index 00000000000..ac69413b335 Binary files /dev/null and b/docs/images/lrchema/elisaBefore.PNG differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593..00000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/mannggoo.png b/docs/images/mannggoo.png new file mode 100644 index 00000000000..14158d5c7e6 Binary files /dev/null and b/docs/images/mannggoo.png differ diff --git a/docs/images/mannggoo/ELISA_EVENT_UI.png b/docs/images/mannggoo/ELISA_EVENT_UI.png new file mode 100644 index 00000000000..30d19e4707b Binary files /dev/null and b/docs/images/mannggoo/ELISA_EVENT_UI.png differ diff --git a/docs/images/mannggoo/ELISA_UI.png b/docs/images/mannggoo/ELISA_UI.png new file mode 100644 index 00000000000..5bfc1bffc59 Binary files /dev/null and b/docs/images/mannggoo/ELISA_UI.png differ diff --git a/docs/images/sianghwee.png b/docs/images/sianghwee.png new file mode 100644 index 00000000000..39419cf640a Binary files /dev/null and b/docs/images/sianghwee.png differ diff --git a/docs/images/sianghwee/task-panel.jpg b/docs/images/sianghwee/task-panel.jpg new file mode 100644 index 00000000000..2012ea41c0d Binary files /dev/null and b/docs/images/sianghwee/task-panel.jpg differ diff --git a/docs/images/sianghwee/taskpanel-annotated.PNG b/docs/images/sianghwee/taskpanel-annotated.PNG new file mode 100644 index 00000000000..d95252db1b0 Binary files /dev/null and b/docs/images/sianghwee/taskpanel-annotated.PNG differ diff --git a/docs/images/sianghwee/taskpanel.PNG b/docs/images/sianghwee/taskpanel.PNG new file mode 100644 index 00000000000..4667045194f Binary files /dev/null and b/docs/images/sianghwee/taskpanel.PNG differ diff --git a/docs/images/snakegamehard.PNG b/docs/images/snakegamehard.PNG new file mode 100644 index 00000000000..4add5b09fad Binary files /dev/null and b/docs/images/snakegamehard.PNG differ diff --git a/docs/images/themewhite.PNG b/docs/images/themewhite.PNG new file mode 100644 index 00000000000..1c1e232507d Binary files /dev/null and b/docs/images/themewhite.PNG differ diff --git a/docs/images/ug_features.png b/docs/images/ug_features.png new file mode 100644 index 00000000000..d6347bf0b78 Binary files /dev/null and b/docs/images/ug_features.png differ diff --git a/docs/images/ug_introduction.png b/docs/images/ug_introduction.png new file mode 100644 index 00000000000..67313573678 Binary files /dev/null and b/docs/images/ug_introduction.png differ diff --git a/docs/images/ug_legend.png b/docs/images/ug_legend.png new file mode 100644 index 00000000000..c76b0a152d4 Binary files /dev/null and b/docs/images/ug_legend.png differ diff --git a/docs/images/ug_quickstart.png b/docs/images/ug_quickstart.png new file mode 100644 index 00000000000..54ca60b4167 Binary files /dev/null and b/docs/images/ug_quickstart.png differ diff --git a/docs/images/ug_tip.png b/docs/images/ug_tip.png new file mode 100644 index 00000000000..7fec1627c27 Binary files /dev/null and b/docs/images/ug_tip.png differ diff --git a/docs/images/ug_title.png b/docs/images/ug_title.png new file mode 100644 index 00000000000..7b3be32ad71 Binary files /dev/null and b/docs/images/ug_title.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad940..00000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a73227..00000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/blimyj.adoc b/docs/team/blimyj.adoc new file mode 100644 index 00000000000..bac245a7565 --- /dev/null +++ b/docs/team/blimyj.adoc @@ -0,0 +1,87 @@ += Bryan Lim Yan Jie - Project Portfolio +:site-section: AboutUs +:toc: +:toc-title: +:toc-placement: preamble +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ELISA + +== Introduction + +My team of 5 were tasked with morphing or enhancing a basic command line interace (CLI) desktop address book application for our Software Engineering Project. + +We were all computing students who were experiencing high amounts of stress from juggling the never-ending list of responsibilities we had. +We then realised that this was not a problem that was unique to ourselves, thus we resolved to develop an application to help reduce the stress of individuals like us. +We decided to morph the project into a student assistant we called ELISA. Our Extremely Intelligent n' Loud Student Assistant. +We decided that ELISA could help us through 5 ways. + +1. She could help keep track of our tasks & events +2. She could help us focus on one task at a time when we feel overwhelmed. +3. She could remind us so that we don't have to constantly scan through the lists to ensure that we remember everything we need to. +4. She could entertain us. + +This is how our user interface looks like: + +image::blimyjUI.png[] + +My role was to implement the reminder and snooze functionality as well as to design the underlying commons classes used by my teammates. + +== Summary of contributions + +In this section, I will be briefly going through my contributions to this team project. For the full extent of my contributions to the project, please visit https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#=undefined&search=blimyj[here]. + +* *Major enhancement*: added *the ability to* set and snooze reminders +** What it does: allows the user to remind himself of details of tasks or events when needed. +** Justification: This feature improves the product significantly because the user now no longer needs to constantly scan through lists in order to remember information at the right time. Instead ELISA will now remind him when it's time. +** Highlights: This enhancement had to deal with multiple threads and had to handle the occurrence of reminders correctly. Due to the undo / redo feature, it required an in-depth analysis of thread-safety and time-sensitive behaviour analysis. The implementation was challenging as it required modifications to other components such as the ItemModelManager in order to ensure the correct handling of reminders when edited, undone or redone. + +* *Minor enhancement*: add *commons classes* +** What it does: Contains the details required for tasks, events & reminders. +** Justification: It allowed for cross-reference of information between the lists so that details of the tasks, events & reminders could be accessed regardless of the type of list being viewed without cyclic dependencies. +** Highlights: This enhancement had to support immutability in order ensure thread-safety as well as to make the code easier to maintain. +** Credits: +*** Ideas for considering builder pattern [https://stackoverflow.com/questions/29881135/difference-between-builder-pattern-and-constructor[StackOverflow] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.1` - `v1.4` (6 releases, excluding `v1.3.4`) on GitHub +*** Managed team meetings & agendas. (Week 4-10) +*** Managed bug allocation for PE. +*** Managed Github team repo. (Setting up of branch protection & tools mentioned below) +** Documentation: +*** Updated the developer guide by giving the implementation of the undo function as well as filling in use cases and user stories +** Reviewed the following pull requests: +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/82[PR #82]: +**** Suggested abstraction of arePrefixesPresent method to adhere to DRY principle. +**** Suggested usage of Optional to prevent potential error. +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/116[PR #116]: Suggested changes in function naming in order to maintain code consistency which were eventually implemented. +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/266[PR #266]: Spotted potential conflict with reminder due to repeated addition from autoReschedule event. + +** Tools: +*** Integrated AppVeyor & Coveralls to the team repo [https://github.com/AY1920S1-CS2103T-T10-3/main/pull/281[#281]] + +== 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=reminder] + +include::../UserGuide.adoc[tag=snooze] + +== 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=commons] + +include::../DeveloperGuide.adoc[tag=addreminder] + +include::../DeveloperGuide.adoc[tag=snoozereminder] + diff --git a/docs/team/icesiolz.adoc b/docs/team/icesiolz.adoc new file mode 100644 index 00000000000..111b384d11b --- /dev/null +++ b/docs/team/icesiolz.adoc @@ -0,0 +1,153 @@ += Low Cheng Yi - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ELISA + +== Overview +My team of 5 software engineering students were tasked with enhancing a basic command line interface (CLI) desktop address book application for our Software Engineering project. + +As computing students, we face a lot of projects and assignments every day. The intense workload and fast-paced environment can make us forget the things that we need to do. We feel stressed out easily as we do not know how to manage all our tasks in an organised manner. + +Therefore, my team came up with ELISA. ELISA stands for Exceptionally Loud and Intelligent Student Assistant. ELISA helps students by keeping track of all the tasks that he/she may have, as well as provide companionship to alleviate the stress. + +== Application introduction +This is how our application looks like: + +.User interface annotated +image::sianghwee/taskpanel-annotated.PNG[] +The rest of the report will refer to this components and so their function will be briefly explained. + +[cols="2, 10"] +|=== +|Component |Function + +|ELISA icon +|Icon of the application which changes with the different mode of the application. + +|Result display +|The "chat box" with ELISA which contains all past user input and ELISA's response. + +|Command box +|The user can type their commands here to interact with ELISA. + +|View panel +|The current view of the application. It can show the task, event, reminder or calendar panel. +|=== + +== Summary of contributions + +In this section, I will be briefly going through my contributions to this team project. For the full extent of my contributions to the project, please visit https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#=undefined&search=icesiolz[here]. + +Major enhancements: + +- Added Game Command + +- Refactored and designed ELISA's UI + +Minor enhancements: + +- Added Navigation Features (`show`, `up/down`) + +- Added Visual Features (`theme`) + +=== Functional contributions +* *Game mode (Major Feature)*: + +.Snake Game *HARD* mode +image::snakegamehard.PNG[500, 600] + +_What does it do?_ + +This feature allows users to enter the Game mode to take a break from all the work. After entering the game mode, users will be able to play the traditional Nokia (phone) game: Snake. Snake has a very simple rule: Eat the food and do not bite your own tail! The game does not take up a lot of time and therefore is the best game for a short break. + +_Why make it?_ + +Users may get stressed or burnt out when they are constantly doing work non-stop. The game mode is developed as an avenue to take a break from all the work and provide a form of entertainment. Taking a temporary break to recharge will definitely help the user be more productive later on. + +_Why is it special?_ + +There are 2 difficulty levels in this game feature: Easy and Hard. In the easy mode, there are no walls (indicated by the blue box) whereas in the hard mode, 20 walls will be randomly spawned in the game space. Colliding into these walls will result in GAME OVER. The option for a hard mode is provided to keep the game interesting as the walls are in different positions every round. Users that have gotten used to the easy mode will be able to stay engaged in the hard mode. + +This feature is very special because there is no task tracking app in the current market with a built-in game feature with the sole purpose of providing entertainment. Many people are neglecting the importance of taking breaks and the effects it has on boosting productivity. I hope that this feature will be able to encourage these people to take breaks. + +_Difficulties?_ + +The difficulty of this feature is mainly the need to integrate this feature into ELISA. Originally, I wanted to run the game in a separate tab (Refer to the annotated figure above) from the list and calendar tabs but to do so would mean that the user has to click into the tab when they want to play the game and click the commandbox when they want to type a command. This is very problematic as ELISA is designed for CLI users which meant that they do not want to use the mouse. + +I worked around that problem by implementing the game feature using a separate scene from the ELISA's scene. This means that when the user enter the game mode, he will not be able to see ELISA and his list of tasks. This solves the problem I mentioned earlier as well as met the goal of encouraging the user to take a break from work. + +* *Navigation Features (Minor Feature)*: + +The following features are features that allow the application to be completely CLI based. Since ELISA is designed for CLI users, I have to provide a way for the users to navigate through ELISA without the use of the mouse. + +`show T/E/R/C` switches the tabs between the lists and calendar views. +`up/down` scrolls the list up/down. + +* *Theme Feature (Minor Feature)*: + +The theme feature is to allow users to change the colour scheme of ELISA. This feature is very useful especially when the screen it too bright/dark depending on the location that the user is in and switching the colour scheme will make ELISA more visible. + +.Theme Feature +image::icesiolz/UG1.png[] + +* *UI integration (Major Contribution)*: + +For this section, please refer to the annotated diagram above. + +I designed the entire UI of ELISA to display the features that ELISA has. While UI may not be considered a feature, it is a huge part of whether an app is user-friendly and therefore I have devoted a significant amount of time into its design. + +Firstly the mascot, ELISA. ELISA is a female student assistant, therefore the overall structure of ELISA is that of a female robot. The colour blue is chosen because blue is the most popular colour in the world. (according to a study by Philip Cohen from the University of Maryland) It is a colour often found in nature and people always describe blue as calm and serene, which is what we want users to feel. + +Secondly, the chat style `Result Display`. I designed it to be a chat style display so that it would seem like ELISA is an intelligent companion. The chat bubbles allow users to experience texting with a friend (like other instant messaging apps) which has been proven to be helpful in alleviating stress. + +Thirdly, the intuitive icons of ELISA that displays information in each list. Green ELISA with a cheerful appearance indicates that a task has been completed while Red ELISA with a crossed eye indicates that a task is incomplete. These icons make ELISA more interesting and friendly to the user as it further personifies ELISA by giving her emotions. Additionally, there were actually more ELISA icons that were designed but not used in the final product because of time constraints and the difficulty of aligning all the elements of the app. + + +=== Other contributions: + +* *Project management*: + + +* *Refactoring*: + +Refactored the UI classes as well as the DarkTheme.css file so that it is able to integrate with the project. + +* *Documentation*: + + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide for my main feature. They showcase my ability to write documentation targeting end-users._ +|=== + +Firstly, I designed the heading banners to make the UG more friendly as compared to plain text headings. + +.UG banner +image::icesiolz/UG5.png[] + +Secondly, I rearranged the features of the UG into better headings such as Visual Features, Navigation Features, CLI Features etc. This improves readability of the UG. + +Thirdly, I wrote the Theme Feature, Calendar Feature, Navigation Feature and Game Feature portion of the UG. I will show the Game Feature portion of the UG here below. + +image::icesiolz/UG3.png[] + +image::icesiolz/UG4.png[] +.Game Feature + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide for my main feature. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +I updated the UI component of the DG as it had been refactored and new classes are added such as TaskListPanel, TaskListCard, ElisaDialogBox etc. + +.UI component +image::icesiolz/DG4.png[] + ++ + +I also updated the Game Features portion of the DG as it is a major feature that I have implemented. + +image::icesiolz/DG1.png[] +image::icesiolz/DG2.png[] +image::icesiolz/DG3.png[] + +.GameFeature + + diff --git a/docs/team/lrchema.adoc b/docs/team/lrchema.adoc new file mode 100644 index 00000000000..70e019acadd --- /dev/null +++ b/docs/team/lrchema.adoc @@ -0,0 +1,94 @@ += Chandrasekaran Hema - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: AddressBook - Level 3 + +''' + +== About the Project + +My team of 4 software engineering students and I were tasked with either enhancing or morphing a basic command line +interface desktop address book application for our Software Engineering project. We decided to morph the application, +keeping a similar underlying architecture but changing the purpose entirely. + +We are living in a fast paced, stressful world where things are constantly changing; and so are our tasks. We thought it +would be helpful to have an application to aid us in managing these tasks, so we chose to morph the address book into a +student assistant with a personality to liven up our dreary days, and we called her ELISA (Exceptionally Loud and +Intelligent Student Assistant). This application enables users to add and track various tasks and events, as well as +receive reminders for them. The assistant is in the form of a chat bot, and she has been given a sassy and humorous +personality in order to enhance the user experience. + +This is how our user interface looks like: + +image::sianghwee/taskpanel-annotated.PNG[] + +My role was to implement the undo and redo functionality as well as give ELISA more of a personality. + +== Summary of contributions +* *Code contributed*: My contributions can be found https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=lrchema&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=lrchema&tabRepo=AY1920S1-CS2103T-T10-3%2Fmain%5Bmaster%5D[here] + +* *Enhancement added*: added the ability to *undo/redo previous commands* +** Function: The undo command allows the user to undo a previous command. The user may reverse this undo command with the redo command. +** Justification: In the event that users have made a mistake or changed their minds about executing a command, the undo command enables them to reverse that command. If they change their minds again and decide to execute the command after all, then the redo command enables them to do so easily. +** Highlights: The main challenge was to refactor the command from the simpler state method to the inverse function method in order to store only one reference to all other components. +** Implementation Details: +*** Initially I had implemented this feature by storing the state of the application after each command execution, and the undo command would set the state of the application to a previous one, almost like the application was time travelling. However, this logic interfered with my teammate Bryan’s feature, reminders, resulting in duplicate reminders. Reminders work by storing pending reminders in a list and past reminders in another list. The previous implementation of the undo feature worked by clearing the model and repopulating it after every command execution. However, this method or clearing and repopulating the model did not properly account for the items in the pending and past reminders list. This caused the reminder to be added multiple times and thus there were duplicate reminders. +*** In order to ensure that the undo redo feature worked correctly and did not interfere with any other features, I rewrote the logic of the feature. This time, there was no time travelling backwards. The undo command only made another modification to the application, instead of reversing a previous modification. This was done by ensuring that all commands had a reverse function, which would exactly reverse the effects of that command’s execution. Naturally, redo would simply run the execution again. + +* *Enhancement added*: gave ELISA a personality and sense of humour +** Function: For each command, ELISA gives either a confirmation or an error message. I made the messages humorous or sarcastic, instead of plain and emotionless. Additionally, ELISA will provide the user with a randomly selected joke upon request. +** Justification: ELISA is able to provide a sense of companionship for the user, and the jokes can help relieve stress and provide a welcome break. + +* *Other contributions*: + +** Enhancements to underlying implementations: +*** Refactored the packages and the parser so that they were no longer named address book, they are now called elisa. +*** Moved and renamed some image and text resource files to make sure that the application works when packaged as a jar file +** Documentation: +*** Updated the user guide with information on how to use the undo and redo commands +*** Updated the developer guide by giving the implementation of the undo function as well as filling in use cases and user stories +** Reviewed the following pull requests: +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/116[PR #116]: Made a comment on cleaning up code by deleting it instead of commenting it out +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/132[PR #132]: Suggested to make the command non-case-sensitive, which was adopted. +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/136[PR #136]: Gave a suggestion to shorten the command word, which was adopted as an option along with the original long form +*** https://github.com/AY1920S1-CS2103T-T10-3/main/pull/156[PR #156]: Proposed to change the colour of the dialog to something less abrasive and gave an alternative colour option. This suggestion was adopted +** Design: +*** Reviewed and gave suggestions on how to improve the ELISA logo my teammate Chengyi created, which were implemented in the final version of the logo. +The logo before, which looks cute and innocent + +image::lrchema/elisaBefore.PNG[][, 200] + +and the logo after, which is more confident and sassy + +image::lrchema/elisaAfter.PNG[][, 200] + +*** Created a chime sound for the reminder function, used in https://github.com/AY1920S1-CS2103T-T10-3/main/pull/157[PR #157] + +== 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=undo] + +include::../UserGuide.adoc[tag=redo] + +include::../UserGuide.adoc[tag=joke] + +== 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=undobyreverse] + +include::../DeveloperGuide.adoc[tag=undoredo] + +include::../DeveloperGuide.adoc[tag=userstories] + +include::../DeveloperGuide.adoc[tag=usecaselrchema] diff --git a/docs/team/mannggoo.adoc b/docs/team/mannggoo.adoc new file mode 100644 index 00000000000..bab60188db4 --- /dev/null +++ b/docs/team/mannggoo.adoc @@ -0,0 +1,134 @@ += Lim Yu Hui - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:sectnums: +:note-caption: :information_source: +== PROJECT: ELISA + +== Overview +Table of Contents: + +. <> +.. <> +.. <> +. <> +.. <> +.. <> +. <> +. <> +.. Design Considerations +.. UML Diagrams +. <> + +== About the Project +My team of 5 software engineering students were tasked with enhancing a basic command line interface (CLI) desktop address book application for our Software Engineering project. + +=== Context +Living in a dynamic world where tasks and events are constantly changing, we recognised a need for a dynamic scheduler that can help computer-savvy college students excel in their busy lives. +As such, our team decided to morph the addressbook application into a student assistant. + +We decided to call her ELISA, an Exceptionally Loud and Intelligent Student Assistant. Our application enables users to add and track their tasks and events while receiving reminders for them. The assistant is in the form of a chat bot and she was given a sassy and humorous personality to enhance the user experience. There are also other features to aid the productivity of students and serve as an entertaining companion. + +=== About ELISA +Here is a brief introduction of ELISA. This is one of the views of ELISA: + +.Annotated user interface +image::mannggoo/ELISA_EVENT_UI.PNG[] + +Upon startup, ELISA would show the default (T)ask tab. However, as I was more involved in the Events, I have chosen to show the (E)vent tab instead. + +Now that I've briefly introduced ELISA, the remaining sections will be mainly focusing on what I contributed. + +== Summary of Contributions + +In this section, I will be briefly going through my contributions to this team project. For the full extent of my contributions to the project, please visit https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=mannggoo&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[here]. + +=== My Role +My role lies mainly in the logic component. I refactored most of the code in the logic such as the commands in AddressBook as well as the parser. Given below are the new features that I've added for this project. + + +|=== +|*Enhancement added:* Automatic rescheduling of recurring events *(Major Feature)*. +|=== + +_What it does:_ + +Updates the time of recurring events automatically. For example, weekly events’ time will be updated when it has passed. + + +_Justification:_ + +Users might have events that occur weekly such as CS2103T Quiz. When the deadline is over, users would have to manually change the date to next weeks’. However, with this feature, ELISA can change it for them, making it more convenient to update events and keep them relevant. + + +_Highlights:_ + +This enhancement works for *_3 aspects_*: + +1) When creating a new event, if the event is already over, AutoReschedule immediately updates its time. + +2) When ELISA is running, the view will automatically be refreshed to reflect the changes in the Event's time. + +3) When loading from the storage. If the event is already over, ELISA will update it and show the user the new date. + + +_Challenges faced:_ + +I had to use a thread that schedules for event’s date to be updated, however this often interfered with my teammates’ thread for reminders as reminders would need to be rescheduled too. However, the reminder’s time is not relative to the event’s, as such given our constraints, we decided to disallow rescheduling for events with reminders. Given the many possible scenarios of violating this, we decided to implement an ItemBuilder that prevents the build of an item that has both reschedule and reminder. + +Also, there was the issue of creating multiple threads for the rescheduling, as there could be an extremely large number of threads, as such, the singleton pattern is used for a thread that serves as an overall manager for all the rescheduling. This made managing the tasks easier as there are many operations such as undo, and edit that requires frequent modifying of the tasks in the thread. +Lastly, updating a past date to an upcoming date required the use of modulo for better efficiency. + + +|=== +|*Enhancement added:* Expanding item view to show its related task, event and reminder *(Minor Feature)*. +|=== + + +_What it does:_ + +Expands the item to show all details of an item. This includes fields of its task, event and reminder, if they are present. + + +_Justification:_ + +Each list shows the minimal details related to the item as we want to keep it concise. However, users viewing the task list might want to know if there is deadline for it, or if there is a reminder associated with it. As such, they can expand the item to show all its details. + + +_Highlights:_ + +This feature works on Task list, Event list and Reminder list, making it convenient to find details, regardless of which view the user is on. + +Users can also open and close and item using the commands “open” and “close’ without touching the mouse. + + +_Challenges faced:_ + +However, as the details are shown in a popup, there were issues such as the popup covering over other windows, even when the app is minimised. As such, I had to add a listener to the window in order the popup when required, as well as prevent multiple popups. + + +=== Other Contributions +* *Project Management* + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/51[PR #51] -- Gave suggestions to use LocalDateTime and improve readability of code, which was eventually adopted. + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/72[PR #72] -- Gave suggestions to use final variables for the string representation of tabs, which was eventually adopted. + +(https://github.com/AY1920S1-CS2103T-T10-3/main/pull/99[PR #99]) (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/119[PR #119]) (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/130[PR #130]) () -- Gave suggestion related to design principles. + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/252[PR #252] -- Fixed issues for the team + +* *Refactoring* + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/82[PR #82] -- Refactored the Logic component and parsers to accept commands and parameters needed for ELISA. Changed flag from "d/" to "-d" to be more intuitive for CLI users. + +* *Enhancements to existing features* + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/129[PR #129] -- Accept `--tk`, `--e` and `--r` flags for convenient deletion of a sub-item using `edit`. Accept `10.min.later` parser format. + +* *Documentation* + +https://github.com/AY1920S1-CS2103T-T10-3/main/pull/40/files[PR #40] -- Updated About-Us and Contact-Us. + +(https://github.com/AY1920S1-CS2103T-T10-3/main/pull/43/files[PR #43]) (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/150/files[PR #150]) (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/172/files[PR #172]) (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/183/files[PR #183]) -- Updated user guide and developer's guide to be more reader-friendly. + + +== Contributions to the User Guide +|=== +|_Given below are sections I contributed to the User Guide for my main feature. They showcase my ability to write documentation targeting end-users. Parts that could not fit into this section are given as links._ +|=== + +Link to: +<> + +include::../UserGuide.adoc[tag=autoreschedule] + +== Contributions to the Developer's Guide +|=== +|_Given below are sections I contributed to the Developer Guide for the logic implementation, the refactored add and edit, as well as my main feature. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +Link to: +<> + +include::../DeveloperGuide.adoc[tag=logic] + +include::../DeveloperGuide.adoc[tag=autoreschedule] + +== Conclusion +This marks the end of my contributions to ELISA. This journey has been a meaningful one, as it is the first time I've worked with teammates on a software engineering project. +Although I've reached the end of this module, I hope to continue developing ELISA even after, as I truly believe that she has the potential to be a great companion and have a positive impact on many students. + + + + diff --git a/docs/team/sianghwee.adoc b/docs/team/sianghwee.adoc new file mode 100644 index 00000000000..17a5d8fa497 --- /dev/null +++ b/docs/team/sianghwee.adoc @@ -0,0 +1,129 @@ += Ng Siang Hwee - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:sectnums: +== PROJECT: ELISA + +== Overview +My team, comprising of 5 software engineering students, was tasked with enhancing a basic command line interface (CLI) desktop address book application for our Software Engineering project. + +With modern life comes a fast-paced, hectic lifestyle. It is increasingly easy to be overwhelmed by the insurmountable number of responsibilities and tasks we have to fulfill. Most task-manager applications involve countless clicking and navigation, which defeats their purpose. To rectify this, my team decided to design a simple, no frills CLI student assistant from the address book to cater to students who prefer CLI. + +We named our application ELISA, an Exceptionally Loud and Intelligent Student Assistant. ELISA enables users to add and track their tasks and events while receiving reminders for them. With a sassy and humorous personality and her user-interface interaction (in the form of a chat bot), she is simpler, yet more engaging to use than other regular task managers. There are also other features to aid the productivity of students and to spice up their life. + +== Application introduction +This is how our application looks like: + +.User interface annotated +image::sianghwee/task-panel.jpg[] +ELISA's various components serve the following functions: + +[cols="2, 10"] +|=== +|Component |Function + +|ELISA icon +|Icon of the application which changes with the different mode of ELISA. + +|Result display +|The "chat box" with ELISA which contains all past user input and ELISA's response. + +|Command box +|The user can type their commands here to interact with ELISA. + +|View panel +|The current view of the application. It can show the task, event, reminder or calendar panel. +|=== + +== Summary of contributions + +In this section, I will be briefly going through my contributions to this team project. For the full extent of my contributions to the project, please visit https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#=undefined&search=sianghwee[here]. + +=== Functional contributions +* *Priority mode (Major Feature)*: + +_What does it do?_ + +This feature allows the user to enter a priority mode where they are able to prioritize tasks with a higher priority first and focus on that task to complete it. + +_Why make it?_ + +This features helps the user narrow their focus down to completing one task at a time and prevents them from being overwhelmed by a long list of tasks. + +_Why is it special?_ + +There are multiple modes to cater to different user needs. Users can choose from a focus mode, a timed mode, a normal mode or a combination of multiple modes. The different modes will be explained in greater detail in the user guide. + +At the same time, users are able to update their task list in realtime (add, delete, edit) so that they can always be sure that the task that they are currently doing is the most important one. + +_Difficulties?_ + +The difficulty of this feature comes from the fact that it has to be integrated with all the other commands within the application. That means that the algorithm needs to be written in a way such that it is constantly updating itself to give the user the most important task despite the changes in the tasks. + +For example, when a new task of a higher priority (than the current task) is added, the algorithm should be able to make that the task that is to be completed first. This requires a lot of integration with all the other code so that ELISA can behave differently depending on whether she is in priority mode. + +* *Sorting (Minor Feature)*: + +The sorting feature allows users to sort their items so that they are able to better arrange their items in a way that is most useful to them. Each list has its own sorting algorithm while users can also choose to sort the items based on their priority or description. Refer to https://ay1920s1-cs2103t-t10-3.github.io/main/UserGuide.html#sorting-elisa-out-code-sort-code[here] for more information on this feature. + +* *Find (Minor Feature)*: + +The find feature allows users to find an item based on the description of the item. This will aid users in getting the item that they need at the fastest possible time. Refer to https://ay1920s1-cs2103t-t10-3.github.io/main/UserGuide.html#finding-a-task-code-find-code[here] for more information on this feature. + +* *Other implementations*: + +Helped my teammates with the following features and implementation: + +. Implementation (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/138[PR #138]) and fixes on the calendar panel so that it is able to display the events and update the events when there are changes to the events (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/148[PR #148], https://github.com/AY1920S1-CS2103T-T10-3/main/pull/181[PR #181] and https://github.com/AY1920S1-CS2103T-T10-3/main/pull/244[PR #244]). + +. Helped with the implementation of the undo for marking a task as completed (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/153[PR #153]). + +=== Other contributions: + +* *Project management*: + +Review and made notable suggestions on the following PR: + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/69#pullrequestreview-299143917[PR #69] - gave idea of storing the priority in the item class that was ultimately adopted + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/130#pullrequestreview-304515805[PR #130] - gave idea of getting the command word to display to the user was ultimately adopted + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/132[PR #132] - gave suggestion on how to improve the code for readability and to follow coding practice. + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/134#pullrequestreview-304938689[PR #134] - gave idea of moving method to the `UndoableCommand` abstract class was ultimately adopted in https://github.com/AY1920S1-CS2103T-T10-3/main/pull/137[PR #137] + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/141#pullrequestreview-306620283[PR #141] - gave suggestion of using `Long` instead of `BigInteger` + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/158#pullrequestreview-308251560[PR #158] - gave suggestion on how to improve the feed back message to the users for undo command, which was ultimately adopted + +. https://github.com/AY1920S1-CS2103T-T10-3/main/pull/252#pullrequestreview-312419235[PR #252] - gave suggestion on fixing some issue with the code and optimizing it + +{empty} + +* *Refactoring*: + +Refactored the model (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/68[PR #68]) and storage (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/79[PR #79]) class so that it will work with the project. + + +* *Documentation*: + +Updated the developer guide to include the use cases (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/44[PR #44]),user stories (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/47[PR #47]) and manual testing (https://github.com/AY1920S1-CS2103T-T10-3/main/pull/271[PR #271]). + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide for my main feature. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=priority] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide for the model implementation and my main feature. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=model] + +include::../DeveloperGuide.adoc[tag=priority] diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 92cd8fa605a..00000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic; - -import java.nio.file.Path; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - - /** - * Returns the AddressBook. - * - * @see seedu.address.model.Model#getAddressBook() - */ - ReadOnlyAddressBook getAddressBook(); - - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Set the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index d47ce874b1a..00000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; - -/** - * The main LogicManager of the app. - */ -public class LogicManager implements Logic { - public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Storage storage; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - - CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); - - try { - storage.saveAddressBook(model.getAddressBook()); - } catch (IOException ioe) { - throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); - } - - return commandResult; - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -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. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public 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" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -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. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - 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()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java deleted file mode 100644 index bf824f91bd0..00000000000 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.Model; - -/** - * Format full help instructions for every command for display. - */ -public class HelpCommand extends Command { - - public static final String COMMAND_WORD = "help"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; - - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; - - @Override - public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.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; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -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.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -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_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} 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_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +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_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 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_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(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/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index a5bbe0b6a5f..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +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 email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_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 " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 79244d71cf7..00000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd5..00000000000 --- 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/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +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 phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} 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 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +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(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * 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 internalUnmodifiableList; - } - - @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 d7290f59442..00000000000 --- 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 fa764426ca7..00000000000 --- 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/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- 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/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : 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_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @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 { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +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; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index e4f452b6cbf..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java deleted file mode 100644 index 7a27ad09888..00000000000 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ /dev/null @@ -1,103 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; -import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; - -/** - * Controller for a help page - */ -public class HelpWindow extends UiPart { - - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; - - private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); - private static final String FXML = "HelpWindow.fxml"; - - @FXML - private Button copyButton; - - @FXML - private Label helpMessage; - - /** - * Creates a new HelpWindow. - * - * @param root Stage to use as the root of the HelpWindow. - */ - public HelpWindow(Stage root) { - super(FXML, root); - helpMessage.setText(HELP_MESSAGE); - root.sizeToScene(); - } - - /** - * Creates a new HelpWindow. - */ - public HelpWindow() { - this(new Stage()); - } - - /** - * Shows the help window. - * @throws IllegalStateException - *
    - *
  • - * if this method is called on a thread other than the JavaFX Application Thread. - *
  • - *
  • - * if this method is called during animation or layout processing. - *
  • - *
  • - * if this method is called on the primary stage. - *
  • - *
  • - * if {@code dialogStage} is already showing. - *
  • - *
- */ - public void show() { - logger.fine("Showing help page about the application."); - getRoot().show(); - getRoot().centerOnScreen(); - } - - /** - * Returns true if the help window is currently being shown. - */ - public boolean isShowing() { - return getRoot().isShowing(); - } - - /** - * Hides the help window. - */ - public void hide() { - getRoot().hide(); - } - - /** - * Focuses on the help window. - */ - public void focus() { - getRoot().requestFocus(); - } - - /** - * Copies the URL to the user guide to the clipboard. - */ - @FXML - private void copyUrl() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); - clipboard.setContent(url); - } -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 90bbf11de97..00000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,193 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - public MainWindow(Stage primaryStage, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - - // Configure the UI - setWindowDefaultSize(logic.getGuiSettings()); - - setAccelerators(); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - /** - * Sets the default size based on {@code guiSettings}. - */ - private void setWindowDefaultSize(GuiSettings guiSettings) { - primaryStage.setHeight(guiSettings.getWindowHeight()); - primaryStage.setWidth(guiSettings.getWindowWidth()); - if (guiSettings.getWindowCoordinates() != null) { - primaryStage.setX(guiSettings.getWindowCoordinates().getX()); - primaryStage.setY(guiSettings.getWindowCoordinates().getY()); - } - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { - try { - CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } - - return commandResult; - } catch (CommandException | ParseException e) { - logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); - throw e; - } - } -} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java deleted file mode 100644 index 7d98e84eedf..00000000000 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ /dev/null @@ -1,28 +0,0 @@ -package seedu.address.ui; - -import static java.util.Objects.requireNonNull; - -import javafx.fxml.FXML; -import javafx.scene.control.TextArea; -import javafx.scene.layout.Region; - -/** - * A ui for the status bar that is displayed at the header of the application. - */ -public class ResultDisplay extends UiPart { - - private static final String FXML = "ResultDisplay.fxml"; - - @FXML - private TextArea resultDisplay; - - public ResultDisplay() { - super(FXML); - } - - public void setFeedbackToUser(String feedbackToUser) { - requireNonNull(feedbackToUser); - resultDisplay.setText(feedbackToUser); - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/elisa/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/elisa/AppParameters.java index ab552c398f3..4299e97d194 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/elisa/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.elisa; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/elisa/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/seedu/elisa/Main.java index 052a5068631..2116ac538bc 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/elisa/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.elisa; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/elisa/MainApp.java similarity index 70% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/elisa/MainApp.java index e5cfb161b73..4464c8a457a 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/elisa/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.elisa; import java.io.IOException; import java.nio.file.Path; @@ -7,44 +7,49 @@ import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; + +import seedu.elisa.commons.core.Config; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.Version; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.commons.util.ConfigUtil; +import seedu.elisa.commons.util.StringUtil; + +import seedu.elisa.logic.Logic; +import seedu.elisa.logic.LogicManager; + +import seedu.elisa.model.ElisaCommandHistory; +import seedu.elisa.model.ElisaCommandHistoryManager; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.ItemModelManager; +import seedu.elisa.model.ItemStorage; +import seedu.elisa.model.ReadOnlyUserPrefs; +import seedu.elisa.model.UserPrefs; + +import seedu.elisa.storage.ItemListStorage; +import seedu.elisa.storage.JsonItemStorage; +import seedu.elisa.storage.JsonUserPrefsStorage; +import seedu.elisa.storage.Storage; +import seedu.elisa.storage.StorageManager; +import seedu.elisa.storage.UserPrefsStorage; +import seedu.elisa.ui.Ui; +import seedu.elisa.ui.UiManager; /** * Runs the application. */ 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); protected Ui ui; protected Logic logic; protected Storage storage; - protected Model model; + protected ItemModel model; protected Config config; + protected ElisaCommandHistory commandHistory; @Override public void init() throws Exception { @@ -56,12 +61,14 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + ItemListStorage itemListStorage = new JsonItemStorage(userPrefs.getItemStorageFilePath()); + storage = new StorageManager(itemListStorage, userPrefsStorage); initLogging(config); - model = initModelManager(storage, userPrefs); + commandHistory = new ElisaCommandHistoryManager(); + + model = initModelManager(storage, userPrefs, commandHistory); logic = new LogicManager(model, storage); @@ -73,24 +80,19 @@ public void init() throws Exception { * The data from the sample address book will be used instead if {@code storage}'s address book is not found, * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ - private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + private ItemModel initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs, ElisaCommandHistory stateHistory) { + ItemStorage initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); - } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = storage.toModelType(); } 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 Item Storage"); + initialData = new ItemStorage(); } 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 Item Storage"); + initialData = new ItemStorage(); } - return new ModelManager(initialData, userPrefs); + return new ItemModelManager(initialData, userPrefs, stateHistory); } private void initLogging(Config config) { @@ -174,6 +176,8 @@ public void start(Stage primaryStage) { @Override public void stop() { logger.info("============================ [ Stopping Address Book ] ============================="); + //Bryan Reminder + logic.shutdown(); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/elisa/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/elisa/commons/core/Config.java index 91145745521..9535928ee19 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/elisa/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.elisa.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/elisa/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/elisa/commons/core/GuiSettings.java index 5ace559ad15..e74b010b53a 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/elisa/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.elisa.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/elisa/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/elisa/commons/core/LogsCenter.java index 431e7185e76..ab66209b437 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/elisa/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.elisa.commons.core; import java.io.IOException; import java.util.Arrays; @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "elisa.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/elisa/commons/core/Messages.java b/src/main/java/seedu/elisa/commons/core/Messages.java new file mode 100644 index 00000000000..65efa5b1120 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/Messages.java @@ -0,0 +1,22 @@ +package seedu.elisa.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "I have no clue what you just said"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! %s"; + public static final String MESSAGE_INVALID_ITEM_DISPLAYED_INDEX = "The item index provided is invalid"; + public static final String MESSAGE_INCORRECT_SYMBOL_USAGE = "I spy with my little eye a \"-\". " + + "That shouldn't be there!"; + public static final String MESSAGE_ITEM_LISTED_OVERVIEW = "Here you go, %1$d items listed!"; + public static final String MESSAGE_NOTHING_TO_UNDO = "Nothing to undo, buddy." + + " Maybe try actually doing something first"; + public static final String MESSAGE_NOTHING_TO_REDO = "Why fix your mistakes when you didn't make any?"; + public static final String MESSAGE_INVALID_FAST_REMINDER_FORMAT = "Don't you remember?" + + "It should be in the format of \"(Positive Integer).(Time unit).later\"" + + "Eg. \"3.hour.later\" or \"10.min.later\""; + + public static final String MESSAGE_NO_PREVIOUS_REMINDER = "There ain't no recent reminder to snooze buddy..."; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/elisa/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/elisa/commons/core/Version.java index e117f91b3b2..64421309383 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/elisa/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.elisa.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/elisa/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/elisa/commons/core/index/Index.java index 19536439c09..e2c83852725 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/elisa/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.elisa.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/elisa/commons/core/item/Event.java b/src/main/java/seedu/elisa/commons/core/item/Event.java new file mode 100644 index 00000000000..e7f26400413 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/Event.java @@ -0,0 +1,184 @@ +package seedu.elisa.commons.core.item; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.commons.util.JsonUtil; +import seedu.elisa.model.AutoReschedulePeriod; + +/** + * Represents an Item's Event in ELISA. + * Duration of Event is 0 by default. + * Priority of Event is medium by default. + * Guarantees: immutable; + */ +public class Event { + + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + //Duration chosen over Period as Events are unlikely to exceed a day. + private final Duration duration; + private final boolean isAutoReschedule; + private final AutoReschedulePeriod period; + + /** + * Constructs an {@code Event}. + * + * @param startDateTime A valid LocalDateTime object that denotes the start of the event. + * @param duration A Duration of the event. Defaults to Duration.ZERO if null. + */ + public Event(LocalDateTime startDateTime, Duration duration) throws IllegalArgumentException { + this(startDateTime, duration, false, null); + } + + public Event(LocalDateTime startDateTime, Duration duration, boolean isAutoReschedule) + throws IllegalArgumentException { + this(startDateTime, duration, isAutoReschedule, null); + } + + public Event(LocalDateTime startDateTime, Duration duration, boolean isAutoReschedule, + AutoReschedulePeriod period) throws IllegalArgumentException { + requireNonNull(startDateTime); + if (duration != null) { + this.duration = duration; + } else { + this.duration = Duration.ZERO; + } + + this.startDateTime = startDateTime; + this.endDateTime = startDateTime.plus(this.duration); + this.isAutoReschedule = isAutoReschedule; + this.period = period; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public Duration getDuration() { + return duration; + } + + public boolean hasAutoReschedule() { + return isAutoReschedule; + } + + /** + * Set auto reschedule to true if the event should recur/auto-reschedule, false otherwise + * @param bool true if event can be auto-rescheduled + * @return a new Event object with the updated parameters + */ + public Event setAutoReschedule(boolean bool) { + return new Event(getStartDateTime(), getDuration(), bool); + } + + /** + * Get the reschedule period of this event + * @return AutoReschedule period of this event + */ + public AutoReschedulePeriod getPeriod() { + return this.period; + } + + /** + * Set the reschedule period of this event. This would also set isAutoReschedule of this event to true. + * @param period to set to this event + * @return a new Event object with the updated parameters + */ + public Event setReschedulePeriod(AutoReschedulePeriod period) { + return new Event(getStartDateTime(), getDuration(), true, period); + } + + public Event changeStartDateTime(LocalDateTime newStartDateTime) { + return new Event(newStartDateTime, getDuration(), this.isAutoReschedule, this.period); + } + + public Event changeDuration(Duration newDuration) { + return new Event(getStartDateTime(), newDuration, this.isAutoReschedule, this.period); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("\nStart DateTime: ") + .append(getStartDateTime().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm"))); + //Removed duration & endDateTime portion of string as requested in team meeting. + + return builder.toString(); + } + + /** + * Creates a string for UI display. + * @return A string containing only the start DateTime of the Event. + */ + public String toDisplay() { + final StringBuilder builder = new StringBuilder(); + builder.append("\nDateTime: ") + .append(getStartDateTime().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm"))); + + return builder.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Event)) { + return false; + } + + Event otherEvent = (Event) other; + return otherEvent.getStartDateTime().equals(getStartDateTime()) + && otherEvent.getEndDateTime().equals(getEndDateTime()) + && otherEvent.getDuration().equals(getDuration()) + && (getPeriod() == otherEvent.getPeriod() //checks if they are both null or same item + || (getPeriod() != null ? getPeriod().equals(otherEvent.getPeriod()) : false)); + } + + @Override + public int hashCode() { + return Objects.hash(startDateTime, endDateTime, duration); + } + + /** + * Creates an event object from a JSON string. + * @param jsonString the JSON string that represents the event + * @return the event object that is created + * @throws IOException when the jsonString is not in JSON format + * @throws IllegalValueException when the JSON string contains incorrect value + */ + public static Event fromJson(String jsonString) throws IOException { + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + + String startDateTimeString = node.get("startDateTime").asText(); + LocalDateTime startDateTime = LocalDateTime.parse(startDateTimeString); + + String durationString = node.get("duration").asText(); + Duration duration = Duration.parse(durationString); + + String periodString = node.get("period").toString(); // in the format of {"period": 60000} + if (!periodString.isEmpty() && !periodString.equals("null")) { + periodString = node.get("period").get("period").asText(); // get the long value + Long periodMillis = Long.valueOf(periodString); + AutoReschedulePeriod period = new AutoReschedulePeriod(periodMillis); + return new Event(startDateTime, duration, true, period); + } + + return new Event(startDateTime, duration); + } + +} diff --git a/src/main/java/seedu/elisa/commons/core/item/Item.java b/src/main/java/seedu/elisa/commons/core/item/Item.java new file mode 100644 index 00000000000..defec180715 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/Item.java @@ -0,0 +1,452 @@ +package seedu.elisa.commons.core.item; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.commons.util.JsonUtil; + +/** + * Represents an Item in Elisa. + * Guarantees: ItemDescription is present and not null. + * At least one of the following three fields (Task, Event, Reminder) is present and not null. + */ +public class Item { + + // Identity fields + private final Task task; + private final Event event; + private final Reminder reminder; + // Data fields + private final ItemDescription itemDescription; + private final Set tags = new HashSet<>(); + private final Priority priority; + + /** + * @param priority A Priority of the event. Defaults to Priority.MEDIUM if null. + */ + private Item(Task task, Event event, Reminder reminder, + ItemDescription itemDescription, Set tags, Priority priority) { + requireNonNull(itemDescription); + this.task = task; + this.event = event; + this.reminder = reminder; + this.itemDescription = itemDescription; + this.tags.addAll(tags); + + if (priority != null) { + this.priority = priority; + } else { + this.priority = Priority.MEDIUM; + } + } + + public boolean hasTask() { + return task != null; + } + + public boolean hasEvent() { + return event != null; + } + + public boolean hasReminder() { + return reminder != null; + } + + public Optional getTask() { + if (this.task == null) { + return Optional.empty(); + } else { + return Optional.of(this.task); + } + } + + public Optional getEvent() { + if (this.event == null) { + return Optional.empty(); + } else { + return Optional.of(this.event); + } + } + + public Optional getReminder() { + if (this.reminder == null) { + return Optional.empty(); + } else { + return Optional.of(this.reminder); + } + } + + public Priority getPriority() { + return priority; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + public ItemDescription getItemDescription() { + return itemDescription; + } + + + + /** + * Change ItemDescription + */ + public Item changeItemDescription(ItemDescription newItemDescription) { + return new ItemBuilder().setItemDescription(newItemDescription) + .setTask(task) + .setEvent(event) + .setReminder(reminder) + .setTags(tags) + .setItemPriority(priority) + .build(); + } + + /** + * Change Task referenced + */ + public Item changeTask(Task newTask) { + return new ItemBuilder().setItemDescription(itemDescription) + .setTask(newTask) + .setEvent(event) + .setReminder(reminder) + .setTags(tags) + .setItemPriority(priority) + .build(); + } + + /** + * Change Event referenced + */ + public Item changeEvent(Event newEvent) { + return new ItemBuilder().setItemDescription(itemDescription) + .setTask(task) + .setEvent(newEvent) + .setReminder(reminder) + .setTags(tags) + .setItemPriority(priority) + .build(); + } + + /** + * Change Reminder referenced + */ + public Item changeReminder(Reminder newReminder) { + return new ItemBuilder().setItemDescription(itemDescription) + .setTask(task) + .setEvent(event) + .setReminder(newReminder) + .setTags(tags) + .setItemPriority(priority) + .build(); + } + + /** + * Change Tags referenced + */ + public Item changeTags(HashSet newTags) { + return new ItemBuilder().setItemDescription(itemDescription) + .setTask(task) + .setEvent(event) + .setReminder(reminder) + .setTags(newTags) + .setItemPriority(priority) + .build(); + } + + /** + * Changes the priority of the item. + * @param newPriority the new priority for the item. + * @return new Item with the new priority. + */ + public Item changePriority(Priority newPriority) { + return new ItemBuilder().setItemDescription(itemDescription) + .setTask(task) + .setEvent(event) + .setReminder(reminder) + .setTags(tags) + .setItemPriority(newPriority) + .build(); + } + + /** + * Returns true if both items have the same task referenced. + * This defines a weaker notion of equality between two items. + */ + public boolean hasSameTask(Item otherItem) { + return getTask().equals(otherItem.getTask()); + } + + /** + * Returns true if both items have the same Event referenced. + * This defines a weaker notion of equality between two items. + */ + public boolean hasSameEvent(Item otherItem) { + return getEvent().equals(otherItem.getEvent()); + } + + /** + * Returns true if both items have the same Reminder referenced. + * This defines a weaker notion of equality between two items. + */ + public boolean hasSameReminder(Item otherItem) { + return getReminder().equals(otherItem.getReminder()); + } + + /** + * Returns true if both items have the same identity and data fields. + * This defines a stronger notion of equality between two items. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Item)) { + return false; + } + + Item otherItem = (Item) other; + + return otherItem.getTask().equals(getTask()) + && otherItem.getReminder().equals(getReminder()) + && otherItem.getEvent().equals(getEvent()) + && otherItem.getItemDescription().equals(getItemDescription()) + && otherItem.getTags().equals(getTags()) + && otherItem.getPriority().equals(getPriority()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(task, event, reminder, itemDescription, tags, priority); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + builder.append("\nDescription: ") + .append(itemDescription.toString()); + + if (getTask().isPresent()) { + builder.append("\n\nTask Details: ") + .append(getTask().get().toString()); + } + + if (getEvent().isPresent()) { + builder.append("\n\nEvent Details: ") + .append(getEvent().get().toString()); + } + + if (getReminder().isPresent()) { + builder.append("\n\nReminder Details: ") + .append(getReminder().get().toString()); + } + + if (tags.size() > 0) { + builder.append("\nTags: "); + getTags().forEach(builder::append); + } + + return builder.toString(); + } + + public String getReminderMessage() { + final StringBuilder builder = new StringBuilder(); + builder.append("Description: ") + .append(itemDescription.toString()) + .append("\nReminder! ") + .append(getReminder().get().toString()); + + builder.append("\nPriority: ") + .append(priority.toString()); + + return builder.toString(); + } + + /** + * Checks the event in the item to see if it is auto reschedulable. + * Requirements: item definitely has an event. + * @return true if it is auto reschedule, false otherwise. + */ + public boolean hasAutoReschedule() { + if (getEvent().isPresent()) { + return getEvent().get().hasAutoReschedule(); + } else { + return false; + } + } + + /** + * Builder class for Item. + */ + public static class ItemBuilder { + + // Identity fields + private Task task = null; + private Event event = null; + private Reminder reminder = null; + + // Data fields + private ItemDescription itemDescription = null; + private Set tags = new HashSet<>(); + private Priority priority = Priority.MEDIUM; + + public ItemBuilder() {} + + public ItemBuilder setTask(Task task) { + this.task = task; + return this; + } + + public ItemBuilder setEvent(Event event) { + + this.event = event; + return this; + } + + public ItemBuilder setReminder(Reminder reminder) { + + this.reminder = reminder; + return this; + } + + public ItemBuilder setItemDescription(ItemDescription descriptor) { + requireNonNull(descriptor); + this.itemDescription = descriptor; + return this; + } + + //Consider using a defensive copy of tags, similar to EditCommand in AB3 + public ItemBuilder setTags(Set tags) { + requireNonNull(tags); + this.tags = tags; + return this; + } + + public ItemBuilder setItemPriority(Priority priority) { + this.priority = priority; + return this; + } + + /**Validates arguments of Item before initialising it + * + * @return A valid Item. + * @throws IllegalArgumentException If description not provided or task, event and reminder fields are null. + */ + public Item build() throws IllegalArgumentException { + Item newItem = new Item(task, event, reminder, itemDescription, tags, priority); + + //Validation of parameters of object after object has been created. + //Validate after object has been created as per StackOverflow link + //https://stackoverflow.com/questions/38173274/builder-pattern-validation-effective-java + //However this seems to be contrary to the answer in the following link + //https://stackoverflow.com/questions/12930852/clearing-doubts-about-the-builder-pattern + //However it seems safer to follow the first as the object fields could be mutated after it has been copied + // from the builder to the object. + if (newItem.getItemDescription() == null) { + throw new IllegalArgumentException("Description must be provided!"); + } + if (newItem.getTask().isEmpty() && newItem.getEvent().isEmpty() && newItem.getReminder().isEmpty()) { + throw new IllegalArgumentException("Task, Event & Reminder cannot all be empty!"); + } + + if (newItem.getReminder().isPresent() && newItem.getEvent().isPresent() + && newItem.getEvent().get().hasAutoReschedule()) { + throw new IllegalArgumentException("An auto-rechedule event can't have a reminder and vice versa!"); + } + + //Resetting all constructing parameters back to null, so a new object doesn't use the parameters of the + // previous object. + task = null; + event = null; + reminder = null; + itemDescription = null; + tags = new HashSet<>(); + priority = Priority.MEDIUM; + + return newItem; + } + + } + + /** + * Converts the item object into a json string. + * @return string representation of the item + * @throws JsonProcessingException when the item cannot be converted into a JSON string + */ + public String toJson() throws JsonProcessingException { + return JsonUtil.toJsonString(this); + } + + /** + * Creates an item object from a JSON string. + * @param jsonString the JSON string that represents the item + * @return the item object that is created + * @throws IOException when the jsonString is not in JSON format + * @throws IllegalValueException when the JSON string contains incorrect value + */ + public static Item fromJson(String jsonString) throws IOException, NullPointerException { + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + ItemBuilder temp = new ItemBuilder(); + + String itemDescriptionString = node.get("itemDescription").toString(); + ItemDescription id = ItemDescription.fromJson(itemDescriptionString); + temp.setItemDescription(id); + + String priorityString = node.get("priority").asText(); + Priority newPriority = Priority.fromJson(priorityString); + temp.setItemPriority(newPriority); + + if (node.hasNonNull("task")) { + String taskString = node.get("task").toString(); + Task t = Task.fromJson(taskString); + temp = temp.setTask(t); + } + + if (node.hasNonNull("event")) { + String eventString = node.get("event").toString(); + Event e = Event.fromJson(eventString); + temp = temp.setEvent(e); + } + + if (node.hasNonNull("reminder")) { + String reminderString = node.get("reminder").toString(); + Reminder r = Reminder.fromJson(reminderString); + temp = temp.setReminder(r); + } + + Set tagsSet = new HashSet<>(); + JsonNode tags = node.get("tags"); + Iterator it = tags.elements(); + while (it.hasNext()) { + tagsSet.add(new Tag(it.next().get("tagName").asText())); + } + + return temp.setTags(tagsSet).build(); + } + + public Item deepCopy() throws IOException { + return Item.fromJson(this.toJson()); + } + +} diff --git a/src/main/java/seedu/elisa/commons/core/item/ItemDescription.java b/src/main/java/seedu/elisa/commons/core/item/ItemDescription.java new file mode 100644 index 00000000000..f36392cd940 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/ItemDescription.java @@ -0,0 +1,73 @@ +package seedu.elisa.commons.core.item; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.commons.util.AppUtil.checkArgument; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.util.JsonUtil; + +/** + * Represents an Item's description in ELISA. + * Guarantees: immutable; is valid as declared in {@link #isValidItemDescription(String)} + */ +public class ItemDescription { + public static final String MESSAGE_CONSTRAINTS = + "ItemDescription should only contain characters and spaces, and it should not be blank"; + + private final String description; + + /** + * Constructs an {@code ItemDescription}. + * + * @param description A valid item description. + */ + public ItemDescription(String description) { + requireNonNull(description); + checkArgument(isValidItemDescription(description), MESSAGE_CONSTRAINTS); + this.description = description; + } + + public String getDescription() { + return description; + } + + /** + * Returns true if a given string is a valid item description. + */ + public static boolean isValidItemDescription(String test) { + return test.trim().length() > 0; + } + + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ItemDescription // instanceof handles nulls + && getDescription().equals(((ItemDescription) other).getDescription())); // state check + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + /** + * Creates an item description object from a JSON string. + * @param jsonString the JSON string that represents the item description + * @return the item description object that is created + * @throws IOException when the jsonString is not in JSON format + */ + public static ItemDescription fromJson(String jsonString) throws IOException { + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + + return new ItemDescription(node.get("description").asText()); + } +} diff --git a/src/main/java/seedu/elisa/commons/core/item/ItemReminderDateTimeComparator.java b/src/main/java/seedu/elisa/commons/core/item/ItemReminderDateTimeComparator.java new file mode 100644 index 00000000000..a6a06e02f04 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/ItemReminderDateTimeComparator.java @@ -0,0 +1,22 @@ +package seedu.elisa.commons.core.item; + +import java.util.Comparator; + +/** + * A comparator to compare the datetime between the Reminders of two Items. + */ +public class ItemReminderDateTimeComparator implements Comparator { + @Override + public int compare(Item firstItem, Item secondItem) { + int comparisonResult = 0; + if (firstItem.getReminder().isEmpty()) { + comparisonResult = -1; + } else if (secondItem.getReminder().isEmpty()) { + comparisonResult = 1; + } else { + comparisonResult = firstItem.getReminder().get().getOccurrenceDateTime().compareTo( + secondItem.getReminder().get().getOccurrenceDateTime()); + } + return comparisonResult; + } +} diff --git a/src/main/java/seedu/elisa/commons/core/item/Priority.java b/src/main/java/seedu/elisa/commons/core/item/Priority.java new file mode 100644 index 00000000000..1c2e4745897 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/Priority.java @@ -0,0 +1,16 @@ +package seedu.elisa.commons.core.item; + +/** + * Available priority levels for tasks and events. + */ +public enum Priority { + HIGH, MEDIUM, LOW; + /** + * Creates a Priority object from a string. + * @param priorityString the string that represents the Priority + * @return the Priority object that is created + */ + public static Priority fromJson(String priorityString) { + return valueOf(priorityString.toUpperCase()); + } +} diff --git a/src/main/java/seedu/elisa/commons/core/item/Reminder.java b/src/main/java/seedu/elisa/commons/core/item/Reminder.java new file mode 100644 index 00000000000..63598370fbe --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/Reminder.java @@ -0,0 +1,129 @@ +package seedu.elisa.commons.core.item; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.commons.util.CollectionUtil.requireAllNonNull; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.util.JsonUtil; + + +/** + * Represents an Item's Reminder in ELISA. + * Guarantees: immutable; + */ +public class Reminder { + + private final LocalDateTime defaultDateTime; + private final LocalDateTime occurrenceDateTime; + + /** + * Constructs a {@code Reminder}. + * + * @param defaultDateTime A valid LocalDateTime object. + */ + public Reminder(LocalDateTime defaultDateTime) { + requireNonNull(defaultDateTime); + this.defaultDateTime = defaultDateTime; + occurrenceDateTime = defaultDateTime; + } + + /** + * Constructs a {@code Reminder}. + * + * @param defaultDateTime A valid LocalDateTime object that stores the original DateTime is intended to occur. + * @param occurrenceDateTime A valid LocalDateTime object for the reminder to occur. + */ + private Reminder(LocalDateTime defaultDateTime, LocalDateTime occurrenceDateTime) { + requireAllNonNull(defaultDateTime, occurrenceDateTime); + this.defaultDateTime = defaultDateTime; + this.occurrenceDateTime = occurrenceDateTime; + } + + public LocalDateTime getDefaultDateTime() { + return defaultDateTime; + } + + public LocalDateTime getOccurrenceDateTime() { + return occurrenceDateTime; + } + + /** + * Changes the dateTime that the reminder occurs. Removes the previous reminder so it does not occur. + * @param dateTime A LocalDateTime object which dictates the dateTime the reminder occurs. + * @return A new Reminder with the new dateTime for the reminder. + */ + public Reminder changeOccurrenceDateTime(LocalDateTime dateTime) { + //When Reminder is implemented, the previous reminder notification should also be removed here + return new Reminder(defaultDateTime, dateTime); + } + + /** + * Changes the default dateTime of the reminder. + * @param dateTime A LocalDateTime object which dictates the default dateTime of the Reminder. + * @return A new Reminder with the new default dateTime for the reminder. + */ + public Reminder changeDefaultDateTime(LocalDateTime dateTime) { + return new Reminder(dateTime, dateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + if (defaultDateTime.isEqual(occurrenceDateTime)) { + builder.append("\nReminder DateTime: ") + .append(getDefaultDateTime().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm"))); + } else { + builder.append("\nOriginal Reminder DateTime: ") + .append(getDefaultDateTime().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm"))); + + builder.append("\nReminder DateTime: ") + .append(getOccurrenceDateTime().format(DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm"))); + } + + return builder.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Reminder)) { + return false; + } + + Reminder otherReminder = (Reminder) other; + return otherReminder.getOccurrenceDateTime().equals(getOccurrenceDateTime()) + && otherReminder.getDefaultDateTime().equals(getDefaultDateTime()); + } + + //Possibility of high number of hash collisions and as a result slower performance + @Override + public int hashCode() { + return Objects.hash(defaultDateTime, occurrenceDateTime); + } + + /** + * Creates a reminder object from a JSON string. + * @param jsonString the JSON string that represents the reminder + * @return the reminder object that is created + * @throws IOException when the jsonString is not in JSON format + */ + public static Reminder fromJson(String jsonString) throws IOException { + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + String defaultDateTimeString = node.get("defaultDateTime").asText(); + LocalDateTime dateTime = LocalDateTime.parse(defaultDateTimeString); + + String occurrenceDateTimeString = node.get("occurrenceDateTime").asText(); + LocalDateTime occurenceDateTime = LocalDateTime.parse(occurrenceDateTimeString); + + return new Reminder(dateTime, occurenceDateTime); + } +} diff --git a/src/main/java/seedu/elisa/commons/core/item/Task.java b/src/main/java/seedu/elisa/commons/core/item/Task.java new file mode 100644 index 00000000000..c7919883d96 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/core/item/Task.java @@ -0,0 +1,95 @@ +package seedu.elisa.commons.core.item; + +import java.io.IOException; +import java.util.Objects; + +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.commons.util.JsonUtil; + +/** + * Represents an Item's Task in ELISA. + * Completion state of Event is false by default. + * Priority of Event is medium by default. + * Guarantees: immutable; + */ +public class Task { + + private final Boolean complete; + + /** + * Constructs a {@code Task}. + * + * @param complete Denotes whether the task has been completed or not. Defaults to false if null. + */ + public Task(Boolean complete) { + + if (complete != null) { + this.complete = complete; + } else { + this.complete = false; + } + } + + public Boolean isComplete() { + return complete; + } + + public Task markComplete() { + return new Task(true); + } + + public Task markIncomplete() { + return new Task(false); + } + + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + builder.append("\nCompleted: ") + .append(isComplete().toString()); + return builder.toString(); + } + + //Problematic as the details of the task might be the same while the Item/actual task being referred to is not the + //same due to description + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.isComplete().equals(isComplete()); + } + + //hashCode is problematic as I believe that there are only 3*2 permutations of priority and Boolean + //Possibility of high number of hash collisions and as a result slower performance + @Override + public int hashCode() { + return Objects.hash(complete); + } + + /** + * Creates a task object from a JSON string. + * @param jsonString the JSON string that represents the task + * @return the task object that is created + * @throws IOException when the jsonString is not in JSON format + * @throws IllegalValueException when the JSON string contains incorrect value + */ + public static Task fromJson(String jsonString) throws IOException { + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + final Priority p; + final boolean complete; + + complete = node.get("complete").asBoolean(); + return new Task(complete); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/elisa/commons/core/item/tag/Tag.java similarity index 92% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/seedu/elisa/commons/core/item/tag/Tag.java index b0ea7e7dad7..1a047eae180 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/elisa/commons/core/item/tag/Tag.java @@ -1,7 +1,7 @@ -package seedu.address.model.tag; +package seedu.elisa.commons.core.item.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.elisa.commons.util.AppUtil.checkArgument; /** * Represents a Tag in the address book. diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/elisa/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/elisa/commons/exceptions/DataConversionException.java index 1f689bd8e3f..e23d3871614 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/elisa/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.elisa.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/elisa/commons/exceptions/DuplicateItemException.java b/src/main/java/seedu/elisa/commons/exceptions/DuplicateItemException.java new file mode 100644 index 00000000000..c42aa7d0418 --- /dev/null +++ b/src/main/java/seedu/elisa/commons/exceptions/DuplicateItemException.java @@ -0,0 +1,11 @@ +package seedu.elisa.commons.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateItemException extends RuntimeException { + public DuplicateItemException() { + super("Operation would result in duplicate items"); + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/elisa/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/elisa/commons/exceptions/IllegalValueException.java index 19124db485c..b6868c1ba4c 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/elisa/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.elisa.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/elisa/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/elisa/commons/util/AppUtil.java index da90201dfd6..e39e636e608 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/elisa/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.elisa.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/elisa/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/elisa/commons/util/CollectionUtil.java index eafe4dfd681..f0a9b41f91c 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/elisa/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/elisa/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/elisa/commons/util/ConfigUtil.java index f7f8a2bd44c..2bf87a36c99 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/elisa/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.elisa.commons.core.Config; +import seedu.elisa.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/elisa/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/elisa/commons/util/FileUtil.java index b1e2767cdd9..5f28688d1d7 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/elisa/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/elisa/commons/util/JsonUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/elisa/commons/util/JsonUtil.java index 8ef609f055d..8abed98269d 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/elisa/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa @@ -140,4 +140,8 @@ public Class handledType() { } } + public static ObjectMapper getObjectMapper() { + return objectMapper; + } + } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/elisa/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/elisa/commons/util/StringUtil.java index 61cc8c9a1cb..4e6c23bdf21 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/elisa/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.elisa.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.elisa.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/elisa/game/Food.java b/src/main/java/seedu/elisa/game/Food.java new file mode 100644 index 00000000000..61336e260e9 --- /dev/null +++ b/src/main/java/seedu/elisa/game/Food.java @@ -0,0 +1,25 @@ +package seedu.elisa.game; + +import javafx.scene.paint.Color; + +/** + * A class to represent food that takes up only one square. + * + */ +public class Food { + public static final Color COLOR = Color.ROSYBROWN; + + private Point point; + + Food(Point point) { + this.point = point; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } +} diff --git a/src/main/java/seedu/elisa/game/GameLoop.java b/src/main/java/seedu/elisa/game/GameLoop.java new file mode 100644 index 00000000000..a258ad40df0 --- /dev/null +++ b/src/main/java/seedu/elisa/game/GameLoop.java @@ -0,0 +1,105 @@ +package seedu.elisa.game; + +import java.util.TreeSet; +import java.util.logging.Logger; + +import javafx.scene.canvas.GraphicsContext; +import seedu.elisa.commons.core.LogsCenter; + +/** + * The game loop class. + */ +public class GameLoop implements Runnable { + private final Grid grid; + private final GraphicsContext context; + private int currentScore; + private int frameRate; + private float interval; + private boolean running; + private boolean paused; + private boolean keyIsPressed; + + private TreeSet scorelist; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + public GameLoop(final Grid grid, final GraphicsContext context, TreeSet scorelist) { + this.grid = grid; + this.context = context; + frameRate = 20; + interval = 1000.0f / frameRate; // 1000 ms in a second + running = true; + paused = false; + keyIsPressed = false; + currentScore = 0; + this.scorelist = scorelist; + } + + @Override + public void run() { + while (running && !paused) { + // Time the update and paint calls + float time = System.currentTimeMillis(); + + keyIsPressed = false; + grid.update(); + Painter.paint(grid, context); + + if (!grid.getSnake().isSafe()) { + pause(); + currentScore = Painter.getCurrentScore(); + Painter.paintResetMessage(context); + Painter.paintHighScore(context, scorelist.last()); + Painter.resetScore(); + break; + } + + time = System.currentTimeMillis() - time; + + // Adjust the timing correctly + if (time < interval) { + try { + Thread.sleep((long) (interval - time)); + } catch (InterruptedException ignore) { + logger.warning("Error with adding listener to primary stage for popup"); + } + } + } + } + + public int getCurrentScore() { + return currentScore; + } + + public void stop() { + running = false; + } + + public boolean isKeyPressed() { + return keyIsPressed; + } + + public void setKeyPressed() { + keyIsPressed = true; + } + + public void resume() { + paused = false; + } + + public void pause() { + paused = true; + } + + public boolean isPaused() { + return paused; + } + + public int getFrameRate() { + return frameRate; + } + + public void setFrameRate(int frameRate) { + this.frameRate = frameRate; + } +} diff --git a/src/main/java/seedu/elisa/game/Grid.java b/src/main/java/seedu/elisa/game/Grid.java new file mode 100644 index 00000000000..12c02e0502e --- /dev/null +++ b/src/main/java/seedu/elisa/game/Grid.java @@ -0,0 +1,141 @@ +package seedu.elisa.game; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import javafx.scene.paint.Color; + +/** + * Grid is the position for every point. + */ +public class Grid { + /** + * The side length of each square point in the grid. + */ + public static final int SIZE = 10; + public static final Color COLOR = new Color(0.1, 0.1, 0.1, 1); + + private final int cols; + private final int rows; + + private boolean hard; + private Snake snake; + private Food food; + private List walls; + + public Grid(final double width, final double height) { + rows = (int) width / SIZE; + cols = (int) height / SIZE; + + this.hard = false; + + // initialize the snake at the centre of the screen + snake = new Snake(this, new Point(rows / 2, cols / 2)); + + // put the food at a random location + food = new Food(getRandomPoint()); + } + + public Grid(final double width, final double height, boolean hard) { + rows = (int) width / SIZE; + cols = (int) height / SIZE; + + this.hard = hard; + + // initialize the snake at the centre of the screen + snake = new Snake(this, new Point(rows / 2, cols / 2)); + + // put the food at a random location + food = new Food(getRandomPoint()); + + //put the wall at a random location + walls = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + walls.add(new Wall(getRandomPoint())); + } + } + /** + * Ensures the snake does not go beyond the screen. + * @param point + * @return + */ + public Point wrap(Point point) { + int x = point.getX(); + int y = point.getY(); + if (x >= rows) { + x = 0; + } + if (y >= cols) { + y = 0; + } + if (x < 0) { + x = rows - 1; + } + if (y < 0) { + y = cols - 1; + } + return new Point(x, y); + } + + private Point getRandomPoint() { + Random random = new Random(); + Point point; + do { + point = new Point(random.nextInt(rows), random.nextInt(cols)); + } while (point.equals(snake.getHead())); + return point; + } + + /** + * This method is called in every cycle of execution. + */ + public void update() { + if (food.getPoint().equals(snake.getHead())) { + snake.extend(); + food.setPoint(getRandomPoint()); + } else { + if (hard) { + for (Wall w : walls) { + if (w.getPoint().equals(snake.getHead())) { + snake.markAsUnsafe(); + return; + } + } + } + snake.move(); + } + } + + public int getCols() { + return cols; + } + + public int getRows() { + return rows; + } + + public double getWidth() { + return rows * SIZE; + } + + public double getHeight() { + return cols * SIZE; + } + + public Snake getSnake() { + return snake; + } + + public Food getFood() { + return food; + } + + public List getWalls() { + return walls; + } + + public boolean isHard() { + return hard; + } +} diff --git a/src/main/java/seedu/elisa/game/Painter.java b/src/main/java/seedu/elisa/game/Painter.java new file mode 100644 index 00000000000..11f4af1e381 --- /dev/null +++ b/src/main/java/seedu/elisa/game/Painter.java @@ -0,0 +1,93 @@ +package seedu.elisa.game; + +import static seedu.elisa.game.Grid.SIZE; + +import java.util.List; + +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.paint.Color; + + +/** + * This is the class that colors the snake. + */ +public class Painter { + + private static Snake snake; + + /** + * The method to paint the snake. + * @param grid + * @param gc + */ + public static void paint(Grid grid, GraphicsContext gc) { + gc.setFill(Grid.COLOR); + gc.fillRect(0, 0, grid.getWidth(), grid.getHeight()); + + // Now the Food + gc.setFill(Food.COLOR); + paintPoint(grid.getFood().getPoint(), gc); + + // Now the Wall + if (grid.isHard()) { + List walls = grid.getWalls(); + for (Wall w : walls) { + gc.setFill(Wall.COLOR); + paintPoint(w.getPoint(), gc); + } + } + + // Now the snake + snake = grid.getSnake(); + gc.setFill(Snake.COLOR); + snake.getPoints().forEach(point -> paintPoint(point, gc)); + if (!snake.isSafe()) { + gc.setFill(Snake.DEAD); + paintPoint(snake.getHead(), gc); + } + + // The score + snake.setCurrentScore(100 * (snake.getPoints().size() - 1)); + gc.setFill(Color.BEIGE); + gc.fillText("Current score : " + snake.getCurrentScore(), 10, 470); + } + + /** + * Paints the point. + * @param point + * @param gc + */ + private static void paintPoint(Point point, GraphicsContext gc) { + gc.fillRect(point.getX() * SIZE, point.getY() * SIZE, SIZE, SIZE); + } + + /** + * Paints the grid that the snake collides with itself. + * @param gc + */ + public static void paintResetMessage(GraphicsContext gc) { + gc.setFill(Color.AQUAMARINE); + gc.fillText("\nHit E for (E)asy Mode. \nHit H for (H)ard Mode. \nHit ESC to get back to WORK!!!", 10, 10); + } + + public static int getCurrentScore() { + return snake.getCurrentScore(); + } + + public static void resetScore() { + snake.setCurrentScore(0); + } + + /** + * Display high score. + * @param gc + * @param score + */ + public static void paintHighScore(GraphicsContext gc, int score) { + gc.setFill(Color.BEIGE); + if (getCurrentScore() > score) { + score = getCurrentScore(); + } + gc.fillText("High score: " + String.valueOf(score), 10, 490); + } +} diff --git a/src/main/java/seedu/elisa/game/Point.java b/src/main/java/seedu/elisa/game/Point.java new file mode 100644 index 00000000000..8cf194e264c --- /dev/null +++ b/src/main/java/seedu/elisa/game/Point.java @@ -0,0 +1,49 @@ +package seedu.elisa.game; + +/** + * This represents a point on the grid. + */ +public class Point { + private final int x; + private final int y; + + Point(final int x, final int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + /** + * @param dx The change in x. + * @param dy The change in y. + * @return A new Point which is the result of translation of this point. + */ + public Point translate(int dx, int dy) { + return new Point(x + dx, y + dy); + } + + /** + * @param other The "other" point to compare against. + * @return {@code true} if the other Object is an instance of Point and + * has the same coordinates. + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof Point)) { + return false; + } + Point point = (Point) other; + return x == point.x & y == point.y; + } + + public String toString() { + return x + ", " + y; + } +} diff --git a/src/main/java/seedu/elisa/game/Snake.java b/src/main/java/seedu/elisa/game/Snake.java new file mode 100644 index 00000000000..b353e4dd631 --- /dev/null +++ b/src/main/java/seedu/elisa/game/Snake.java @@ -0,0 +1,166 @@ +package seedu.elisa.game; + +import java.util.LinkedList; +import java.util.List; + +import javafx.scene.paint.Color; + +/** + * Main logic class. Stores game's current state. + * + */ +public class Snake { + public static final Color COLOR = Color.CORNSILK; + public static final Color DEAD = Color.RED; + private Grid grid; + private int length; + private boolean safe; + private List points; + private Point head; + private int currentScore; + private int xVelocity; + private int yVelocity; + + /** + * The constructor the snake. It takes the initial point, for the head and the Grid + * that it lives (and dies) in. + * + * @param initialPoint The {@link Point} to the put the snake's head on. + */ + public Snake(Grid grid, Point initialPoint) { + length = 1; + points = new LinkedList<>(); + points.add(initialPoint); + head = initialPoint; + safe = true; + this.grid = grid; + xVelocity = 0; + yVelocity = 0; + currentScore = 0; + } + + public int getCurrentScore() { + return currentScore; + } + + /** + * This method is called after food has been consumed. It increases the length of the + * snake by one. + * + * @param point The Point where the food was and the new location for the head. + */ + private void growTo(Point point) { + length++; + checkAndAdd(point); + } + + /** + * Called during every update. It gets rid of the oldest point and adds the given point. + * + * @param point The new Point to add. + */ + private void shiftTo(Point point) { + // The head goes to the new location + checkAndAdd(point); + // The last/oldest position is dropped + points.remove(0); + } + + /** + * Checks for an intersection and marks the "safe" flag accordingly. + * + * @param point The new Point to move to. + */ + private void checkAndAdd(Point point) { + point = grid.wrap(point); + safe &= !points.contains(point); + points.add(point); + head = point; + } + + /** + * @return The points occupied by the snake. + */ + public List getPoints() { + return points; + } + + /** + * @return {@code true} if the Snake hasn't run into itself yet. + */ + public boolean isSafe() { + return safe; + } + + /** + * Marks snake as unsafe + */ + public void markAsUnsafe() { + safe = false; + } + + /** + * @return The location of the head of the Snake. + */ + public Point getHead() { + return head; + } + + private boolean isStill() { + return xVelocity == 0 & yVelocity == 0; + } + + /** + * Make the snake move one square in it's current direction. + */ + public void move() { + if (!isStill()) { + shiftTo(head.translate(xVelocity, yVelocity)); + } + } + + /** + * Make the snake extend/grow to the square where it's headed. + */ + public void extend() { + if (!isStill()) { + growTo(head.translate(xVelocity, yVelocity)); + } + } + + public void setUp() { + if (yVelocity == 1 && length > 1) { + return; + } + xVelocity = 0; + yVelocity = -1; + } + + public void setDown() { + if (yVelocity == -1 && length > 1) { + return; + } + xVelocity = 0; + yVelocity = 1; + } + + public void setLeft() { + if (xVelocity == 1 && length > 1) { + return; + } + xVelocity = -1; + yVelocity = 0; + } + + public void setRight() { + if (xVelocity == -1 && length > 1) { + return; + } + xVelocity = 1; + yVelocity = 0; + } + + public void setCurrentScore(int score) { + this.currentScore = score; + } +} diff --git a/src/main/java/seedu/elisa/game/Wall.java b/src/main/java/seedu/elisa/game/Wall.java new file mode 100644 index 00000000000..cbcd995a1e4 --- /dev/null +++ b/src/main/java/seedu/elisa/game/Wall.java @@ -0,0 +1,25 @@ +package seedu.elisa.game; + +import javafx.scene.paint.Color; + +/** + * A class to represent wall that takes up only one square. + * + */ +public class Wall { + public static final Color COLOR = Color.BLUE; + + private Point point; + + Wall(Point point) { + this.point = point; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } +} diff --git a/src/main/java/seedu/elisa/logic/CheckTaskRunnable.java b/src/main/java/seedu/elisa/logic/CheckTaskRunnable.java new file mode 100644 index 00000000000..cee71349f3c --- /dev/null +++ b/src/main/java/seedu/elisa/logic/CheckTaskRunnable.java @@ -0,0 +1,63 @@ +package seedu.elisa.logic; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.FutureRemindersList; + +/** + * A runnable that checks if the nearest reminder in futureReminders has a datetime that exceeds now. + */ +public class CheckTaskRunnable implements Runnable { + private final Logger logger = LogsCenter.getLogger(CheckTaskRunnable.class); + private ItemModel model; + private ArrayList activeReminders; + private FutureRemindersList futureReminders; + private Reminder reminder; + private Item item; + + public CheckTaskRunnable(ItemModel model) { + this.model = model; + futureReminders = model.getFutureRemindersList(); + + while (futureReminders.size() > 0 + && futureReminders.get(0).getReminder().get().getOccurrenceDateTime().isBefore(LocalDateTime.now())) { + futureReminders.remove(0); + } + + activeReminders = new ArrayList(0); + reminder = null; + } + + /** + * Method that checks if the nearest reminder in futureReminders has a datetime that exceeds current datetime.. + */ + public void run() { + logger.info("----------------[LOGIC MANAGER][" + + "Checking for pending reminders" + "]"); + + if (futureReminders.size() > 0) { + reminder = futureReminders.get(0).getReminder().get(); + while (reminder != null && reminder.getOccurrenceDateTime().isBefore(LocalDateTime.now())) { + logger.info("----------------[LOGIC MANAGER][" + + "Transferring reminder from futureReminders to activeReminders" + "]"); + + item = futureReminders.remove(0); + activeReminders.add(item); + if (futureReminders.size() > 0) { + reminder = futureReminders.get(0).getReminder().get(); + } else { + reminder = null; + } + } + + model.getActiveReminderListProperty().addReminders(activeReminders); + } + activeReminders.clear(); + } +} diff --git a/src/main/java/seedu/elisa/logic/Logic.java b/src/main/java/seedu/elisa/logic/Logic.java new file mode 100644 index 00000000000..454fae0b63f --- /dev/null +++ b/src/main/java/seedu/elisa/logic/Logic.java @@ -0,0 +1,74 @@ +package seedu.elisa.logic; + +import java.nio.file.Path; + +import javafx.beans.property.ListPropertyBase; +import javafx.beans.property.SimpleBooleanProperty; +import seedu.elisa.commons.core.GuiSettings; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.CommandResult; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.ItemStorage; +import seedu.elisa.model.PriorityExitStatus; +import seedu.elisa.model.item.VisualizeList; + +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * + * @param commandText The command as entered by the user. + * @return the result of the command execution. + * @throws CommandException If an error occurs during command execution. + * @throws ParseException If an error occurs during parsing. + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + + /** + * Returns the AddressBook. + * + * @see seedu.elisa.model.Model#getAddressBook() + */ + ItemStorage getItemStorage(); + + /* + /** Returns an unmodifiable view of the filtered list of persons + ObservableList getFilteredPersonList(); + */ + + /** + * Returns the user prefs' address book file path. + */ + Path getAddressBookFilePath(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Set the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + //ObservableList getFilteredPersonList(); + VisualizeList getVisualList(); + + ItemModel getModel(); + //Bryan Reminder + void shutdown(); + + ListPropertyBase getActiveRemindersListProperty(); + + SimpleBooleanProperty getPriorityMode(); + + boolean isSystemToggle(); + + PriorityExitStatus getExitStatus(); + + boolean isFocusMode(); +} diff --git a/src/main/java/seedu/elisa/logic/LogicManager.java b/src/main/java/seedu/elisa/logic/LogicManager.java new file mode 100644 index 00000000000..54c4f6b24fb --- /dev/null +++ b/src/main/java/seedu/elisa/logic/LogicManager.java @@ -0,0 +1,145 @@ +package seedu.elisa.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +import javafx.beans.property.ListPropertyBase; +import javafx.beans.property.SimpleBooleanProperty; +import seedu.elisa.commons.core.GuiSettings; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.commands.CommandResult; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.logic.parser.ElisaParser; +import seedu.elisa.logic.parser.FocusElisaParser; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.AutoRescheduleManager; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.ItemStorage; +import seedu.elisa.model.PriorityExitStatus; +import seedu.elisa.model.item.VisualizeList; +import seedu.elisa.storage.Storage; + +/** + * The main LogicManager of the app. + */ +public class LogicManager implements Logic { + public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final ItemModel model; + private final Storage storage; + private ElisaParser addressBookParser; + private final ElisaParser normalParser; + private final ElisaParser focusParser; + private final ScheduledThreadPoolExecutor checker; + private final AutoRescheduleManager autoRescheduleManager; + + public LogicManager(ItemModel model, Storage storage) { + this.storage = storage; + this.model = model; + normalParser = new ElisaParser(model.getElisaCommandHistory()); + addressBookParser = normalParser; + focusParser = new FocusElisaParser(model.getElisaCommandHistory()); + autoRescheduleManager = AutoRescheduleManager.getInstance(); + autoRescheduleManager.initStorageEvents(model.getEventList(), model); + + Runnable checkTask = new CheckTaskRunnable(model); + checker = new ScheduledThreadPoolExecutor(1); + checker.scheduleAtFixedRate(checkTask, 0, 5, TimeUnit.SECONDS); + + // Changing of parser in focus mode + model.getFocusMode().addListener((observable, oldValue, newValue) -> { + if (newValue) { + addressBookParser = focusParser; + } else { + addressBookParser = normalParser; + } + }); + } + + public final ListPropertyBase getActiveRemindersListProperty() { + return model.getActiveReminderListProperty(); + } + + /** + * Shutdown threads for Reminders, PriorityMode and AutoRescheduleManager + */ + public final void shutdown() { + checker.shutdown(); + autoRescheduleManager.shutdown(); + model.closePriorityModeThread(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + + //Logging + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + //Parse user input from String to a Command + Command command = addressBookParser.parseCommand(commandText); + commandResult = command.execute(model); + + try { + storage.saveItemStorage(model.getItemStorage()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + + model.updateCommandHistory(command); + + return commandResult; + } + + @Override + public ItemStorage getItemStorage() { + return model.getItemStorage(); + } + + @Override + public VisualizeList getVisualList() { + return model.getVisualList(); + } + + @Override + public ItemModel getModel() { + return model; + } + + @Override + public Path getAddressBookFilePath() { + return model.getItemStorageFilePath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } + + public SimpleBooleanProperty getPriorityMode() { + return model.getPriorityMode(); + } + + public boolean isSystemToggle() { + return model.isSystemToggle(); + } + + public PriorityExitStatus getExitStatus() { + return model.getExitStatus(); + } + + public boolean isFocusMode() { + return model.isFocusMode(); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/AddCommand.java b/src/main/java/seedu/elisa/logic/commands/AddCommand.java new file mode 100644 index 00000000000..ce2920f2f19 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/AddCommand.java @@ -0,0 +1,90 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.AutoRescheduleManager; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.RescheduleTask; + +/** + * Add an Item to the item list. + */ +public abstract class AddCommand extends UndoableCommand { + + public static final AutoRescheduleManager AUTO_RESCHEDULE_MANAGER = AutoRescheduleManager.getInstance(); + + public static final String MESSAGE_SUCCESS = "New Item added: %1$s"; + public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists."; + + protected final Item toAdd; + + /** + * Creates an AddCommand to add the specified {@code Item} + */ + public AddCommand(Item item) { + requireNonNull(item); + toAdd = item; + } + + /** + * Executes this AddCommand to add the new item to this model. + * @param model {@code Model} which the command should operate on. + * @return CommandResult of executing this add + * @throws CommandException if item fails to be added to this model + */ + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + + // Check if item already exists, else, add it to the model. + if (model.hasItem(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ITEM); + } else { + model.addItem(toAdd); + } + + if (toAdd.hasAutoReschedule()) { + Event event = toAdd.getEvent().get(); + RescheduleTask task = new RescheduleTask(toAdd, event.getPeriod(), model); + AUTO_RESCHEDULE_MANAGER.add(task); + } + + // Notify Ui to change the view the that of the newly added item. + try { + model.setVisualList(getListView()); + } catch (Exception e) { + // should not enter here as listView given is definitely valid. + } + + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + + return new CommandResult(String.format(getMessageSuccess(), toAdd)); + } + + public abstract String getListView(); + + public abstract String getMessageSuccess(); + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCommand // instanceof handles nulls + && toAdd.equals(((AddCommand) other).toAdd)); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.deleteItem(toAdd); + + if (toAdd.hasAutoReschedule()) { + Event event = toAdd.getEvent().get(); // if autoReschedule is present, item definitely has an event. + RescheduleTask.removeFromAllTasks(toAdd.getEvent().get()); + } + } +} + diff --git a/src/main/java/seedu/elisa/logic/commands/AddEventCommand.java b/src/main/java/seedu/elisa/logic/commands/AddEventCommand.java new file mode 100644 index 00000000000..4b86492f100 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/AddEventCommand.java @@ -0,0 +1,46 @@ +package seedu.elisa.logic.commands; + +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_AUTO_RESCHEDULE; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.elisa.commons.core.item.Item; + +/** + * Adds an Event to the item model. + */ +public class AddEventCommand extends AddCommand { + + public static final String SHOW_EVENT_VIEW = "E"; + public static final String COMMAND_WORD = "event"; + public static final String MESSAGE_SUCCESS = "Oh great, new Event added: %1$s \nDon't forget about it!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an Event to the Event list. \n" + + "Parameters: \n" + + " " + "description \n" + + " " + PREFIX_DATETIME + " Event time \n" + + " " + PREFIX_REMINDER + " Reminder \n" + + " " + PREFIX_PRIORITY + " Priority \n" + + " " + PREFIX_TAG + " Tag \n" + + " " + PREFIX_AUTO_RESCHEDULE + " Period \n"; + + public AddEventCommand(Item item) { + super(item); + } + + @Override + public String getListView() { + return SHOW_EVENT_VIEW; + } + + @Override + public String getMessageSuccess() { + return MESSAGE_SUCCESS; + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/AddReminderCommand.java b/src/main/java/seedu/elisa/logic/commands/AddReminderCommand.java new file mode 100644 index 00000000000..c1cab9419a9 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/AddReminderCommand.java @@ -0,0 +1,44 @@ +package seedu.elisa.logic.commands; + +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.elisa.commons.core.item.Item; + +/** + * Adds a Reminder to the item model. + */ +public class AddReminderCommand extends AddCommand { + + public static final String SHOW_REMINDER_VIEW = "R"; + public static final String COMMAND_WORD = "reminder"; + public static final String MESSAGE_SUCCESS = "Fine, I'll remind you. New Reminder added: %1$s" + + "\nIt's like you need a keeper"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a Reminder to the Reminder list. \n" + + "Parameters: \n" + + " " + "description \n" + + " " + PREFIX_REMINDER + " Reminder time \n" + + " " + PREFIX_PRIORITY + " Priority \n" + + " " + PREFIX_TAG + " Tag \n"; + + + public AddReminderCommand(Item item) { + super(item); + } + + @Override + public String getListView() { + return SHOW_REMINDER_VIEW; + } + + @Override + public String getMessageSuccess() { + return MESSAGE_SUCCESS; + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/AddTaskCommand.java b/src/main/java/seedu/elisa/logic/commands/AddTaskCommand.java new file mode 100644 index 00000000000..038f5931d4d --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/AddTaskCommand.java @@ -0,0 +1,44 @@ +package seedu.elisa.logic.commands; + +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.elisa.commons.core.item.Item; + +/** + * Adds a Task to the item model. + */ +public class AddTaskCommand extends AddCommand { + + public static final String SHOW_TASK_VIEW = "T"; + public static final String COMMAND_WORD = "task"; + public static final String MESSAGE_SUCCESS = "New Task added: %1$s\nDon't just watch it pile up!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a Task to the Task List. \n" + + "Parameters: \n" + + " " + "description \n" + + " " + PREFIX_DATETIME + " Deadline \n" + + " " + PREFIX_REMINDER + " Reminder \n" + + " " + PREFIX_PRIORITY + " Priority \n" + + " " + PREFIX_TAG + " Tag \n"; + + public AddTaskCommand(Item item) { + super(item); + } + + @Override + public String getListView() { + return SHOW_TASK_VIEW; + } + + @Override + public String getMessageSuccess() { + return MESSAGE_SUCCESS; + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ClearCommand.java b/src/main/java/seedu/elisa/logic/commands/ClearCommand.java new file mode 100644 index 00000000000..2cbdd2ba8fb --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ClearCommand.java @@ -0,0 +1,52 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.ItemStorage; + +/** + * Clears the address book. + */ +public class ClearCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Item list has been cleared! (About time, isn't it?)"; + private ItemStorage beforeClear; + + + @Override + public CommandResult execute(ItemModel model) { + requireNonNull(model); + beforeClear = model.getItemStorage().deepCopy(); + model.clear(); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.setItemStorage(beforeClear); + model.repopulateLists(); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ClearCommand) { + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ClearScreenCommand.java b/src/main/java/seedu/elisa/logic/commands/ClearScreenCommand.java new file mode 100644 index 00000000000..dc495133e03 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ClearScreenCommand.java @@ -0,0 +1,18 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Clears the chat box. + */ +public class ClearScreenCommand extends Command { + + public static final String COMMAND_WORD = "clearscreen"; + public static final String MESSAGE_SUCCESS = "Commands has been cleared! (About time, isn't it?)"; + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + return new ClearScreenCommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ClearScreenCommandResult.java b/src/main/java/seedu/elisa/logic/commands/ClearScreenCommandResult.java new file mode 100644 index 00000000000..26b7c76de1d --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ClearScreenCommandResult.java @@ -0,0 +1,10 @@ +package seedu.elisa.logic.commands; + +/** + * Command Result class for Clear Screen Command. + */ +public class ClearScreenCommandResult extends CommandResult { + public ClearScreenCommandResult(String feedbackToUser) { + super(feedbackToUser); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/CloseCommand.java b/src/main/java/seedu/elisa/logic/commands/CloseCommand.java new file mode 100644 index 00000000000..dcc8acd7e10 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/CloseCommand.java @@ -0,0 +1,24 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.model.ItemModel; + +/** + * Creates a command to close the expanded view of an item. + */ +public class CloseCommand extends Command { + public static final String COMMAND_WORD = "close"; + public static final String MESSAGE_SUCCESS = "I've closed it"; + public static final String MESSAGE_FAILURE = "There's nothing to close"; + + /** + * Carries out the operations of this close command on the given model. + * @param model {@code Model} which the command should operate on. + * @return the result of executing this command. + */ + public CloseCommandResult execute(ItemModel model) { + requireNonNull(model); + return new CloseCommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/CloseCommandResult.java b/src/main/java/seedu/elisa/logic/commands/CloseCommandResult.java new file mode 100644 index 00000000000..d1078c16aa9 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/CloseCommandResult.java @@ -0,0 +1,10 @@ +package seedu.elisa.logic.commands; + +/** + * Creates the command result of CloseCommand. + */ +public class CloseCommandResult extends CommandResult { + public CloseCommandResult(String feedbackToUser) { + super(feedbackToUser); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/elisa/logic/commands/Command.java similarity index 66% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/elisa/logic/commands/Command.java index 64f18992160..11691155724 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/elisa/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package seedu.elisa.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -15,6 +15,6 @@ public abstract class Command { * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model) throws CommandException; + public abstract CommandResult execute(ItemModel model) throws CommandException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/elisa/logic/commands/CommandResult.java similarity index 76% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/elisa/logic/commands/CommandResult.java index 92f900b7916..5bc47afb012 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/elisa/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.elisa.logic.commands; import static java.util.Objects.requireNonNull; @@ -8,14 +8,20 @@ * Represents the result of a command execution. */ public class CommandResult { + protected String pane = ""; + protected String theme = ""; - private final String feedbackToUser; + protected final String feedbackToUser; - /** Help information should be shown to the user. */ - private final boolean showHelp; + /** + * Help information should be shown to the user. + */ + protected final boolean showHelp; - /** The application should exit. */ - private final boolean exit; + /** + * The application should exit. + */ + protected final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. @@ -68,4 +74,12 @@ public int hashCode() { return Objects.hash(feedbackToUser, showHelp, exit); } + public String getPane() { + return this.pane; + } + + public String getTheme() { + return this.theme; + } + } diff --git a/src/main/java/seedu/elisa/logic/commands/ContinueCommand.java b/src/main/java/seedu/elisa/logic/commands/ContinueCommand.java new file mode 100644 index 00000000000..b63942f579f --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ContinueCommand.java @@ -0,0 +1,75 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.exceptions.IllegalListException; +import seedu.elisa.model.item.VisualizeList; + +/** + * Mark a task as not done using it's index in ELISA. + */ +public class ContinueCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "cont"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark the item identified by the index number used in the displayed item list as not done.\n" + + "Can only be used on task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_COMPLETE_ITEM_SUCCESS = "Oops... More work I guess. \nItem: %1$s"; + + private final Index targetIndex; + private Item oldItem; + private Item itemNotDone; + + public ContinueCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + VisualizeList lastShownList = model.getVisualList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + try { + oldItem = model.getItem(targetIndex.getZeroBased()); + itemNotDone = model.markComplete(targetIndex.getZeroBased(), false); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_COMPLETE_ITEM_SUCCESS, itemNotDone)); + } catch (IllegalListException e) { + throw new CommandException("Continue can only be done on the task list."); + } + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.replaceItem(itemNotDone, oldItem); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ContinueCommand // instanceof handles nulls + && targetIndex.equals(((ContinueCommand) other).targetIndex)); // state check + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} + diff --git a/src/main/java/seedu/elisa/logic/commands/DeleteCommand.java b/src/main/java/seedu/elisa/logic/commands/DeleteCommand.java new file mode 100644 index 00000000000..7bdc33134e1 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/DeleteCommand.java @@ -0,0 +1,81 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.AutoRescheduleManager; +import seedu.elisa.model.ItemIndexWrapper; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.RescheduleTask; +import seedu.elisa.model.item.VisualizeList; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class DeleteCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the item identified by the index number used in the displayed item list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_ITEM_SUCCESS = "Finally! Deleted Item: %1$s"; + + private final Index targetIndex; + private ItemIndexWrapper deleted; + + public DeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + VisualizeList lastShownList = model.getVisualList(); + + if (targetIndex.getZeroBased() >= lastShownList.size() || targetIndex.getOneBased() == 0) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + deleted = model.getIndices(targetIndex.getZeroBased()); + Item itemDeleted = model.deleteItem(targetIndex.getZeroBased()); + + if (itemDeleted.hasAutoReschedule()) { // also ensures that itemDeleted has an Event. + RescheduleTask.removeFromAllTasks(itemDeleted.getEvent().get()); + } + + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_DELETE_ITEM_SUCCESS, itemDeleted)); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.addItem(deleted); + + // if deleted item is autoReschedulable, add item back to thread with an updated DateTime. + if (deleted.getItem().hasAutoReschedule()) { + Item item = deleted.getItem(); + AutoRescheduleManager.updateEvent(item, model); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/DoneCommand.java b/src/main/java/seedu/elisa/logic/commands/DoneCommand.java new file mode 100644 index 00000000000..bd341b4a770 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/DoneCommand.java @@ -0,0 +1,75 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.exceptions.IllegalListException; +import seedu.elisa.model.item.VisualizeList; + +/** + * Mark a task as done using it's index in ELISA. + */ +public class DoneCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "done"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark the item identified by the index number used in the displayed item list as done.\n" + + "Can only be used on task list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_COMPLETE_ITEM_SUCCESS = "Finally! Completed Item: %1$s"; + + private final Index targetIndex; + private Item oldItem; + private Item itemDone; + + public DoneCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + VisualizeList lastShownList = model.getVisualList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + try { + oldItem = model.getItem(targetIndex.getZeroBased()); + itemDone = model.markComplete(targetIndex.getZeroBased(), true); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_COMPLETE_ITEM_SUCCESS, itemDone)); + } catch (IllegalListException e) { + throw new CommandException("Done can only be done on the task list."); + } + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.replaceItem(itemDone, oldItem); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DoneCommand // instanceof handles nulls + && targetIndex.equals(((DoneCommand) other).targetIndex)); // state check + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} + diff --git a/src/main/java/seedu/elisa/logic/commands/DownCommand.java b/src/main/java/seedu/elisa/logic/commands/DownCommand.java new file mode 100644 index 00000000000..8f0b810ff61 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/DownCommand.java @@ -0,0 +1,30 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Command for scrolling down. + */ +public class DownCommand extends ScrollCommand { + + public static final String COMMAND_WORD = "down"; + public static final String MESSAGE_SUCCESS = "Scrolling down..."; + public static final String MESSAGE_USAGE = "down"; + + public DownCommand() { + super(); + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + if (pane.equals("Illegal")) { + throw new CommandException(MESSAGE_USAGE); + } + return new DownCommandResult(MESSAGE_SUCCESS, pane); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/DownCommandResult.java b/src/main/java/seedu/elisa/logic/commands/DownCommandResult.java new file mode 100644 index 00000000000..c2040bb59e3 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/DownCommandResult.java @@ -0,0 +1,18 @@ +package seedu.elisa.logic.commands; + +/** + * A class to identify when a command result is a down command. + */ +public class DownCommandResult extends CommandResult { + + private String pane; + + public DownCommandResult(String feedbackToUser, String pane) { + super(feedbackToUser); + this.pane = pane; + } + + public String getPane() { + return this.pane; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/EditCommand.java b/src/main/java/seedu/elisa/logic/commands/EditCommand.java new file mode 100644 index 00000000000..a930511523d --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/EditCommand.java @@ -0,0 +1,328 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Item.ItemBuilder; +import seedu.elisa.commons.core.item.ItemDescription; +import seedu.elisa.commons.core.item.Priority; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.Task; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.commons.util.CollectionUtil; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.AutoRescheduleManager; +import seedu.elisa.model.AutoReschedulePeriod; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.RescheduleTask; +import seedu.elisa.model.item.VisualizeList; + +/** + * Edits the details of an existing item in the item list. + */ +public class EditCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the item in the shown list " + + "by the index number used in the displayed list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: \n" + + "INDEX (must be a positive integer) \n" + + "[" + PREFIX_DESCRIPTION + " DESCRIPTION] \n" + + "[" + PREFIX_REMINDER + " REMINDER] \n" + + "[" + PREFIX_PRIORITY + " PRIORITY] \n" + + "[" + PREFIX_TAG + " TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DESCRIPTION + " DRINK WATER " + + PREFIX_PRIORITY + " HIGH"; + + public static final String MESSAGE_EDIT_ITEM_SUCCESS = "Edited Item: %1$s," + + " because someone couldn't make up their mind"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists in the item list."; + + private final Index index; + private final EditItemDescriptor editItemDescriptor; + + private Item oldItem; + private Item editedItem; + + /** + * @param index of the person in the filtered person list to edit + * @param editItemDescriptor details to edit the person with + */ + public EditCommand(Index index, EditItemDescriptor editItemDescriptor) { + requireNonNull(index); + requireNonNull(editItemDescriptor); + + this.index = index; + this.editItemDescriptor = new EditItemDescriptor(editItemDescriptor); + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + VisualizeList lastShownList = model.getVisualList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + Item oldItem = lastShownList.get(index.getZeroBased()); + this.oldItem = oldItem; + Item editedItem = createEditedItem(oldItem, editItemDescriptor, lastShownList); + this.editedItem = editedItem; + + if (model.hasItem(editedItem)) { + throw new CommandException("Edit failed! Don't you remember that this item already exists?"); + } + // if event has AutoReschedule, add it to the AutoRescheduleManager + if (editedItem.hasAutoReschedule()) { + Event event = editedItem.getEvent().get(); + RescheduleTask task = new RescheduleTask(editedItem, event.getPeriod(), model); + AutoRescheduleManager.getInstance().add(task); + } + + model.editItem(oldItem, editedItem); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_EDIT_ITEM_SUCCESS, editedItem)); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.editItem(editedItem, oldItem); + } + + /** + * Create a new Item with the new edited details. + * + * @param itemToEdit old item to edit + * @param editItemDescriptor details to edit the item with + * @param lastShownList the last list shown to the user, set by the model + * @return a new Item with the edited details + * @throws CommandException representing failure to create new edited item + */ + private static Item createEditedItem(Item itemToEdit, EditItemDescriptor editItemDescriptor, + VisualizeList lastShownList) throws CommandException { + assert itemToEdit != null; + + ItemDescription updatedDescription = editItemDescriptor + .getDescription() + .orElse(itemToEdit.getItemDescription()); + Optional updatedTask = Optional.ofNullable(editItemDescriptor + .getTask() + .orElse(itemToEdit + .getTask() + .orElse(null))); + Optional updatedEvent = Optional.ofNullable(editItemDescriptor + .getEvent() + .orElse(itemToEdit + .getEvent() + .orElse(null))); + Optional updatedReminder = Optional.ofNullable(editItemDescriptor + .getReminder() + .orElse(itemToEdit + .getReminder() + .orElse(null))); + Set updatedTags = editItemDescriptor.getTags().orElse(itemToEdit.getTags()); + Priority updatedPriority = editItemDescriptor.getPriority().orElse(itemToEdit.getPriority()); + Optional updatedPeriod = Optional.ofNullable(editItemDescriptor + .getAutoReschedulePeriod() + .orElse(itemToEdit.hasEvent() + ? itemToEdit.getEvent().get().getPeriod() : null)); + + ItemBuilder itemBuilder = new ItemBuilder(); + itemBuilder.setItemDescription(updatedDescription); + itemBuilder.setTags(updatedTags); + itemBuilder.setItemPriority(updatedPriority); + + if (updatedTask.isPresent()) { + itemBuilder.setTask(updatedTask.get()); + } + if (updatedEvent.isPresent()) { + if (updatedPeriod.isPresent()) { + updatedEvent = Optional.of(updatedEvent.get().setReschedulePeriod(updatedPeriod.get())); + } + itemBuilder.setEvent(updatedEvent.get()); + } + if (updatedReminder.isPresent()) { + itemBuilder.setReminder(updatedReminder.get()); + } + + if (editItemDescriptor.getHasDeleteTask()) { + itemBuilder.setTask(null); + } + if (editItemDescriptor.getHasDeleteEvent()) { + itemBuilder.setEvent(null); + } + if (editItemDescriptor.getHasDeleteReminder()) { + itemBuilder.setReminder(null); + } + + Item updatedItem; + try { + updatedItem = itemBuilder.build(); + } catch (IllegalArgumentException e) { + throw new CommandException(e.getMessage()); + } + + return updatedItem; + } + + /** + * Create a new EditItemDescriptor that edits the details of an item. + */ + public static class EditItemDescriptor { + private ItemDescription description; + private Task task; + private Event event; + private Reminder reminder; + private Priority priority; + private Set tags; + private AutoReschedulePeriod period; + private boolean hasDeleteTask = false; + private boolean hasDeleteEvent = false; + private boolean hasDeleteReminder = false; + + public EditItemDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditItemDescriptor(EditItemDescriptor toCopy) { + setDescription(toCopy.description); + setTask(toCopy.task); + setEvent(toCopy.event); + setReminder(toCopy.reminder); + setPriority(toCopy.priority); + setTags(toCopy.tags); + setAutoReschedulePeriod(toCopy.period); + setHasDeleteTask(toCopy.hasDeleteTask); + setHasDeleteEvent(toCopy.hasDeleteEvent); + setHasDeleteReminder(toCopy.hasDeleteReminder); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, task, event, reminder, priority, tags, period); + } + + public void setDescription(ItemDescription description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setTask(Task task) { + this.task = task; + } + + public Optional getTask() { + return Optional.ofNullable(task); + } + + public void setEvent(Event event) { + this.event = event; + } + + public Optional getEvent() { + return Optional.ofNullable(event); + } + + public void setReminder(Reminder reminder) { + this.reminder = reminder; + } + + public Optional getReminder() { + return Optional.ofNullable(reminder); + } + + public void setPriority(Priority priority) { + this.priority = priority; + } + + public Optional getPriority() { + return Optional.ofNullable(priority); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + public void setAutoReschedulePeriod(AutoReschedulePeriod period) { + this.period = period; + } + + public Optional getAutoReschedulePeriod() { + return Optional.ofNullable(period); + } + + public boolean hasAnyDelete() { + return (hasDeleteEvent || hasDeleteReminder || hasDeleteTask); + } + + public void setHasDeleteTask(boolean bool) { + this.hasDeleteTask = bool; + } + + public boolean getHasDeleteTask() { + return this.hasDeleteTask; + } + + public void setHasDeleteEvent(boolean bool) { + this.hasDeleteEvent = bool; + } + + public boolean getHasDeleteEvent() { + return this.hasDeleteEvent; + } + + public void setHasDeleteReminder(boolean bool) { + this.hasDeleteReminder = bool; + } + + public boolean getHasDeleteReminder() { + return this.hasDeleteReminder; + } + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/elisa/logic/commands/ExitCommand.java similarity index 65% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/elisa/logic/commands/ExitCommand.java index 3dd85a8ba90..7c76e473f0a 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/elisa/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.elisa.logic.commands; -import seedu.address.model.Model; +import seedu.elisa.model.ItemModel; /** * Terminates the program. @@ -9,10 +9,10 @@ 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 = "ELISee you again!"; @Override - public CommandResult execute(Model model) { + public CommandResult execute(ItemModel model) { return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); } diff --git a/src/main/java/seedu/elisa/logic/commands/FindCommand.java b/src/main/java/seedu/elisa/logic/commands/FindCommand.java new file mode 100644 index 00000000000..0175410fbb9 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/FindCommand.java @@ -0,0 +1,61 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.VisualizeList; + + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final String[] searchString; + + private VisualizeList beforeFilter; + + public FindCommand(String[] searchString) { + this.searchString = searchString; + } + + @Override + public CommandResult execute(ItemModel model) { + requireNonNull(model); + beforeFilter = model.getVisualList().deepCopy(); + model.findItem(searchString); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult( + String.format(Messages.MESSAGE_ITEM_LISTED_OVERVIEW, model.getVisualList().size())); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.setVisualizeList(beforeFilter); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && searchString.equals(((FindCommand) other).searchString)); // state check + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/GameCommand.java b/src/main/java/seedu/elisa/logic/commands/GameCommand.java new file mode 100644 index 00000000000..a8f41cc9929 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/GameCommand.java @@ -0,0 +1,34 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Command class for Game. + */ +public class GameCommand extends Command { + + public static final String COMMAND_WORD = "game"; + public static final String MESSAGE_SUCCESS = "Seems like you are really bored. Lets play a game!"; + public static final String MESSAGE_USAGE = "game H / game E"; + + private String diff; + + public GameCommand(String diff) { + this.diff = diff; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + switch(diff.toLowerCase()) { + case ("hard"): + case ("h"): + return new GameHardCommandResult(MESSAGE_SUCCESS); + case ("easy"): + case ("e"): + return new GameCommandResult(MESSAGE_SUCCESS); + default: + throw new IllegalArgumentException(MESSAGE_USAGE); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/GameCommandResult.java b/src/main/java/seedu/elisa/logic/commands/GameCommandResult.java new file mode 100644 index 00000000000..6495e696e49 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/GameCommandResult.java @@ -0,0 +1,10 @@ +package seedu.elisa.logic.commands; + +/** + * Command Result class for easy mode of Game. + */ +public class GameCommandResult extends CommandResult { + public GameCommandResult(String feedbackToUser) { + super(feedbackToUser); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/GameHardCommandResult.java b/src/main/java/seedu/elisa/logic/commands/GameHardCommandResult.java new file mode 100644 index 00000000000..4ce916a3cef --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/GameHardCommandResult.java @@ -0,0 +1,10 @@ +package seedu.elisa.logic.commands; + +/** + * Command Result class for hard mode of game. + */ +public class GameHardCommandResult extends CommandResult { + public GameHardCommandResult(String feedbackToUser) { + super(feedbackToUser); + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/JokeCommand.java b/src/main/java/seedu/elisa/logic/commands/JokeCommand.java new file mode 100644 index 00000000000..0b01cdfd441 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/JokeCommand.java @@ -0,0 +1,19 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * This commands asks Elisa to give user a joke + * */ + +public class JokeCommand extends Command { + + public static final String COMMAND_WORD = "joke"; + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + return new CommandResult(model.getJoke()); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/OpenCommand.java b/src/main/java/seedu/elisa/logic/commands/OpenCommand.java new file mode 100644 index 00000000000..4cad9a594dd --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/OpenCommand.java @@ -0,0 +1,50 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.VisualizeList; + +/** + * Creates an open command to expand the view of an item. + */ +public class OpenCommand extends Command { + + public static final String COMMAND_WORD = "open"; + public static final String MESSAGE_SUCCESS = "opening up item %d"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Opens up the desired item to view more details.\n" + + "Parameters: INDEX (Must be a positive integer within the current list) \n" + + "Example: " + COMMAND_WORD + " 1"; + + private Index index; + private VisualizeList beforeOpen; + + public OpenCommand(Index index) { + this.index = index; + } + + /** + * Executes this command + * @param model {@code Model} which the command should operate on. + * @return the command result of executing this command + * @throws CommandException if the index given is invalid + */ + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + beforeOpen = model.getVisualList().deepCopy(); // for undo. Undo action is technically close. + VisualizeList lastShownList = model.getVisualList(); //shallow copy just to get item + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + Item toOpen = lastShownList.get(index.getZeroBased()); + return new OpenCommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased()), toOpen); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/OpenCommandResult.java b/src/main/java/seedu/elisa/logic/commands/OpenCommandResult.java new file mode 100644 index 00000000000..e4a056eb176 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/OpenCommandResult.java @@ -0,0 +1,20 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.commons.core.item.Item; + +/** + * Creates a command result for open the view of a given item. + */ +public class OpenCommandResult extends CommandResult { + + private Item item; + + public OpenCommandResult(String feedbackToUser, Item item) { + super(feedbackToUser); + this.item = item; + } + + public Item getItem() { + return this.item; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/PriorityCommand.java b/src/main/java/seedu/elisa/logic/commands/PriorityCommand.java new file mode 100644 index 00000000000..4203bbac29e --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/PriorityCommand.java @@ -0,0 +1,62 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.exceptions.IllegalListException; + +/** + * Toggle the state of ELISA between priority and non-priority mode. + */ +public class PriorityCommand extends UndoableCommand { + public static final String COMMAND_WORD = "priority"; + public static final String MESSAGE_USAGE = "Activates the priority mode of this application.\n" + + "It can be activated indefinitely by using \"priority\" or \"priority 10.min.later\"\n" + + "Include the flag -f or -focus to enter a more focus mode i.e. \"priority -f\""; + public static final String FINISHED_ALL_TASKS = "Congrats! You have finished all your tasks." + + " Taking you out of priority mode now."; + public static final String TIME_OUT = "Oops, guess you are out of time. Hope you have done enough!"; + + private static final String PRIORITY_MODE_OFF = "Priority mode deactivated! Not so stressed anymore, are you?"; + private static final String PRIORITY_MODE_ON = "Priority mode activated, just manage this one task, that'll do."; + private static final String FOCUS_MODE_ON = "Let's focus on this one task!"; + private static final String NO_TASK_TO_DO = "You have no incomplete task. Go out and enjoy the sun."; + private static final String PRIORITY_MODE_ERROR = "Priority mode can only be activated on task pane"; + + private boolean focusMode; + + public PriorityCommand(boolean focusMode) { + this.focusMode = focusMode; + } + + @Override + public CommandResult execute(ItemModel model) { + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + try { + boolean status = model.togglePriorityMode(); + if (focusMode && status) { + model.toggleOnFocusMode(); + } + return new CommandResult(status ? (focusMode ? FOCUS_MODE_ON : PRIORITY_MODE_ON) + : (model.getExitStatus() == null ? PRIORITY_MODE_OFF : NO_TASK_TO_DO)); + } catch (IllegalListException e) { + return new CommandResult(PRIORITY_MODE_ERROR); + } + } + + @Override + public void reverse(ItemModel model) throws CommandException { + try { + model.togglePriorityMode(); + } catch (IllegalListException e) { + e.printStackTrace(); + } + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/RedoCommand.java b/src/main/java/seedu/elisa/logic/commands/RedoCommand.java new file mode 100644 index 00000000000..b411a9e3f49 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/RedoCommand.java @@ -0,0 +1,32 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.commons.core.Messages; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ElisaCommandHistory; +import seedu.elisa.model.ItemModel; + +/** + * Class for redoing a previously undone command + * */ + +public class RedoCommand extends Command { + public static final String COMMAND_WORD = "redo"; + + private ElisaCommandHistory elisaCommandHistory; + + public RedoCommand(ElisaCommandHistory elisaCommandHistory) { + this.elisaCommandHistory = elisaCommandHistory; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + if (elisaCommandHistory.sizeRedo() == 0) { + throw new CommandException(Messages.MESSAGE_NOTHING_TO_REDO); + } else { + UndoableCommand lastDone = elisaCommandHistory.popRedo(); + lastDone.execute(model); + return new CommandResult("Redo [" + lastDone.getCommandWord() + "] command successful!"); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ScheduledPriorityCommand.java b/src/main/java/seedu/elisa/logic/commands/ScheduledPriorityCommand.java new file mode 100644 index 00000000000..016106e41c8 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ScheduledPriorityCommand.java @@ -0,0 +1,32 @@ +package seedu.elisa.logic.commands; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import seedu.elisa.model.ItemModel; + +/** + * Create a priority command that will turn off after a certain time. + */ +public class ScheduledPriorityCommand extends PriorityCommand { + public static final String COMMAND_WORD = "priority"; + private LocalDateTime ldt; + + /** + * Constructor to create the command + * @param ldt the time at which the priority mode will end. + */ + public ScheduledPriorityCommand(LocalDateTime ldt, boolean focusMode) { + super(focusMode); + ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()); + this.ldt = ldt; + } + + @Override + public CommandResult execute(ItemModel model) { + model.scheduleOffPriorityMode(ldt); + return super.execute(model); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/ScrollCommand.java b/src/main/java/seedu/elisa/logic/commands/ScrollCommand.java new file mode 100644 index 00000000000..153b25bdf28 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ScrollCommand.java @@ -0,0 +1,21 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Abstract class for scrolling. + */ +public abstract class ScrollCommand extends Command { + + protected String pane; + + public ScrollCommand() { + this.pane = "tabPane"; + } + @Override + public CommandResult execute(ItemModel model) throws CommandException { + return new CommandResult("Scrolling"); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/ShowCommand.java b/src/main/java/seedu/elisa/logic/commands/ShowCommand.java new file mode 100644 index 00000000000..7cf70223765 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ShowCommand.java @@ -0,0 +1,95 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.VisualizeList; + +/** + * Switches the current view to the desired view. + */ +public class ShowCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "show"; + public static final String TASK_VIEW_COMMAND = "T"; + public static final String EVENT_VIEW_COMMAND = "E"; + public static final String REMINDER_VIEW_COMMAND = "R"; + public static final String CALENDAR_VIEW_COMMAND = "C"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows the desired view.\n" + + "Parameters: KEYWORD (T,E,R,C)\n" + + "Example: " + COMMAND_WORD + " E"; + + public static final String MESSAGE_SUCCESS = "Switched view to %1$s, because somebody couldn't use the mouse"; + + private final String targetView; + private final String targetList; + private VisualizeList beforeSwitch; + + public ShowCommand(String unprocessedView) { + String targetView = unprocessedView.toUpperCase(); + this.targetView = targetView; + + switch(targetView) { + case TASK_VIEW_COMMAND: + this.targetList = TASK_VIEW_COMMAND; //"TASK" + break; + case EVENT_VIEW_COMMAND: + this.targetList = EVENT_VIEW_COMMAND; //"EVENT" + break; + case CALENDAR_VIEW_COMMAND: + this.targetList = CALENDAR_VIEW_COMMAND; //"CALENDAR" + break; + case REMINDER_VIEW_COMMAND: + this.targetList = REMINDER_VIEW_COMMAND; //"REMINDER" + break; + default: + this.targetList = null; + } + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + + beforeSwitch = model.getVisualList().deepCopy(); + try { + model.setVisualList(targetList); // should be T/E/R + } catch (IllegalValueException e) { + throw new CommandException("Show command format is incorrect. It should be \"show T\""); + } + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, targetView)); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.setVisualizeList(beforeSwitch); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof ShowCommand)) { + return false; + } else { + ShowCommand other = (ShowCommand) obj; + return other.targetList.equalsIgnoreCase(targetList) + && other.targetView.equalsIgnoreCase(targetView); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/SnoozeCommand.java b/src/main/java/seedu/elisa/logic/commands/SnoozeCommand.java new file mode 100644 index 00000000000..2e056bbdee7 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/SnoozeCommand.java @@ -0,0 +1,103 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_SNOOZE; + +import java.time.LocalDateTime; +import java.util.NoSuchElementException; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.VisualizeList; + +/** + * Edits the details of an existing item in the item list. + */ +public class SnoozeCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "snooze"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Snoozes the reminder " + + "by the index number used in the reminder list " + + "or the most recent occurred reminder if no index is provided.\n" + + "Parameters: [INDEX] (must be a positive integer) " + + "[" + PREFIX_SNOOZE + " SNOOZE TIME]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_SNOOZE + " 1.min.later"; + + public static final String MESSAGE_SNOOZED_REMINDER_SUCCESS = "Snoozed Reminder: %1$s," + + " because someone is real lazy..."; + + private static final String SHOW_REMINDER_VIEW = "R"; + + private final boolean hasIndex; + private final Index index; + private final LocalDateTime newReminderOccurrence; + + private Item oldItem; + private Item snoozedItem; + + /** + * @param hasIndex boolean that indicates if an index was specified. + * @param index of the item to edit. + * @param newReminderOccurrence LocalDateTime of new occurrence of reminder. + */ + public SnoozeCommand(boolean hasIndex, Index index, LocalDateTime newReminderOccurrence) { + requireAllNonNull(hasIndex, newReminderOccurrence); + + this.hasIndex = hasIndex; + this.index = index; + this.newReminderOccurrence = newReminderOccurrence; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + try { + model.setVisualList(SHOW_REMINDER_VIEW); + } catch (Exception e) { + // should not enter here as itemType is definitely valid. + } + + if (hasIndex) { + VisualizeList lastShownList = model.getVisualList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + oldItem = lastShownList.get(index.getZeroBased()); + snoozedItem = oldItem.changeReminder( + oldItem.getReminder().get().changeOccurrenceDateTime(newReminderOccurrence)); + + } else { + try { + oldItem = model.getLatestOccurredReminder(); + } catch (NoSuchElementException e) { + throw new CommandException(Messages.MESSAGE_NO_PREVIOUS_REMINDER); + } + + snoozedItem = oldItem.changeReminder( + oldItem.getReminder().get().changeOccurrenceDateTime(newReminderOccurrence)); + } + model.editItem(oldItem, snoozedItem); + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_SNOOZED_REMINDER_SUCCESS, snoozedItem)); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.replaceItem(snoozedItem, oldItem); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/SortCommand.java b/src/main/java/seedu/elisa/logic/commands/SortCommand.java new file mode 100644 index 00000000000..5927767d231 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/SortCommand.java @@ -0,0 +1,66 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; +import java.util.Optional; + +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; +import seedu.elisa.model.item.VisualizeList; + +/** + * Sort the current list. + */ +public class SortCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "sort"; + public static final String MESSAGE_SUCCESS = "%s has been sorted!"; + private VisualizeList beforeSort; + private Optional> comparator; + + public SortCommand(Optional> comparator) { + this.comparator = comparator; + } + + @Override + public CommandResult execute(ItemModel model) { + requireNonNull(model); + beforeSort = model.getVisualList().deepCopy(); + if (comparator.isPresent()) { + model.sort(comparator.get()); + } else { + model.sort(); + } + if (!isExecuted()) { + model.getElisaCommandHistory().clearRedo(); + setExecuted(true); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, beforeSort.getClass().getSimpleName())); + } + + @Override + public void reverse(ItemModel model) throws CommandException { + model.setVisualizeList(beforeSort); + } + + @Override + public String getCommandWord() { + return COMMAND_WORD; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof SortCommand)) { + return false; + } else { + SortCommand other = (SortCommand) obj; + return other.comparator.equals(comparator); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ThemeCommand.java b/src/main/java/seedu/elisa/logic/commands/ThemeCommand.java new file mode 100644 index 00000000000..ca3260ae5d3 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ThemeCommand.java @@ -0,0 +1,31 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Changes the theme. + */ +public class ThemeCommand extends Command { + + public static final String COMMAND_WORD = "theme"; + public static final String MESSAGE_INVALID = "Oh dumb dumb, choose only 'theme white' or 'theme black'"; + public static final String MESSAGE_SUCCESS = "Oh you don't like the color? Lets switch it up!"; + + private String theme; + + public ThemeCommand(String theme) { + this.theme = theme; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + switch(theme.trim()) { + case "white": + case "black": + return new ThemeCommandResult(MESSAGE_SUCCESS, theme); + default: + throw new CommandException(MESSAGE_INVALID); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/ThemeCommandResult.java b/src/main/java/seedu/elisa/logic/commands/ThemeCommandResult.java new file mode 100644 index 00000000000..48c9dde17c7 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/ThemeCommandResult.java @@ -0,0 +1,18 @@ +package seedu.elisa.logic.commands; + +/** + * This class identifies when a command is a theme command. + */ +public class ThemeCommandResult extends CommandResult { + + private String theme; + + public ThemeCommandResult(String feedbackToUser, String theme) { + super(feedbackToUser); + this.theme = theme; + } + + public String getTheme() { + return this.theme; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/UndoCommand.java b/src/main/java/seedu/elisa/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..4b09bc577b0 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/UndoCommand.java @@ -0,0 +1,32 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.commons.core.Messages; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ElisaCommandHistory; +import seedu.elisa.model.ItemModel; + +/** + * Undoes last entered command + */ +public class UndoCommand extends Command { + public static final String COMMAND_WORD = "undo"; + + private ElisaCommandHistory elisaCommandHistory; + + public UndoCommand(ElisaCommandHistory elisaCommandHistory) { + this.elisaCommandHistory = elisaCommandHistory; + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + if (elisaCommandHistory.sizeUndo() == 0) { + throw new CommandException(Messages.MESSAGE_NOTHING_TO_UNDO); + } else { + UndoableCommand lastDone = elisaCommandHistory.popUndo(); + lastDone.reverse(model); + return new CommandResult("Undo [" + lastDone.getCommandWord() + "] command successful!" + + " Try to do it right this time.."); + } + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/UndoableCommand.java b/src/main/java/seedu/elisa/logic/commands/UndoableCommand.java new file mode 100644 index 00000000000..050ddb9f3e6 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/UndoableCommand.java @@ -0,0 +1,22 @@ +package seedu.elisa.logic.commands; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Superclass of all commands that can be undone + * */ + +public abstract class UndoableCommand extends Command { + private boolean isExecuted = false; + public abstract void reverse(ItemModel model) throws CommandException; + public abstract String getCommandWord(); + + public boolean isExecuted() { + return isExecuted; + } + + public void setExecuted(boolean executed) { + isExecuted = executed; + } +} diff --git a/src/main/java/seedu/elisa/logic/commands/UpCommand.java b/src/main/java/seedu/elisa/logic/commands/UpCommand.java new file mode 100644 index 00000000000..ca67a6e0ca9 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/UpCommand.java @@ -0,0 +1,30 @@ +package seedu.elisa.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.model.ItemModel; + +/** + * Command for scrolling up. + */ +public class UpCommand extends ScrollCommand { + + public static final String COMMAND_WORD = "up"; + public static final String MESSAGE_SUCCESS = "Scrolling up..."; + public static final String MESSAGE_USAGE = "up"; + + public UpCommand() { + super(); + } + + @Override + public CommandResult execute(ItemModel model) throws CommandException { + requireNonNull(model); + if (pane.equals("Illegal")) { + throw new CommandException(MESSAGE_USAGE); + } + return new UpCommandResult(MESSAGE_SUCCESS, pane); + } + +} diff --git a/src/main/java/seedu/elisa/logic/commands/UpCommandResult.java b/src/main/java/seedu/elisa/logic/commands/UpCommandResult.java new file mode 100644 index 00000000000..a0323337af2 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/commands/UpCommandResult.java @@ -0,0 +1,18 @@ +package seedu.elisa.logic.commands; + +/** + * A class to identify when a command result is an up command. + */ +public class UpCommandResult extends CommandResult { + + private String pane; + + public UpCommandResult(String feedbackToUser, String pane) { + super(feedbackToUser); + this.pane = pane; + } + + public String getPane() { + return this.pane; + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/elisa/logic/commands/exceptions/CommandException.java similarity index 89% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/elisa/logic/commands/exceptions/CommandException.java index a16bd14f2cd..3e1ee9d90c2 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/elisa/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.elisa.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/elisa/logic/parser/AddEventCommandParser.java b/src/main/java/seedu/elisa/logic/parser/AddEventCommandParser.java new file mode 100644 index 00000000000..d7232565c99 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/AddEventCommandParser.java @@ -0,0 +1,109 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_AUTO_RESCHEDULE; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Item.ItemBuilder; +import seedu.elisa.commons.core.item.ItemDescription; +import seedu.elisa.commons.core.item.Priority; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.logic.commands.AddCommand; +import seedu.elisa.logic.commands.AddEventCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.AutoReschedulePeriod; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddEventCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String desc, String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATETIME, PREFIX_REMINDER, PREFIX_PRIORITY, + PREFIX_TAG, PREFIX_AUTO_RESCHEDULE); + + // Event must have a deadline. + if (!arePrefixesPresent(argMultimap, PREFIX_DATETIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddEventCommand.MESSAGE_USAGE)); + } + + ItemDescription description = ParserUtil.parseDescription(desc); + // Event must be present. + Event event = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()).get(); + Optional itemReminder = ParserUtil.parseReminder( + argMultimap.getValue(PREFIX_REMINDER).orElse(null)); + Optional priority = ParserUtil.parsePriority( + argMultimap.getValue(PREFIX_PRIORITY).orElse(null)); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + try { + Optional reschedulePeriod = ParserUtil.parseReschedule( + argMultimap.getValue(PREFIX_AUTO_RESCHEDULE).orElse(null)); + if (reschedulePeriod.isPresent()) { + event = event.setAutoReschedule(true).setReschedulePeriod(reschedulePeriod.get()); + if (event.getStartDateTime().isBefore(LocalDateTime.now())) { + LocalDateTime updatedDateTime = ParserUtil.getUpdatedDateTime(event.getStartDateTime(), + reschedulePeriod.get().getPeriod()); + event = event.changeStartDateTime(updatedDateTime); + } + } + } catch (ParseException pe) { + throw pe; + } catch (Exception e) { + System.out.println("Issue with parsing -auto " + e.getMessage()); + } + + ItemBuilder itemBuilder = new ItemBuilder(); + itemBuilder.setItemDescription(description); + itemBuilder.setTags(tagList); + + if (priority.isPresent()) { + itemBuilder.setItemPriority(priority.get()); + } + itemBuilder.setEvent(event); + + + if (itemReminder.isPresent()) { + itemBuilder.setReminder(itemReminder.get()); + } + + Item newItem; + try { + newItem = itemBuilder.build(); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + + AddCommand addCommand = new AddEventCommand(newItem); + return addCommand; + } + + + /** + * 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/elisa/logic/parser/AddReminderCommandParser.java b/src/main/java/seedu/elisa/logic/parser/AddReminderCommandParser.java new file mode 100644 index 00000000000..38eacc9cc83 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/AddReminderCommandParser.java @@ -0,0 +1,87 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_AUTO_RESCHEDULE; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Item.ItemBuilder; +import seedu.elisa.commons.core.item.ItemDescription; +import seedu.elisa.commons.core.item.Priority; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.logic.commands.AddCommand; +import seedu.elisa.logic.commands.AddReminderCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddReminderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String desc, String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATETIME, PREFIX_REMINDER, PREFIX_PRIORITY, PREFIX_TAG, + PREFIX_AUTO_RESCHEDULE); + + // AutoReschedule cannot be present with reminders + if (argMultimap.getValue(PREFIX_AUTO_RESCHEDULE).isPresent()) { + throw new ParseException("-auto can't be used with reminders!! Use it with events instead!"); + } + + ItemDescription description = ParserUtil.parseDescription(desc); + + // Reminder must be present. + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException("I can't believe you forgot the reminder format again! " + + "The format should follow:\n\"reminder DESCRIPTION -r REMINDER [-t TAG]\""); + } + Reminder itemReminder = ParserUtil.parseReminder(argMultimap.getValue(PREFIX_REMINDER).get()).get(); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + Optional priority = ParserUtil.parsePriority(argMultimap.getValue(PREFIX_PRIORITY) + .orElse(null)); + + ItemBuilder itemBuilder = new ItemBuilder(); + itemBuilder.setItemDescription(description); + itemBuilder.setReminder(itemReminder); + itemBuilder.setTags(tagList); + + if (priority.isPresent()) { + itemBuilder.setItemPriority(priority.get()); + } + + Item newItem; + try { + newItem = itemBuilder.build(); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + + AddCommand addCommand = new AddReminderCommand(newItem); + return addCommand; + } + + + /** + * 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/elisa/logic/parser/AddTaskCommandParser.java b/src/main/java/seedu/elisa/logic/parser/AddTaskCommandParser.java new file mode 100644 index 00000000000..267dc5fb968 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/AddTaskCommandParser.java @@ -0,0 +1,76 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_AUTO_RESCHEDULE; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Optional; +import java.util.Set; + +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Item.ItemBuilder; +import seedu.elisa.commons.core.item.ItemDescription; +import seedu.elisa.commons.core.item.Priority; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.Task; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.logic.commands.AddCommand; +import seedu.elisa.logic.commands.AddTaskCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String desc, String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATETIME, PREFIX_REMINDER, PREFIX_PRIORITY, PREFIX_TAG, + PREFIX_AUTO_RESCHEDULE); + + if (argMultimap.getValue(PREFIX_AUTO_RESCHEDULE).isPresent()) { + throw new ParseException("-auto can't be used with task!! Use it with events instead!"); + } + + ItemDescription description = ParserUtil.parseDescription(desc); + Optional dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).orElse(null)); + Optional itemReminder = ParserUtil.parseReminder(argMultimap.getValue(PREFIX_REMINDER).orElse(null)); + Optional priority = ParserUtil.parsePriority(argMultimap.getValue(PREFIX_PRIORITY).orElse(null)); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + ItemBuilder itemBuilder = new ItemBuilder(); + itemBuilder.setItemDescription(description); + itemBuilder.setTags(tagList); + Task task = new Task(null); + itemBuilder.setTask(task); + + if (priority.isPresent()) { + itemBuilder.setItemPriority(priority.get()); + } + + if (dateTime.isPresent()) { + itemBuilder.setEvent(dateTime.get()); + } + if (itemReminder.isPresent()) { + itemBuilder.setReminder(itemReminder.get()); + } + + Item newItem; + try { + newItem = itemBuilder.build(); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + + AddCommand addCommand = new AddTaskCommand(newItem); + return addCommand; + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/elisa/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/elisa/logic/parser/ArgumentMultimap.java index 954c8e18f8e..8de6da51d10 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/elisa/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.elisa.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/elisa/logic/parser/ArgumentTokenizer.java similarity index 92% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/elisa/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..fe2f521452d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/elisa/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.elisa.logic.parser; import java.util.ArrayList; import java.util.Arrays; @@ -70,7 +70,7 @@ private static List findPrefixPositions(String argsString, Prefi * {@code fromIndex} = 0, this method returns 5. */ private static int findPrefixPosition(String argsString, String prefix, int fromIndex) { - int prefixIndex = argsString.indexOf(" " + prefix, fromIndex); + int prefixIndex = argsString.indexOf(" " + prefix + " ", fromIndex); return prefixIndex == -1 ? -1 : prefixIndex + 1; // +1 as offset for whitespace } @@ -103,7 +103,15 @@ private static ArgumentMultimap extractArguments(String argsString, List { + + /** + * Parses the given {@code String} of arguments in the context of the ContinueCommand + * and returns a ContinueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ContinueCommand parse(String args, String flags) throws ParseException { + // flags should be empty in this case, focus on args only + // if flags is not empty, it means symbol "-" is present + if (!flags.equals(" ")) { + throw new ParseException(MESSAGE_INCORRECT_SYMBOL_USAGE); + } + + // if index is not provided + if (args.equals("")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, " I need an index! \n" + + ContinueCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(args); + return new ContinueCommand(index); + } + +} diff --git a/src/main/java/seedu/elisa/logic/parser/DateTimeParser.java b/src/main/java/seedu/elisa/logic/parser/DateTimeParser.java new file mode 100644 index 00000000000..827b3919e02 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/DateTimeParser.java @@ -0,0 +1,17 @@ +package seedu.elisa.logic.parser; + +import java.time.LocalDateTime; + +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Represents a Parser that is able to parse DateTime input into a {@code LocalDateTime}. + */ +public interface DateTimeParser { + /** + * Parses {@code stringDateTime} into a LocalDateTime and returns it + * @param stringDateTime of the unprocessed date time string + * @return LocalDateTime representation of the given string + */ + LocalDateTime parseDateTime(String stringDateTime) throws ParseException; +} diff --git a/src/main/java/seedu/elisa/logic/parser/DefinedDateTimeParser.java b/src/main/java/seedu/elisa/logic/parser/DefinedDateTimeParser.java new file mode 100644 index 00000000000..f0af712adbf --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/DefinedDateTimeParser.java @@ -0,0 +1,71 @@ +package seedu.elisa.logic.parser; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; +import java.util.Locale; + +import seedu.elisa.logic.parser.exceptions.InvalidDateException; +import seedu.elisa.logic.parser.exceptions.MidnightParseException; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses string date time using a formatter dd/MM/yyyy HHmm + */ +public class DefinedDateTimeParser implements DateTimeParser { + private static Locale sgLocale = new Locale("en", "SG"); + /** + * Processes the string using the given format and returns a LocalDateTime + * @param stringDateTime of the format "dd/MM/yyyy HHmm" + * @return LocalDateTime representation of the string + */ + public LocalDateTime parseDateTime(String stringDateTime) throws ParseException { + try { + String[] splitTime = stringDateTime.split(" "); + + // If time given is 2400, prompts the user for 0000 of the next day instead + if (splitTime[1].equals("2400")) { + throw new MidnightParseException("0000"); + } + + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("d/M/yyyy"); + LocalDate processedDate = LocalDate.parse(splitTime[0], dateFormatter); + + if (!isValid(splitTime[0])) { + throw new InvalidDateException(); + } + + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HHmm"); + LocalTime processedTime = LocalTime.parse(splitTime[1], timeFormatter); + + LocalDateTime processedDateTime = LocalDateTime.of(processedDate, processedTime); + return processedDateTime; + } catch (MidnightParseException me) { + throw me; + } catch (InvalidDateException de) { + throw de; + } catch (Exception e) { + throw new ParseException("Date Time format given is incorrect." + + " Should be \"25/09/2019 2300\""); + } + } + + /** + * Checks that the given date string is a valid date. + * @param dateStr of the input date + * @return true if the date exists, false otherwise + */ + private boolean isValid(String dateStr) { + DateTimeFormatter df = DateTimeFormatter.ofPattern("d/M/uuuu", sgLocale) + .withResolverStyle(ResolverStyle.STRICT); + try { + df.parse(dateStr); + } catch (DateTimeException e) { + return false; + } + return true; + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/elisa/logic/parser/DeleteCommandParser.java new file mode 100644 index 00000000000..88848813c50 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/DeleteCommandParser.java @@ -0,0 +1,36 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INCORRECT_SYMBOL_USAGE; +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.logic.commands.DeleteCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCommand parse(String args, String flags) throws ParseException { + // flags should be empty in this case, focus on args only + // if flags is not empty, it means symbol "-" is present + if (!flags.equals(" ")) { + throw new ParseException(MESSAGE_INCORRECT_SYMBOL_USAGE); + } + + // if index is not provided + if (args.equals("")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, " I need an index! \n" + + DeleteCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(args); + return new DeleteCommand(index); + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/DoneCommandParser.java b/src/main/java/seedu/elisa/logic/parser/DoneCommandParser.java new file mode 100644 index 00000000000..1271809a21a --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/DoneCommandParser.java @@ -0,0 +1,38 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INCORRECT_SYMBOL_USAGE; +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.logic.commands.DoneCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DoneCommand object + */ +public class DoneCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DoneCommand + * and returns a DoneCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DoneCommand parse(String args, String flags) throws ParseException { + // flags should be empty in this case, focus on args only + // if flags is not empty, it means symbol "-" is present + if (!flags.equals(" ")) { + throw new ParseException(MESSAGE_INCORRECT_SYMBOL_USAGE); + } + + // if index is not provided + if (args.equals("")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, " I need an index! \n" + + DoneCommand.MESSAGE_USAGE)); + } + + Index index = ParserUtil.parseIndex(args); + return new DoneCommand(index); + + } + +} diff --git a/src/main/java/seedu/elisa/logic/parser/EditCommandParser.java b/src/main/java/seedu/elisa/logic/parser/EditCommandParser.java new file mode 100644 index 00000000000..31be9953c94 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/EditCommandParser.java @@ -0,0 +1,123 @@ +package seedu.elisa.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_AUTO_RESCHEDULE; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DELETE_EVENT; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DELETE_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DELETE_TASK; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_PRIORITY; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_REMINDER; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.logic.LogicManager; +import seedu.elisa.logic.commands.EditCommand; +import seedu.elisa.logic.commands.EditCommand.EditItemDescriptor; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditCommandParser implements Parser { + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + /** + * Parses the given {@code description} and {@code args} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditCommand parse(String description, String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultiMap = new ArgumentMultimap(); + String processArgs = args + " "; // account for the possibility that --tk or --r or --e is given with no space + try { + argMultiMap = ArgumentTokenizer.tokenize(processArgs, PREFIX_DESCRIPTION, PREFIX_DATETIME, PREFIX_REMINDER, + PREFIX_PRIORITY, PREFIX_TAG, PREFIX_DELETE_TASK, PREFIX_DELETE_REMINDER, PREFIX_DELETE_EVENT, + PREFIX_AUTO_RESCHEDULE); + } catch (Exception e) { + logger.info("Failure to tokenize arguments: EditCommand"); + throw new ParseException("Edit command format is incorrect."); + } + + Index index; + + try { + index = ParserUtil.parseIndex(description); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + } + + EditItemDescriptor editItemDescriptor = new EditItemDescriptor(); + if (argMultiMap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editItemDescriptor.setDescription( + ParserUtil.parseDescription( + argMultiMap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultiMap.getValue(PREFIX_DATETIME).isPresent()) { + editItemDescriptor.setEvent( + ParserUtil.parseDateTime( + argMultiMap.getValue(PREFIX_DATETIME).get()).get()); + } + if (argMultiMap.getValue(PREFIX_REMINDER).isPresent()) { + editItemDescriptor.setReminder( + ParserUtil.parseReminder( + argMultiMap.getValue(PREFIX_REMINDER).get()).get()); + } + if (argMultiMap.getValue(PREFIX_AUTO_RESCHEDULE).isPresent()) { + editItemDescriptor.setAutoReschedulePeriod( + ParserUtil.parseReschedule( + argMultiMap.getValue(PREFIX_AUTO_RESCHEDULE).get()).get()); + } + if (argMultiMap.getValue(PREFIX_PRIORITY).isPresent()) { + editItemDescriptor.setPriority( + ParserUtil.parsePriority( + argMultiMap.getValue(PREFIX_PRIORITY).get()).get()); + } + parseTagsForEdit(argMultiMap.getAllValues(PREFIX_TAG)).ifPresent(editItemDescriptor::setTags); + + // if delete tag is present, even if edits are made above, relevant subitems should still be deleted. + if (argMultiMap.getValue(PREFIX_DELETE_TASK).isPresent()) { + editItemDescriptor.setHasDeleteTask(true); + } + if (argMultiMap.getValue(PREFIX_DELETE_REMINDER).isPresent()) { + editItemDescriptor.setHasDeleteReminder(true); + } + if (argMultiMap.getValue(PREFIX_DELETE_EVENT).isPresent()) { + editItemDescriptor.setHasDeleteEvent(true); + } + + if ((!editItemDescriptor.isAnyFieldEdited()) && (!editItemDescriptor.hasAnyDelete())) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editItemDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } + +} diff --git a/src/main/java/seedu/elisa/logic/parser/ElisaParser.java b/src/main/java/seedu/elisa/logic/parser/ElisaParser.java new file mode 100644 index 00000000000..5eb51dcebb8 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/ElisaParser.java @@ -0,0 +1,170 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.elisa.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.elisa.logic.commands.ClearCommand; +import seedu.elisa.logic.commands.ClearScreenCommand; +import seedu.elisa.logic.commands.CloseCommand; +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.commands.ContinueCommand; +import seedu.elisa.logic.commands.DeleteCommand; +import seedu.elisa.logic.commands.DoneCommand; +import seedu.elisa.logic.commands.DownCommand; +import seedu.elisa.logic.commands.EditCommand; +import seedu.elisa.logic.commands.ExitCommand; +import seedu.elisa.logic.commands.FindCommand; +import seedu.elisa.logic.commands.GameCommand; +import seedu.elisa.logic.commands.JokeCommand; +import seedu.elisa.logic.commands.OpenCommand; +import seedu.elisa.logic.commands.PriorityCommand; +import seedu.elisa.logic.commands.RedoCommand; +import seedu.elisa.logic.commands.ShowCommand; +import seedu.elisa.logic.commands.SnoozeCommand; +import seedu.elisa.logic.commands.SortCommand; +import seedu.elisa.logic.commands.ThemeCommand; +import seedu.elisa.logic.commands.UndoCommand; +import seedu.elisa.logic.commands.UpCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.ElisaCommandHistory; + +/** + * Parses user input. + */ +public class ElisaParser { + + /** + * Used for initial separation of command word and args. + */ + protected static final Pattern BASIC_COMMAND_FORMAT = + Pattern.compile("(?\\S+)(?[^-]*)(?.*)"); + + protected ElisaCommandHistory elisaCommandHistory; + + public ElisaParser(ElisaCommandHistory elisaCommandHistory) { + this.elisaCommandHistory = elisaCommandHistory; + } + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = inputToMatcher(userInput); + + final String commandWord = matcher.group("commandWord"); + final String description = matcher.group("description"); + final String flags = " " + matcher.group("flags"); + + return parseCommandHelper(commandWord, description, flags); + } + + /** + * Converts the user input into the matcher to easily find the command word, description and flags. + * @param userInput the input from the user. + * @return the Matcher object of the user's input. + * @throws ParseException if the matcher is not of a valid format. + */ + protected Matcher inputToMatcher(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, " This doesn't match anything!")); + } + return matcher; + } + + /** + * Helper function that does the actual creation of the command. + * @param commandWord the command word from the user's input. + * @param description the description that comes with the user's input. + * @param flags the flags that comes with the user's input. + * @return the command that is to be executed based on the user's input. + * @throws ParseException if the command word is not found. + */ + protected Command parseCommandHelper(String commandWord, String description, String flags) throws ParseException { + switch (commandWord.toLowerCase()) { + + case "task": + return new AddTaskCommandParser().parse(description, flags); + + case "event": + return new AddEventCommandParser().parse(description, flags); + + case "reminder": + return new AddReminderCommandParser().parse(description, flags); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(description, flags); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(description, flags); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(description, flags); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(elisaCommandHistory); + + case RedoCommand.COMMAND_WORD: + return new RedoCommand(elisaCommandHistory); + + case ShowCommand.COMMAND_WORD: + return new ShowCommandParser().parse(description, flags); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(description, flags); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case PriorityCommand.COMMAND_WORD: + return new PriorityCommandParser().parse(description, flags); + + case DoneCommand.COMMAND_WORD: + return new DoneCommandParser().parse(description, flags); + + case "continue": + case ContinueCommand.COMMAND_WORD: + return new ContinueCommandParser().parse(description, flags); + + case JokeCommand.COMMAND_WORD: + return new JokeCommand(); + + case ThemeCommand.COMMAND_WORD: + return new ThemeCommand(description); + + case UpCommand.COMMAND_WORD: + return new UpCommand(); + + case DownCommand.COMMAND_WORD: + return new DownCommand(); + + case ClearScreenCommand.COMMAND_WORD: + return new ClearScreenCommand(); + + case OpenCommand.COMMAND_WORD: + return new OpenCommandParser().parse(description, flags); + + case CloseCommand.COMMAND_WORD: + return new CloseCommand(); + + case SnoozeCommand.COMMAND_WORD: + return new SnoozeCommandParser().parse(description, flags); + + case GameCommand.COMMAND_WORD: + return new GameCommandParser().parse(description, ""); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/FastReminderDateTimeParser.java b/src/main/java/seedu/elisa/logic/parser/FastReminderDateTimeParser.java new file mode 100644 index 00000000000..04e2f1a37fc --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/FastReminderDateTimeParser.java @@ -0,0 +1,73 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_FAST_REMINDER_FORMAT; + +import java.time.DateTimeException; +import java.time.LocalDateTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.elisa.logic.parser.exceptions.FastReminderParseException; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parse stringDateTime in fast reminder format (ie 10.min.later) to a LocalDateTime object. + */ +public class FastReminderDateTimeParser implements DateTimeParser { + + private static final String MESSAGE_BEYOND_RANGE = "That's a bit too far don't you think? " + + "I can only accept positive integers less than 100. For example: 100.day.later or 2.min.later"; + private static final String DAY_INDICATOR = "DAY"; + private static final String HOUR_INDICATOR = "HOUR"; + private static final String MIN_INDICATOR = "MIN"; + + private static final Pattern BASIC_INPUT_FORMAT = + Pattern.compile("(?[1-9]\\d*)(\\.)(?MIN|HOUR|DAY)(\\.LATER)$"); + + /** + * Parse this stringDateTime into a LocalDateTime representation + * @param stringDateTime of the unprocessed date time string + * @return LocalDateTime representation of the stringDateTime + * @throws ParseException if the format of stringDateTime is incorrect or if the value given is greater than 100 + */ + public LocalDateTime parseDateTime(String stringDateTime) throws ParseException { + //stringDateTime should be of format "10.min.later" or "3.hour.later" or "2.day.later" + String processedString = stringDateTime.toUpperCase(); + + final Matcher matcher = BASIC_INPUT_FORMAT.matcher(processedString); + if (!matcher.matches()) { + throw new ParseException(MESSAGE_INVALID_FAST_REMINDER_FORMAT); + } + + final String quantity = matcher.group("quantity"); + final String unit = matcher.group("unit"); + final int intQuantity = Integer.valueOf(quantity); + + if (intQuantity > 100) { + throw new FastReminderParseException(MESSAGE_BEYOND_RANGE); + } + + LocalDateTime current = LocalDateTime.now(); + LocalDateTime processedDateTime = LocalDateTime.now(); // just to initialize + + try { + switch (unit) { + case DAY_INDICATOR: + processedDateTime = current.plusDays(intQuantity); + break; + case HOUR_INDICATOR: + processedDateTime = current.plusHours(intQuantity); + break; + case MIN_INDICATOR: + processedDateTime = current.plusMinutes(intQuantity); + break; + default: + // nothing + } + } catch (DateTimeException e) { + throw new ParseException(e.getMessage()); + } + + return processedDateTime; + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/FindCommandParser.java b/src/main/java/seedu/elisa/logic/parser/FindCommandParser.java new file mode 100644 index 00000000000..a2dfaf8c916 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/FindCommandParser.java @@ -0,0 +1,31 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.elisa.logic.commands.FindCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCommand parse(String keywords, String empty) throws ParseException { + + String trimmedArgs = keywords.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + String[] keywordArray = trimmedArgs.split("\\s+"); + + return new FindCommand(keywordArray); + } + +} diff --git a/src/main/java/seedu/elisa/logic/parser/FocusElisaParser.java b/src/main/java/seedu/elisa/logic/parser/FocusElisaParser.java new file mode 100644 index 00000000000..baa2467759f --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/FocusElisaParser.java @@ -0,0 +1,47 @@ +package seedu.elisa.logic.parser; + +import java.util.regex.Matcher; + +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.commands.DownCommand; +import seedu.elisa.logic.commands.FindCommand; +import seedu.elisa.logic.commands.GameCommand; +import seedu.elisa.logic.commands.ShowCommand; +import seedu.elisa.logic.commands.SortCommand; +import seedu.elisa.logic.commands.UpCommand; +import seedu.elisa.logic.parser.exceptions.FocusModeException; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.ElisaCommandHistory; + +/** + * The parser that is to be used by ELISA when it is in focus mode. + * It prevents certain commands from being parsed when it is in focus mode. + */ +public class FocusElisaParser extends ElisaParser { + public FocusElisaParser(ElisaCommandHistory elisaCommandHistory) { + super(elisaCommandHistory); + } + + @Override + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = super.inputToMatcher(userInput); + + final String commandWord = matcher.group("commandWord"); + final String description = matcher.group("description"); + final String flags = " " + matcher.group("flags"); + + switch(commandWord.toLowerCase()) { + case "event": + case "reminder": + case SortCommand.COMMAND_WORD: + case ShowCommand.COMMAND_WORD: + case FindCommand.COMMAND_WORD: + case UpCommand.COMMAND_WORD: + case DownCommand.COMMAND_WORD: + case GameCommand.COMMAND_WORD: + throw new FocusModeException(); + default: + return super.parseCommandHelper(commandWord, description, flags); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/GameCommandParser.java b/src/main/java/seedu/elisa/logic/parser/GameCommandParser.java new file mode 100644 index 00000000000..6beb16a1539 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/GameCommandParser.java @@ -0,0 +1,24 @@ +package seedu.elisa.logic.parser; + +import seedu.elisa.logic.commands.GameCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates new GameCommand object. + */ +public class GameCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GameCommand + * and returns a GameCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GameCommand parse(String description, String empty) throws ParseException { + + if (description.trim().isEmpty()) { + return new GameCommand("e"); + } else { + return new GameCommand(description.trim()); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/OpenCommandParser.java b/src/main/java/seedu/elisa/logic/parser/OpenCommandParser.java new file mode 100644 index 00000000000..8ef71acef4f --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/OpenCommandParser.java @@ -0,0 +1,30 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.logic.commands.OpenCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses the input to create an OpenCommand with the proper parameters. + */ +public class OpenCommandParser implements Parser { + + /** + * Parses the given {@code description} to generate an OpenCommand with the item at the given index of this list + * @param description index of item + * @param flags should be empty + * @return + * @throws ParseException if the given string is not a positive integer + */ + public OpenCommand parse(String description, String flags) throws ParseException { + // flags should be empty in this case, focus on description only + try { + Index index = ParserUtil.parseIndex(description); + return new OpenCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, OpenCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/elisa/logic/parser/Parser.java similarity index 60% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/elisa/logic/parser/Parser.java index d6551ad8e3f..6ac5cc6856e 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/elisa/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package seedu.elisa.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. @@ -12,5 +12,5 @@ public interface Parser { * Parses {@code userInput} into a command and returns it. * @throws ParseException if {@code userInput} does not conform the expected format */ - T parse(String userInput) throws ParseException; + T parse(String description, String userInput) throws ParseException; } diff --git a/src/main/java/seedu/elisa/logic/parser/ParserUtil.java b/src/main/java/seedu/elisa/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..8d73f123027 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/ParserUtil.java @@ -0,0 +1,310 @@ +package seedu.elisa.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.ItemDescription; +import seedu.elisa.commons.core.item.Priority; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.commons.util.StringUtil; +import seedu.elisa.logic.parser.exceptions.FastReminderParseException; +import seedu.elisa.logic.parser.exceptions.InvalidDateException; +import seedu.elisa.logic.parser.exceptions.MidnightParseException; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.AutoReschedulePeriod; + + +/** + * Contains utility methods used for parsing strings in the various *Parser classes. + */ +public class ParserUtil { + + public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INCORRECT_AUTORESCHEDULE_FORMAT = "Auto Reschedule format given is incorrect. " + + "Use either hour/day/week or 10.min.later format"; + public static final String MESSAGE_INCORRECT_DATETIME_FORMAT = "Date Time format given is incorrect. " + + "Please follow this format: \"2019-09-25T23:59\"" + + "or \"25/09/2019 2359\"" + + "or \"10.min.later\""; + public static final String MESSAGE_INCORRECT_PRIORITY_FORMAT = "Priority format given is incorrect. " + + "Please follow this format \"-p High\""; + public static final String MESSAGE_INVALID_SNOOZE_TIME = "You can't snooze backwards in time you lazy bird."; + public static final String MESSAGE_MIDNIGHT = "Perhaps you mean %s of the next day?"; + + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). + */ + public static Index parseIndex(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + /** + * Parses {@code description} into a {@code ItemDescription} and returns it. + * Leading and trailing whitespaces will be trimmed. + * @param description given for the item. + * @return a new item description that is processed + * @throws ParseException if the description is invalid (empty description). + */ + public static ItemDescription parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!ItemDescription.isValidItemDescription(trimmedDescription)) { + throw new ParseException(ItemDescription.MESSAGE_CONSTRAINTS); + } + return new ItemDescription(trimmedDescription); + } + + /** + * Parse the {@code dateTime} into a {@code Optional} and returns it. + * Converts a String to a LocalDateTime object and creates a new event with it. + * @param dateTime representing the deadline of the event + * @return Optional.of(event) if the Event created is valid, Optional.empty() otherwise + * @throws ParseException if the format of deadline provided is incorrect + */ + public static Optional parseDateTime(String dateTime) throws ParseException { + if (dateTime == null) { + return Optional.empty(); + } + + String trimmedDateTime = dateTime.trim(); + LocalDateTime formattedDateTime; + formattedDateTime = getFormattedDateTime(trimmedDateTime); //LocalDateTime.parse(trimmedDateTime); + + Event newEvent = new Event(formattedDateTime, null); + + return Optional.of(newEvent); + } + + /** + * Parse the {@code reminder} into a {@code Optional} and returns it. + * Converts the string time into a LocalDateTime object and create a Reminder with it. + * @param reminder representing the time of the reminder + * @return Optional.of(reminder) if the reminder created is valid, Optional.empty() otherwise + * @throws ParseException if the format of the reminder time is incorrect + */ + public static Optional parseReminder(String reminder) throws ParseException { + if (reminder == null) { + return Optional.empty(); + } + + String trimmedDateTime = reminder.trim(); + LocalDateTime formattedDateTime; + + try { + formattedDateTime = getFormattedDateTime(trimmedDateTime); //LocalDateTime.parse(trimmedDateTime); + if (formattedDateTime.isBefore(LocalDateTime.now())) { + throw new ParseException("You can't remind your past self silly!"); + } + } catch (DateTimeParseException e) { + throw new ParseException("Date Time format given is incorrect. " + + "Please follow this format: \"-r 2019-09-25T23:59:50.63\"" + + "or \"-r 25/09/2019 2359\"" + + "of \"-r 10.min.later\""); + } + + Reminder newReminder = new Reminder(formattedDateTime); + return Optional.of(newReminder); + } + + /** + * Parse the {@code snoozeTillTime} into a {@code Optional} and returns it. + * Converts the string time into a LocalDateTime object/ + * @param snoozeTillTime representing the time of the next occurrence of the Reminder + * @return Optional.of(formattedDateTime) if the occurenceDateTime created is valid, Optional.empty() otherwise + * @throws ParseException if the format of the reminder time is incorrect + */ + public static Optional parseSnooze(String snoozeTillTime) throws ParseException { + if (snoozeTillTime == null) { + return Optional.empty(); + } + + String trimmedDateTime = snoozeTillTime.trim(); + LocalDateTime formattedDateTime; + formattedDateTime = getFormattedDateTime(trimmedDateTime); + + //Checks if you are snoozing to a dateTime that is before now. + if (formattedDateTime.isBefore(LocalDateTime.now())) { + throw new ParseException(MESSAGE_INVALID_SNOOZE_TIME); + } + + return Optional.of(formattedDateTime); + } + + /** + * Parse the {@code priority} into a {@code Optional} and returns it. + * Converts the string of priority into an enumeration priority object, is case-insensitive. + * @param priority of the task or event + * @return Optional.of(priority) if the priority is valid, Optional.empty() otherwise + * @throws ParseException if the priority given is not high/medium/low + */ + public static Optional parsePriority(String priority) throws ParseException { + if (priority == null) { + return Optional.empty(); + } + + String trimmedPriority = priority.trim(); + Priority processedPriority; + + if (trimmedPriority.equalsIgnoreCase("HIGH")) { + processedPriority = Priority.HIGH; + } else if (trimmedPriority.equalsIgnoreCase("MEDIUM")) { + processedPriority = Priority.MEDIUM; + } else if (trimmedPriority.equalsIgnoreCase("LOW")) { + processedPriority = Priority.LOW; + } else { + throw new ParseException(MESSAGE_INCORRECT_PRIORITY_FORMAT); + } + + return Optional.of(processedPriority); //maybe use enum here + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseTags(Collection tags) throws ParseException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + /** + * Parses string {@code period} into an {@code Optional} representation of the period. + * @param period of the auto-reschedule. Expects "hour"/"day"/"week" or format "10.min.later" + * @return Optional.of(AutoRechedulePeriod) if this period is valid. Optional.of(empty) otherwise. + * @throws ParseException if the format of period given is incorrect. + */ + public static Optional parseReschedule(String period) throws ParseException { + if (period == null) { + return Optional.empty(); + } + + String processedPeriod = period.trim().toUpperCase(); + boolean isFixedPeriod = false; + AutoReschedulePeriod reschedulePeriod = new AutoReschedulePeriod(0); // just to initialise + + switch(processedPeriod) { + case(AutoReschedulePeriod.BY_HOUR): + reschedulePeriod = AutoReschedulePeriod.byHour(); + isFixedPeriod = true; + break; + case (AutoReschedulePeriod.BY_DAY): + reschedulePeriod = AutoReschedulePeriod.byDay(); + isFixedPeriod = true; + break; + case (AutoReschedulePeriod.BY_WEEK): + reschedulePeriod = AutoReschedulePeriod.byWeek(); + isFixedPeriod = true; + break; + default : + // nothing + } + + if (!isFixedPeriod) { + try { + DateTimeParser parser = new FastReminderDateTimeParser(); + LocalDateTime temp = parser.parseDateTime(processedPeriod); + reschedulePeriod = AutoReschedulePeriod.from(temp); + } catch (Exception e) { + if (e instanceof FastReminderParseException) { + throw new ParseException("Hmmm... There seems to be an issue with your -auto flag... " + + e.getMessage()); + } else { + throw new ParseException(MESSAGE_INCORRECT_AUTORESCHEDULE_FORMAT); + } + } + } + + return Optional.of(reschedulePeriod); + } + + /** + * Processes the string by trying out different formats, and returns a LocalDateTime + * @param stringDateTime of the date and time + * @return a LocalDateTime representation of the given string + * @throws DateTimeParseException if the format of the string given is incorrect + */ + public static LocalDateTime getFormattedDateTime(String stringDateTime) throws ParseException { + boolean invalidFormat = false; + ParseException parseException = new ParseException(MESSAGE_INCORRECT_DATETIME_FORMAT); + ArrayList allParsers = new ArrayList<>() { + { + add(new StandardDateTimeParser()); + add(new DefinedDateTimeParser()); + add(new FastReminderDateTimeParser()); + } + }; + + LocalDateTime processedDateTime = LocalDateTime.now(); // just to initialize + for (DateTimeParser parser : allParsers) { + try { + processedDateTime = parser.parseDateTime(stringDateTime); + invalidFormat = false; + break; + } catch (FastReminderParseException fp) { + invalidFormat = true; + parseException = fp; + } catch (MidnightParseException mp) { + invalidFormat = true; + String formatted = String.format(MESSAGE_MIDNIGHT, mp.getMessage()); + parseException = new ParseException(formatted); + } catch (InvalidDateException de) { + invalidFormat = true; + parseException = de; + } catch (ParseException err) { + invalidFormat = true; + } + } + + if (invalidFormat) { + throw parseException; + } + + return processedDateTime; + } + + public static LocalDateTime getUpdatedDateTime(LocalDateTime startDateTime, Long period) { + // Use modulo to get the remaining time till the next reschedule time. Add that remaining time to the time now. + long millisDifference = Duration.between(startDateTime, LocalDateTime.now()).toMillis(); // positive difference; + + long millisRemainder = millisDifference % period; //millisDifferenceBi.mod(periodBi).longValue(); + long tillNextStart = period - millisRemainder; + LocalDateTime updatedDateTime = LocalDateTime.now().plusNanos(Duration.ofMillis(tillNextStart).toNanos()); + + return updatedDateTime; + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/elisa/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/elisa/logic/parser/Prefix.java index c859d5fa5db..70213547077 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/elisa/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.elisa.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/seedu/elisa/logic/parser/PriorityCommandParser.java b/src/main/java/seedu/elisa/logic/parser/PriorityCommandParser.java new file mode 100644 index 00000000000..ada036ea77c --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/PriorityCommandParser.java @@ -0,0 +1,46 @@ +package seedu.elisa.logic.parser; + +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.LocalDateTime; + +import seedu.elisa.logic.commands.PriorityCommand; +import seedu.elisa.logic.commands.ScheduledPriorityCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parser to generate a priority command based on the user's input. + */ +public class PriorityCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the PriorityCommand + * and returns a PriorityCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PriorityCommand parse(String args, String flags) throws ParseException { + String time = args.trim(); + flags = flags.trim(); + + boolean focusMode = false; + if (!flags.isEmpty()) { + if (flags.equalsIgnoreCase("-f") || flags.equalsIgnoreCase("-focus")) { + focusMode = true; + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PriorityCommand.MESSAGE_USAGE)); + } + } + + if (time.isEmpty()) { + return new PriorityCommand(focusMode); + } + + try { + LocalDateTime ldt = ParserUtil.getFormattedDateTime(time); + return new ScheduledPriorityCommand(ldt, focusMode); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PriorityCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/ShowCommandParser.java b/src/main/java/seedu/elisa/logic/parser/ShowCommandParser.java new file mode 100644 index 00000000000..6ce1c4f43e3 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/ShowCommandParser.java @@ -0,0 +1,38 @@ +package seedu.elisa.logic.parser; + +import static java.util.Objects.requireNonNull; + +import seedu.elisa.logic.commands.ShowCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class ShowCommandParser implements Parser { + public static final String TASK_VIEW_COMMAND = "T"; + public static final String EVENT_VIEW_COMMAND = "E"; + public static final String REMINDER_VIEW_COMMAND = "R"; + public static final String CALENDAR_VIEW_COMMAND = "C"; + + /** + * Parses the given {@code view} in the context of ShowCommand and returns a ShowCommand for execution + * @param view of the view to show + * @param empty an empty string + * @return appropriate ShowCommand to execute + * @throws ParseException if the format of view string is incorrect + */ + public ShowCommand parse(String view, String empty) throws ParseException { + requireNonNull(view); + String pane = view.trim(); + + if (pane.equalsIgnoreCase(TASK_VIEW_COMMAND) + || pane.equalsIgnoreCase(EVENT_VIEW_COMMAND) + || pane.equalsIgnoreCase(REMINDER_VIEW_COMMAND) + || pane.equalsIgnoreCase(CALENDAR_VIEW_COMMAND)) { + return new ShowCommand(pane); + } else { + throw new ParseException("The format given for show command is incorrect. " + + "Example usage: \"show T\""); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/SnoozeCommandParser.java b/src/main/java/seedu/elisa/logic/parser/SnoozeCommandParser.java new file mode 100644 index 00000000000..27a341cd13c --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/SnoozeCommandParser.java @@ -0,0 +1,71 @@ +package seedu.elisa.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.elisa.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.elisa.logic.parser.CliSyntax.PREFIX_SNOOZE; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.index.Index; +import seedu.elisa.logic.LogicManager; +import seedu.elisa.logic.commands.SnoozeCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class SnoozeCommandParser implements Parser { + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + private Duration defaultSnoozeDuration = Duration.of(5, ChronoUnit.MINUTES); + + /** + * Parses the given {@code description} and {@code args} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SnoozeCommand parse(String description, String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultiMap = new ArgumentMultimap(); + String processArgs = args + " "; // account for the possibility that --tk or --r or --e is given with no space + try { + argMultiMap = ArgumentTokenizer.tokenize(processArgs, PREFIX_SNOOZE); + } catch (Exception e) { + logger.info("Failure to tokenize arguments: SnoozeCommand"); + throw new ParseException("Snooze command format is incorrect."); + } + + Index index; + boolean hasIndex; + + if (!description.isBlank()) { + try { + index = ParserUtil.parseIndex(description); + hasIndex = true; + } catch (ParseException pe) { + //TODO: Auto snooze most recent reminder + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SnoozeCommand.MESSAGE_USAGE), pe); + } + } else { + index = null; + hasIndex = false; + } + + LocalDateTime newReminderOccurrence = null; + + if (argMultiMap.getValue(PREFIX_SNOOZE).isPresent()) { + newReminderOccurrence = ParserUtil.parseSnooze(argMultiMap.getValue(PREFIX_SNOOZE).get()).get(); + } else { + newReminderOccurrence = LocalDateTime.now().plus(defaultSnoozeDuration); + } + + return new SnoozeCommand(hasIndex, index, newReminderOccurrence); + } + +} diff --git a/src/main/java/seedu/elisa/logic/parser/SortCommandParser.java b/src/main/java/seedu/elisa/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..d7eea763989 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/SortCommandParser.java @@ -0,0 +1,53 @@ +package seedu.elisa.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; +import java.util.Optional; + +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.commands.SortCommand; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parser to create a sort command with the right arguments + */ +public class SortCommandParser { + public static final String DESC = "desc"; + public static final String DESCRIPTION = "description"; + public static final String PRI = "pri"; + public static final String PRIORITY = "priority"; + + /** + * Create a sort command base on the flag that was passed in. + * @param args of the way in which + * @param empty an empty string + * @return appropriate SortCommand to execute + * @throws ParseException if the argument string is incorrect + */ + public SortCommand parse(String args, String empty) throws ParseException { + requireNonNull(args); + String flag = args.trim(); + + if (flag.isEmpty()) { + return new SortCommand(Optional.empty()); + } + + Comparator comparator; + switch(flag.toLowerCase()) { + case DESC: + case DESCRIPTION: + comparator = (item1, item2) -> item1.getItemDescription().getDescription() + .compareToIgnoreCase(item2.getItemDescription().getDescription()); + break; + case PRI: + case PRIORITY: + comparator = (item1, item2) -> item1.getPriority().compareTo(item2.getPriority()); + break; + default: + throw new ParseException("The parameter given for sort command is incorrect. " + + "Example usage: \"sort desc\""); + } + return new SortCommand(Optional.of(comparator)); + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/StandardDateTimeParser.java b/src/main/java/seedu/elisa/logic/parser/StandardDateTimeParser.java new file mode 100644 index 00000000000..f6f5923efaf --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/StandardDateTimeParser.java @@ -0,0 +1,33 @@ +package seedu.elisa.logic.parser; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import seedu.elisa.logic.parser.exceptions.MidnightParseException; +import seedu.elisa.logic.parser.exceptions.ParseException; + +/** + * Parse string Date Time in the standard way using LocalDateTime.parse(). + */ +public class StandardDateTimeParser implements DateTimeParser { + + /** + * Parse string using LocalDateTime.parse() + * @param stringDateTime of the unprocessed date time string + * @return LocalDateTime representation of this string + * @throws DateTimeParseException if the format of this string is incorrect + */ + public LocalDateTime parseDateTime(String stringDateTime) throws ParseException { + LocalDateTime parsed; + try { + parsed = LocalDateTime.parse(stringDateTime); + return parsed; + } catch (DateTimeParseException e) { + if (stringDateTime.contains("24:00")) { + throw new MidnightParseException("00:00"); + } + throw new ParseException("Date Time format given is incorrect." + + " Should be \"2019-12-03T23:00\""); + } + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/exceptions/FastReminderParseException.java b/src/main/java/seedu/elisa/logic/parser/exceptions/FastReminderParseException.java new file mode 100644 index 00000000000..0cbbd4d95f8 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/exceptions/FastReminderParseException.java @@ -0,0 +1,15 @@ +package seedu.elisa.logic.parser.exceptions; + +/** + * Represents a parse error encountered by FastReminderDateTimeParser. + */ +public class FastReminderParseException extends ParseException { + + public FastReminderParseException(String message) { + super(message); + } + + public FastReminderParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/exceptions/FocusModeException.java b/src/main/java/seedu/elisa/logic/parser/exceptions/FocusModeException.java new file mode 100644 index 00000000000..b079e332ef7 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/exceptions/FocusModeException.java @@ -0,0 +1,10 @@ +package seedu.elisa.logic.parser.exceptions; + +/** + * Exception that is thrown when attempting to run some operation when in focus mode. + */ +public class FocusModeException extends ParseException { + public FocusModeException() { + super("Hey, stay focused on your task!"); + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/exceptions/InvalidDateException.java b/src/main/java/seedu/elisa/logic/parser/exceptions/InvalidDateException.java new file mode 100644 index 00000000000..30c65e92ab8 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/exceptions/InvalidDateException.java @@ -0,0 +1,11 @@ +package seedu.elisa.logic.parser.exceptions; + +/** + * Represents a parse error encountered by parsing a non-existent date + */ +public class InvalidDateException extends ParseException { + + public InvalidDateException() { + super("This date does not exist!"); + } +} diff --git a/src/main/java/seedu/elisa/logic/parser/exceptions/MidnightParseException.java b/src/main/java/seedu/elisa/logic/parser/exceptions/MidnightParseException.java new file mode 100644 index 00000000000..628ec8cf5c0 --- /dev/null +++ b/src/main/java/seedu/elisa/logic/parser/exceptions/MidnightParseException.java @@ -0,0 +1,11 @@ +package seedu.elisa.logic.parser.exceptions; + +/** + * Represents a parse error encountered with parsing "24:00". + */ +public class MidnightParseException extends ParseException { + + public MidnightParseException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/elisa/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/elisa/logic/parser/exceptions/ParseException.java index 158a1a54c1c..6162ca387c4 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/elisa/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.elisa.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.elisa.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/elisa/model/AutoRescheduleManager.java b/src/main/java/seedu/elisa/model/AutoRescheduleManager.java new file mode 100644 index 00000000000..0f2c84838ad --- /dev/null +++ b/src/main/java/seedu/elisa/model/AutoRescheduleManager.java @@ -0,0 +1,127 @@ +package seedu.elisa.model; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Timer; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.LogicManager; +import seedu.elisa.logic.parser.ParserUtil; +import seedu.elisa.model.item.EventList; + +/** + * Manages all the events that are to be rescheduled automatically at each of their given interval period. + * Uses a Timer to keep track of when to update the Event's startDateTime. + */ +public class AutoRescheduleManager { + private static AutoRescheduleManager manager; + private static Timer timer; + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private AutoRescheduleManager() {} + + /** + * The only way to get an AutoRescheduleManager object. There should only be one AutoRescheduleManager at any time. + * @return the only instance of AutoRescheduleManager + */ + public static AutoRescheduleManager getInstance() { + if (manager == null) { + manager = new AutoRescheduleManager(); + timer = new Timer(); + } + return manager; + } + + /** + * Initialise this AutoRescheduleManager with all the events that can be rescheduled, in the given event list. + * Update event times to the latest upcoming one, given their period. + * @param eventList of all events in the storage + * @param model containing the events + */ + public static void initStorageEvents(EventList eventList, ItemModel model) { + for (Item item : eventList) { + if (item.hasEvent()) { + updateEvent(item, model); + } + } + } + + /** + * Updates the start time of the event and creates a RescheduleTask based on the updated event. + * If the current start time is already over, update to the next upcoming time. + * Else, start time remains the same. + * @param item whose event is to be updated + * @param model where the item is stored in + */ + public static void updateEvent(Item item, ItemModel model) { + Event event = item.getEvent().get(); + if (event.hasAutoReschedule()) { + if (event.getStartDateTime().compareTo(LocalDateTime.now()) > 0) { + // event date is after now + // add event to Timer thread, add(newEvent) + RescheduleTask task = new RescheduleTask(item, event.getPeriod(), model); + getInstance().add(task); + } else { + // event date is before now, but is reschedulable + // modify the event date to the most upcoming one (Use modulo and add remainder) + // add(newEvent) + LocalDateTime updatedDateTime = getUpdatedDateTime(event); + Event updatedEvent = event.changeStartDateTime(updatedDateTime); + + // update the old event time to the new one in the itemModel + Item oldItem = item; + Item newItem = item.changeEvent(updatedEvent); + RescheduleTask task = new RescheduleTask(newItem, updatedEvent.getPeriod(), model); + model.replaceItem(oldItem, newItem); + + getInstance().add(task); + } + } + } + + /** + * Update the old event start time to the closest upcoming start time, using the event's auto-reschedule period + * @param event whose start date we want to modify + * @return LocalDateTime representation of the modified time + */ + private static LocalDateTime getUpdatedDateTime(Event event) { + // Use modulo to get the remaining time till the next reschedule time. Add that remaining time to the time now. + long period = event.getPeriod().getPeriod(); + LocalDateTime startDateTime = event.getStartDateTime(); + + LocalDateTime updatedDateTime = ParserUtil.getUpdatedDateTime(startDateTime, period); + return updatedDateTime; + } + + /** + * Adds a RescheduleTask that is to be rescheduled periodically, to this AutoRescheduleManager. + * Requirements: task's start time is after current time. If not, use {@code updateEvent()} to add. + * @param task RescheduleTask to be carried out after reaching an event's startDateTime + */ + public void add(RescheduleTask task) { + try { + Duration delay = Duration.between(LocalDateTime.now(), task.getStartTime()); + if (delay.getSeconds() < 0) { + LocalDateTime updatedTime = getUpdatedDateTime(task.getEvent()); + //delay = Duration.between(LocalDateTime.now(), updatedTime); + } + + RescheduleTask.addToAllTasks(task); + timer.scheduleAtFixedRate(task, delay.toMillis(), task.getLongPeriod()); + } catch (Exception e) { + logger.warning("----------------[Failed to schedule Event][" + task.getEvent() + "]" + e.getMessage()); + } + } + + /** + * Shutdown the AutoRescheduleManager + */ + public void shutdown() { + timer.cancel(); + } + +} diff --git a/src/main/java/seedu/elisa/model/AutoReschedulePeriod.java b/src/main/java/seedu/elisa/model/AutoReschedulePeriod.java new file mode 100644 index 00000000000..a6877f87ab1 --- /dev/null +++ b/src/main/java/seedu/elisa/model/AutoReschedulePeriod.java @@ -0,0 +1,84 @@ +package seedu.elisa.model; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * Represents the auto-reschedule period of an event. + */ +public class AutoReschedulePeriod { + + public static final String BY_HOUR = "HOUR"; + public static final String BY_DAY = "DAY"; + public static final String BY_WEEK = "WEEK"; + private static final Long hourInMilliseconds = Duration.ofHours(1).toMillis(); + private static final Long dayInMilliseconds = Duration.ofDays(1).toMillis(); + private static final Long weekInMilliseconds = Duration.ofDays(7).toMillis(); + + private long period; + + public AutoReschedulePeriod(long period) { + this.period = period; + } + + /** + * Get an auto-reschedule period of one hour. + * @return AutoReschedulePeriod object of one hour. + */ + public static AutoReschedulePeriod byHour() { + long hourPeriod = hourInMilliseconds; + return new AutoReschedulePeriod(hourPeriod); + } + + /** + * Get an auto-reschedule period of one day. + * @return AutoReschedulePeriod object of one day. + */ + public static AutoReschedulePeriod byDay() { + long dayPeriod = dayInMilliseconds; + return new AutoReschedulePeriod(dayPeriod); + } + + /** + * Get an auto-reschedule period of one week. + * @return AutoReschedulePeriod object of one week. + */ + public static AutoReschedulePeriod byWeek() { + long weekPeriod = weekInMilliseconds; + return new AutoReschedulePeriod(weekPeriod); + } + + /** + * Get an auto-reschedule period of this LocalDateTime {@code then} from now. + * @param then LocalDateTime of the time later + * @return AutoReschedulePeriod object representing the time difference from now till then. + */ + public static AutoReschedulePeriod from(LocalDateTime then) { + Duration duration = Duration.between(LocalDateTime.now(), then); + return new AutoReschedulePeriod(duration.toMillis()); + } + + /** + * Get the period of this AutoReschedulePeriod in Millis. + * @return a long representation of this period in Millis. + */ + public long getPeriod() { + return this.period; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof AutoReschedulePeriod)) { + return false; + } + + AutoReschedulePeriod otherPeriod = (AutoReschedulePeriod) other; + Long otherLongPeriod = Long.valueOf(otherPeriod.getPeriod()); + Long thisLongPeriod = Long.valueOf(getPeriod()); + return otherLongPeriod.equals(thisLongPeriod); + } +} diff --git a/src/main/java/seedu/elisa/model/ElisaCommandHistory.java b/src/main/java/seedu/elisa/model/ElisaCommandHistory.java new file mode 100644 index 00000000000..163312adfb2 --- /dev/null +++ b/src/main/java/seedu/elisa/model/ElisaCommandHistory.java @@ -0,0 +1,28 @@ +package seedu.elisa.model; + +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.commands.UndoableCommand; + +/** + * interface for state history + */ + +public interface ElisaCommandHistory { + public void pushUndo(Command command); + + public UndoableCommand popUndo(); + + public UndoableCommand peekUndo(); + + public int sizeUndo(); + + public void pushRedo(Command command); + + public UndoableCommand popRedo(); + + public UndoableCommand peekRedo(); + + public int sizeRedo(); + + public void clearRedo(); +} diff --git a/src/main/java/seedu/elisa/model/ElisaCommandHistoryManager.java b/src/main/java/seedu/elisa/model/ElisaCommandHistoryManager.java new file mode 100644 index 00000000000..f2f81c814dd --- /dev/null +++ b/src/main/java/seedu/elisa/model/ElisaCommandHistoryManager.java @@ -0,0 +1,104 @@ +package seedu.elisa.model; + +import java.util.Stack; + +import seedu.elisa.logic.commands.Command; +import seedu.elisa.logic.commands.UndoableCommand; + + +/** + * Stores the stack of all application states with current state at the top + */ + +public class ElisaCommandHistoryManager implements ElisaCommandHistory { + private Stack undoStack; + private Stack redoStack; + + public ElisaCommandHistoryManager() { + undoStack = new Stack<>(); + redoStack = new Stack<>(); + } + + /** + * push a command into undo stack + * */ + @Override + public void pushUndo(Command command) { + if (command instanceof UndoableCommand) { + undoStack.push((UndoableCommand) command); + } + } + + /** + * empty the redo stack (for use when new command is executed) + * */ + @Override + public void clearRedo() { + redoStack.clear(); + } + + /** + * pop last command from undo stack + * */ + @Override + public UndoableCommand popUndo() { + UndoableCommand command = undoStack.pop(); + pushRedo(command); + return command; + } + + /** + * peek last command from undo stack + * */ + @Override + public UndoableCommand peekUndo() { + return undoStack.peek(); + } + + /** + * return stack size of undo stack + * */ + @Override + public int sizeUndo() { + return undoStack.size(); + } + + @Override + public void pushRedo(Command command) { + if (command instanceof UndoableCommand) { + redoStack.push((UndoableCommand) command); + } + } + + @Override + public UndoableCommand popRedo() { + UndoableCommand command = redoStack.pop(); + pushUndo(command); + return command; + } + + @Override + public UndoableCommand peekRedo() { + return redoStack.peek(); + } + + @Override + public int sizeRedo() { + return redoStack.size(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof ElisaCommandHistoryManager)) { + return false; + } + + ElisaCommandHistoryManager hm = (ElisaCommandHistoryManager) other; + return hm.redoStack.equals(this.redoStack) + && hm.undoStack.equals(this.undoStack); + } +} diff --git a/src/main/java/seedu/elisa/model/ItemIndexWrapper.java b/src/main/java/seedu/elisa/model/ItemIndexWrapper.java new file mode 100644 index 00000000000..9a4b33f1b70 --- /dev/null +++ b/src/main/java/seedu/elisa/model/ItemIndexWrapper.java @@ -0,0 +1,62 @@ +package seedu.elisa.model; + +import seedu.elisa.commons.core.item.Item; + +/** + * Stores an item along with its indices in the separate item lists + * */ + +public class ItemIndexWrapper { + private Item item; //the item + private int visual; //its index in VisualizeList + private int storage; //index in ItemStorage + private int task; //index in TaskList + private int eve; //index in EventList + private int rem; //index in ReminderList + private int frem; //index in FutureReminders + private int arem; //index in ActiveReminders + //All indices are -1 if not in the respective list. + + public ItemIndexWrapper(Item item, int visual, int storage, int task, int eve, int rem, int frem, int arem) { + this.item = item; + this.storage = storage; + this.visual = visual; + this.task = task; + this.eve = eve; + this.rem = rem; + this.frem = frem; + this.arem = arem; + } + + public int getVisual() { + return visual; + } + + public int getStorage() { + return storage; + } + + public int getTask() { + return task; + } + + public int getEve() { + return eve; + } + + public int getRem() { + return rem; + } + + public int getFrem() { + return frem; + } + + public Item getItem() { + return item; + } + + public int getArem() { + return arem; + } +} diff --git a/src/main/java/seedu/elisa/model/ItemModel.java b/src/main/java/seedu/elisa/model/ItemModel.java new file mode 100644 index 00000000000..6ce92d2d4cf --- /dev/null +++ b/src/main/java/seedu/elisa/model/ItemModel.java @@ -0,0 +1,138 @@ +package seedu.elisa.model; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.NoSuchElementException; + +import javafx.beans.property.SimpleBooleanProperty; +import seedu.elisa.commons.core.GuiSettings; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.logic.commands.Command; +import seedu.elisa.model.exceptions.IllegalListException; +import seedu.elisa.model.item.ActiveRemindersList; +import seedu.elisa.model.item.EventList; +import seedu.elisa.model.item.FutureRemindersList; +import seedu.elisa.model.item.VisualizeList; + +/** + * The API of the Model component. + */ +public interface ItemModel { + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' address book file path. + */ + Path getItemStorageFilePath(); + + /** + * Sets the user prefs' address book file path. + */ + void setItemStorageFilePath(Path addressBookFilePath); + + /** + * Replaces address book data with the data in {@code addressBook}. + */ + void setItemStorage(ItemStorage itemStorage); + + /** Returns the AddressBook */ + ItemStorage getItemStorage(); + + public void addItem(Item item); + + public void addItem(ItemIndexWrapper wrapper); + + public void replaceItem(Item item, Item newItem); + + public Item deleteItem(int index); + + public Item deleteItem(Item item); + + public VisualizeList getVisualList(); + + public void setVisualList(String listString) throws IllegalValueException; + + public void clear(); + + public void emptyLists(); + + public VisualizeList findItem(String[] searchStrings); + + public void setVisualizeList(VisualizeList list); + + public void sort(); + + public void sort(Comparator comparator); + + public boolean hasItem(Item item); + + public void addToSeparateList(Item item); + + public void addToSeparateList(ItemIndexWrapper wrapper); + + public ItemIndexWrapper getIndices(int index); + + public void repopulateLists(); + + public ElisaCommandHistory getElisaCommandHistory(); + + public boolean togglePriorityMode() throws IllegalListException; + + public void scheduleOffPriorityMode(LocalDateTime localDateTime); + + public void closePriorityModeThread(); + + public Item markComplete(int index, boolean status) throws IllegalListException; + + public JokeList getJokeList(); + + public String getJoke(); + + //Bryan Reminder + ActiveRemindersList getActiveReminderListProperty(); + + FutureRemindersList getFutureRemindersList(); + + void updateCommandHistory(Command command); + + public EventList getEventList(); + + public Item getItem(int index); + + SimpleBooleanProperty getPriorityMode(); + + boolean isSystemToggle(); + + public Item editItem(Item oldItem, Item newItem); + + Item getLatestOccurredReminder() throws NoSuchElementException; + + PriorityExitStatus getExitStatus(); + + boolean isFocusMode(); + + void toggleOnFocusMode(); + + SimpleBooleanProperty getFocusMode(); +} diff --git a/src/main/java/seedu/elisa/model/ItemModelManager.java b/src/main/java/seedu/elisa/model/ItemModelManager.java new file mode 100644 index 00000000000..b68ccd2ef19 --- /dev/null +++ b/src/main/java/seedu/elisa/model/ItemModelManager.java @@ -0,0 +1,661 @@ +package seedu.elisa.model; + +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Comparator; +import java.util.Date; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Timer; +import java.util.TimerTask; + +import javafx.beans.property.SimpleBooleanProperty; +import seedu.elisa.commons.core.GuiSettings; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Reminder; +import seedu.elisa.commons.core.item.Task; +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.logic.commands.Command; +import seedu.elisa.model.exceptions.IllegalListException; +import seedu.elisa.model.item.ActiveRemindersList; +import seedu.elisa.model.item.CalendarList; +import seedu.elisa.model.item.EventList; +import seedu.elisa.model.item.FutureRemindersList; +import seedu.elisa.model.item.ReminderList; +import seedu.elisa.model.item.TaskList; +import seedu.elisa.model.item.VisualizeList; + +/** + * Represents the model for ELISA + */ +public class ItemModelManager implements ItemModel { + private TaskList taskList; + private EventList eventList; + private ReminderList reminderList; + private CalendarList calendarList; + // The list to be used for visualizing in the Ui + private VisualizeList visualList; + private final UserPrefs userPrefs; + private ItemStorage itemStorage; + private final ElisaCommandHistory elisaCommandHistory; + private final JokeList jokeList; + private SimpleBooleanProperty priorityMode = new SimpleBooleanProperty(false); + private boolean systemToggle = false; + private PriorityExitStatus priorityExitStatus = null; + private PriorityQueue sortedTask = null; + private SimpleBooleanProperty focusMode = new SimpleBooleanProperty(false); + + //Bryan Reminder + //These three lists must be synchronized + private ReminderList pastReminders; + private ActiveRemindersList activeReminders; + private FutureRemindersList futureReminders; + + private Timer timer = null; + + public ItemModelManager(ItemStorage itemStorage, ReadOnlyUserPrefs userPrefs, + ElisaCommandHistory elisaCommandHistory) { + + this.taskList = new TaskList(); + this.eventList = new EventList(); + this.reminderList = new ReminderList(); + this.calendarList = new CalendarList(); + this.visualList = taskList; + this.itemStorage = itemStorage; + this.userPrefs = new UserPrefs(userPrefs); + this.elisaCommandHistory = elisaCommandHistory; + + this.jokeList = new JokeList(); + + pastReminders = new ReminderList(); + activeReminders = new ActiveRemindersList(new ReminderList()); + futureReminders = new FutureRemindersList(); + + repopulateLists(); + } + + + /** + * Repopulate item lists from storage + * */ + + public void repopulateLists() { + for (int i = 0; i < itemStorage.size(); i++) { + addToSeparateList(itemStorage.get(i)); + } + } + + /* Bryan Reminder + * + * Referenced: https://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm + * for property naming conventions. + * + */ + + //Function to get property + @Override + public ActiveRemindersList getActiveReminderListProperty() { + return activeReminders; + } + + @Override + public final FutureRemindersList getFutureRemindersList() { + return futureReminders; + } + + @Override + public void updateCommandHistory(Command command) { + elisaCommandHistory.pushUndo(command); + } + + //Deals with Storage + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + @Override + public Path getItemStorageFilePath() { + return userPrefs.getItemStorageFilePath(); + } + + @Override + public void setItemStorageFilePath(Path itemStorageFilePath) { + requireNonNull(itemStorageFilePath); + userPrefs.setItemStorageFilePath(itemStorageFilePath); + } + + @Override + public void setItemStorage(ItemStorage itemStorage) { + this.itemStorage = itemStorage; + } + + @Override + public ItemStorage getItemStorage() { + return itemStorage; + } + //Above deals with storage + + //Edits state of model + /** + * Adds an item to the respective list. All items will be added to the central list. + * It will also be added to the respective list depending on whether it is a task, event or a reminder. + * @param item the item to be added to the program + */ + public void addItem (Item item) { + addToSeparateList(item); + itemStorage.add(item); + } + + /** + * add given item into specified index + * */ + + public void addItem(ItemIndexWrapper wrapper) { + if (visualList.belongToList(wrapper.getItem())) { + visualList.addToIndex(wrapper.getVisual(), wrapper.getItem()); + } + addToSeparateList(wrapper); + itemStorage.add(wrapper.getStorage(), wrapper.getItem()); + } + + /** + * Helper function to add an item to it's respective list + * @param item the item to be added into the lists + */ + public void addToSeparateList(Item item) { + if (visualList.belongToList(item)) { + visualList.add(item); + } + + if (item.hasTask()) { + taskList.add(item); + if (priorityMode.getValue()) { + sortedTask.offer(item); + getNextTask(); + } + } + + if (item.hasEvent()) { + eventList.add(item); + calendarList.add(item); + } + + if (item.hasReminder()) { + reminderList.add(item); + if ((!futureReminders.contains(item)) + && item.getReminder().get().getOccurrenceDateTime().isAfter(LocalDateTime.now())) { + futureReminders.add(item); + } + } + } + + /** + * add item to separate lists into given index + * */ + + public void addToSeparateList(ItemIndexWrapper wrapper) { + if (wrapper.getTask() != -1) { + taskList.addToIndex(wrapper.getTask(), wrapper.getItem()); + } + + if (wrapper.getEve() != -1) { + eventList.addToIndex(wrapper.getEve(), wrapper.getItem()); + calendarList.addToIndex(wrapper.getEve(), wrapper.getItem()); + } + + if (wrapper.getRem() != -1) { + reminderList.addToIndex(wrapper.getRem(), wrapper.getItem()); + } + + if (wrapper.getFrem() != -1) { + futureReminders.add(wrapper.getFrem(), wrapper.getItem()); + } + } + + @Override + public ElisaCommandHistory getElisaCommandHistory() { + return elisaCommandHistory; + } + + @Override + public JokeList getJokeList() { + return jokeList; + } + + public String getJoke() { + return jokeList.getJoke(); + } + + /** + * Deletes an item from the program. + * @param index the index of the item to be deleted. + * @return the item that was deleted from the program + */ + public Item deleteItem(int index) { + Item item = visualList.remove(index); + return deleteItem(item); + } + + /** + * Deletes an item from the program. + * @param item the item to be deleted. + * @return the item that was deleted from the program + */ + public Item deleteItem(Item item) { + visualList.remove(item); + taskList.remove(item); + eventList.remove(item); + calendarList.remove(item); + reminderList.remove(item); + futureReminders.remove(item); + activeReminders.remove(item); + itemStorage.remove(item); + if (priorityMode.getValue() && sortedTask != null) { + sortedTask.remove(item); + getNextTask(); + } + return item; + } + + public ItemIndexWrapper getIndices(int index) { + Item item = visualList.get(index); + return new ItemIndexWrapper(item, index, itemStorage.indexOf(item), taskList.indexOf(item), + eventList.indexOf(item), reminderList.indexOf(item), + futureReminders.indexOf(item), activeReminders.indexOf(item)); + } + + public VisualizeList getVisualList() { + return this.visualList; + } + + /** + * Set a new item list to be the visualization list. + * @param listString the string representation of the list to be visualized + */ + public void setVisualList(String listString) throws IllegalValueException { + switch(listString) { + case "T": + setVisualList(taskList); + if (priorityMode.getValue()) { + getNextTask(); + } + break; + case "E": + setVisualList(eventList); + break; + case "R": + setVisualList(reminderList); + break; + case "C": + setVisualList(calendarList); + break; + default: + throw new IllegalValueException(String.format("%s is no a valid list", listString)); + } + } + + private void setVisualList(VisualizeList il) { + this.visualList = il; + } + + /** + * Replaces one item with another item. + * @param item the item to be replace + * @param newItem the item that will replace the previous item + */ + public void replaceItem(Item item, Item newItem) { + int index = itemStorage.indexOf(item); + + if (index >= 0) { + itemStorage.setItem(index, newItem); + } + + if ((index = taskList.indexOf(item)) >= 0) { + if (newItem.hasTask()) { + taskList.setItem(index, newItem); + } else { + taskList.remove(index); + } + } + + if ((index = eventList.indexOf(item)) >= 0) { + if (newItem.hasEvent()) { + eventList.setItem(index, newItem); + calendarList.setItem(index, newItem); + } else { + eventList.remove(index); + calendarList.remove(index); + } + } + + if ((index = calendarList.indexOf(item)) >= 0) { + if (newItem.hasEvent()) { + calendarList.setItem(index, newItem); + } else { + calendarList.remove(index); + } + } + + if ((index = reminderList.indexOf(item)) >= 0) { + if (newItem.hasReminder()) { + reminderList.setItem(index, newItem); + } else { + reminderList.remove(index); + } + + if (!item.getReminder().equals(newItem.getReminder())) { + if (newItem.getReminder().isEmpty()) { + if (activeReminders.contains(item)) { + activeReminders.remove(item); + } + + if (futureReminders.contains(item)) { + futureReminders.remove(item); + } + + } else { + //means that newItem has a reminder that is diff from old reminder + //if old had nothing just add to corresponding + //if old had a reminder, remove it + if (item.getReminder().isPresent()) { + if (activeReminders.contains(item)) { + activeReminders.remove(item); + } + + if (futureReminders.contains(item)) { + futureReminders.remove(item); + } + } + Reminder newReminder = newItem.getReminder().get(); + + if (newReminder.getOccurrenceDateTime().isAfter(LocalDateTime.now())) { + //Add to futureReminders if it is to occur later. + futureReminders.add(newItem); + //Otherwise do not add + } + } + } + } + + if ((index = visualList.indexOf(item)) >= 0) { + if (visualList.belongToList(newItem)) { + visualList.setItem(index, newItem); + } else { + visualList.remove(index); + } + } + + if (priorityMode.getValue()) { + sortedTask.remove(item); + if (newItem.hasTask()) { + sortedTask.offer(newItem); + } + getNextTask(); + } + } + + /** + * Edits an item with another item. + * @param oldItem the item to be edited + * @param newItem the edited item + * @return the edited item + */ + public Item editItem(Item oldItem, Item newItem) { + replaceItem(oldItem, newItem); + addToSeparateList(newItem); + return newItem; + } + + /** + * Find an item based on its description. + * @param searchStrings the string to search for within the description + * @return the item list containing all the items that contain the search string + */ + public VisualizeList findItem(String[] searchStrings) { + this.visualList = visualList.find(searchStrings); + return this.visualList; + } + + @Override + public void setVisualizeList(VisualizeList list) { + this.visualList = list; + } + + /** + * Clears the storage for the current ELISA run. + */ + public void clear() { + setItemStorage(new ItemStorage()); + emptyLists(); + this.visualList = taskList; + } + + /** + * Clears the 3 lists for re-populating + * */ + public void emptyLists() { + taskList.clear(); + eventList.clear(); + reminderList.clear(); + calendarList.clear(); + futureReminders.clear(); + } + + /** + * Sort the current visual list. + */ + public void sort() { + this.visualList = visualList.sort(); + } + + /** + * Sorts the current visual list based on a comparator. + * @param comparator the comparator to sort the current list by. + */ + public void sort(Comparator comparator) { + VisualizeList tempList = visualList.deepCopy(); + tempList.sort(comparator); + this.visualList = tempList; + } + + /** + * Checks if the item storage already contains this item. + * @param item to check + * @return true if the item storage contains this item, false otherwise + */ + public boolean hasItem(Item item) { + return itemStorage.contains(item); + } + + /** + * Enable and disable the priority mode + * @return a boolean value. If true, means priority mode is on, else returns false. + * @throws IllegalListException if the visualList is not a task list. + */ + public boolean togglePriorityMode() throws IllegalListException { + if (!(visualList instanceof TaskList)) { + throw new IllegalListException(); + } + + if (priorityMode.getValue()) { + toggleOffPriorityMode(); + } else { + toggleOnPriorityMode(); + } + return priorityMode.getValue(); + } + + /** + * Schedule a timer to off the priority mode. + * @param localDateTime the time at which the priority mode should be turned off. + */ + public void scheduleOffPriorityMode(LocalDateTime localDateTime) { + this.timer = new Timer(); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + Date date = Date.from(zdt.toInstant()); + timer.schedule(new TimerTask() { + @Override + public void run() { + systemToggle = true; + priorityExitStatus = PriorityExitStatus.PRIORITY_TIMEOUT; + toggleOffPriorityMode(); + } + }, date); + } + + private void getNextTask() { + Item head = sortedTask.peek(); + if (sortedTask.isEmpty() || head.getTask().get().isComplete()) { + systemToggle = true; + priorityExitStatus = PriorityExitStatus.ALL_TASK_COMPLETED; + toggleOffPriorityMode(); + return; + } + + if (visualList instanceof TaskList) { + TaskList result = new TaskList(); + result.add(head); + visualList = result; + } + } + + /** + * Method to close the priority mode thread. + */ + public void closePriorityModeThread() { + if (timer != null) { + timer.cancel(); + timer = null; + } + } + + /** + * Turns off the priority mode. + */ + private void toggleOffPriorityMode() { + closePriorityModeThread(); + + sortedTask = null; + focusMode.set(false); + if (visualList instanceof TaskList) { + this.visualList = taskList; + } + priorityMode.setValue(false); + } + + /** + * Turns on the priority mode. + */ + private void toggleOnPriorityMode() { + systemToggle = false; + priorityExitStatus = null; + priorityMode.setValue(true); + + populateQueue(); + + // should not be null as it is populated by the previous method + requireNonNull(sortedTask); + if (sortedTask.isEmpty()) { + priorityExitStatus = PriorityExitStatus.ALL_TASK_COMPLETED; + priorityMode.setValue(false); + } else { + getNextTask(); + } + } + + /** + * Helper method to create the priority queue and fill it up. + */ + private void populateQueue() { + sortedTask = new PriorityQueue((item1, item2) -> { + int result; + if ((result = TaskList.COMPARATOR.compare(item1, item2)) != 0) { + return result; + } else { + int index1 = taskList.indexOf(item1); + int index2 = taskList.indexOf(item2); + return Integer.compare(index1, index2); + } + }); + + sortedTask.addAll(taskList.filtered(x -> !x.getTask().get().isComplete())); + } + + /** + * Mark an item with a task as done or not done. + * @param index the index of the item to be marked as done or not done + * @param status the status of the item. True means that it is done and false mean it is not done. + * @return the item that is marked as done or not done. + * @throws IllegalListException if the operation is not done on a task list. + */ + public Item markComplete(int index, boolean status) throws IllegalListException { + if (!(visualList instanceof TaskList)) { + throw new IllegalListException(); + } + + Item item = visualList.get(index); + Task task = item.getTask().get(); + Task newTask = status ? task.markComplete() : task.markIncomplete(); + Item newItem = item.changeTask(newTask); + editItem(item, newItem); + return newItem; + } + + public EventList getEventList() { + return this.eventList; + } + + public Item getItem(int index) { + return this.visualList.get(index); + } + + public Item getLatestOccurredReminder() throws NoSuchElementException { + return activeReminders.getLatestOccurredReminder(); + } + + public SimpleBooleanProperty getPriorityMode() { + return this.priorityMode; + } + + public boolean isSystemToggle() { + return systemToggle; + } + + public PriorityExitStatus getExitStatus() { + return priorityExitStatus; + } + + public void toggleOnFocusMode() { + focusMode.set(true); + } + + public boolean isFocusMode() { + return focusMode.get(); + } + + public SimpleBooleanProperty getFocusMode() { + return focusMode; + } +} diff --git a/src/main/java/seedu/elisa/model/ItemStorage.java b/src/main/java/seedu/elisa/model/ItemStorage.java new file mode 100644 index 00000000000..5624e6b5b6b --- /dev/null +++ b/src/main/java/seedu/elisa/model/ItemStorage.java @@ -0,0 +1,175 @@ +package seedu.elisa.model; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.commons.exceptions.DuplicateItemException; +import seedu.elisa.commons.util.JsonUtil; + +/** + * The central storage of all the items in the program. + */ +public class ItemStorage { + private static final Logger logger = LogsCenter.getLogger(ItemModelManager.class); + + private ArrayList items = new ArrayList<>(); + + /** + * Adds an item to the item list. + * @param item the item to be added to the item list. + */ + public void add(Item item) throws DuplicateItemException { + if (items.contains(item)) { + throw new DuplicateItemException(); + } + items.add(item); + } + + /** + * add item into specified index + * */ + public void add(int targetIndex, Item item) throws DuplicateItemException { + if (items.contains(item)) { + throw new DuplicateItemException(); + } + items.add(targetIndex, item); + } + + /** + * Retrieve the item list. + * @return the item list. + */ + public ArrayList getItems() { + return this.items; + } + + /** + * Gets the item at the index. + * @param index the index of the item to be retrieved. + * @return the item at that index. + */ + public Item get(int index) { + return items.get(index); + } + + /** + * Return the size of the storage. + * @return the size of the storage as an integer. + */ + public int size() { + return items.size(); + } + + /** + * Returns if the storage already contains the item. + * @param item the item to be searched for + * @return + */ + public boolean contains(Item item) { + return items.contains(item); + } + + /** + * Remove the item from within the storage. + * @param item the item to be removed from the storage. + * @return + */ + public Item remove(Item item) { + items.remove(item); + return item; + } + + /** + * Returns the index of the item in the storage. If the item is not in the storage, + * a index of -1 is returned. + * @param item the item to be searched for. + * @return the index of the item in the storage. + */ + public int indexOf(Item item) { + return items.indexOf(item); + } + + /** + * Set the item at the specific index to a new item. + * @param index the index of the item to be replaced. + * @param newItem the item to replace the old item. + * @return + */ + public Item setItem(int index, Item newItem) { + return items.set(index, newItem); + } + + /** + * Converts the storage into a JSON string. + * @return the JSON representation of the storage. + * @throws JsonProcessingException + */ + public String toJson() throws JsonProcessingException { + return JsonUtil.toJsonString(items); + } + + /** + * Creates the item storage from a json string. + * @param jsonString the string representation of the item storage. + * @return the item storage with all items added + * @throws IOException when the file cannot be read from + * @throws DataConversionException when the item is not in a proper format + */ + public static ItemStorage fromJson(String jsonString) throws IOException, DataConversionException { + ItemStorage itemStorage = new ItemStorage(); + JsonNode node = JsonUtil.getObjectMapper().readTree(jsonString); + Iterator it = node.iterator(); + while (it.hasNext()) { + String json = it.next().toString(); + try { + Item item = Item.fromJson(json); + itemStorage.add(item); + } catch (NullPointerException e) { + throw new DataConversionException(e); + } catch (DuplicateItemException e) { + logger.log(Level.INFO, String.format("%s already exists. Skipping.", json)); + } catch (IllegalArgumentException e) { + logger.log(Level.WARNING, String.format("%s is corrupted and will be skipped.", json)); + } + } + return itemStorage; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else { + if (!(other instanceof ItemStorage)) { + return false; + } else { + return this.items.equals(((ItemStorage) other).items); + } + } + } + + /** + * Returns a deep copy of this item storage. + * @return deep copy of the item storage + */ + public ItemStorage deepCopy() { + ItemStorage itemStorage = new ItemStorage(); + for (Item i : items) { + try { + itemStorage.add(i.deepCopy()); + } catch (Exception e) { + // not supposed to happen + } + } + return itemStorage; + } +} diff --git a/src/main/java/seedu/elisa/model/JokeList.java b/src/main/java/seedu/elisa/model/JokeList.java new file mode 100644 index 00000000000..ff385b01136 --- /dev/null +++ b/src/main/java/seedu/elisa/model/JokeList.java @@ -0,0 +1,49 @@ +package seedu.elisa.model; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Random; + +/** + * List of jokes to pick and display + * */ + +public class JokeList { + private InputStream jokeFile = JokeList.class.getResourceAsStream("/documents/jokes.txt"); + private ArrayList jokes; + private Random rng; + + public JokeList() { + jokes = new ArrayList<>(); + rng = new Random(); + + BufferedReader r = null; + try { + r = new BufferedReader(new InputStreamReader(jokeFile, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + try { + String l; + while ((l = r.readLine()) != null) { + jokes.add(l); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns random joke from list + */ + public String getJoke() { + rng.setSeed(System.currentTimeMillis()); + int index = rng.nextInt(jokes.size()); + return jokes.get(index); + } +} diff --git a/src/main/java/seedu/elisa/model/PriorityExitStatus.java b/src/main/java/seedu/elisa/model/PriorityExitStatus.java new file mode 100644 index 00000000000..17e270cb978 --- /dev/null +++ b/src/main/java/seedu/elisa/model/PriorityExitStatus.java @@ -0,0 +1,9 @@ +package seedu.elisa.model; + +/** + * Enumeration containing the different ways in which one can exit the priority mode. + * Helps to personalize the feed back messages to the users. + */ +public enum PriorityExitStatus { + ALL_TASK_COMPLETED, PRIORITY_TIMEOUT +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/elisa/model/ReadOnlyUserPrefs.java similarity index 57% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/seedu/elisa/model/ReadOnlyUserPrefs.java index befd58a4c73..2c0b54c1d17 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/elisa/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package seedu.elisa.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import seedu.elisa.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getItemStorageFilePath(); } diff --git a/src/main/java/seedu/elisa/model/RescheduleTask.java b/src/main/java/seedu/elisa/model/RescheduleTask.java new file mode 100644 index 00000000000..b1d6a0289b9 --- /dev/null +++ b/src/main/java/seedu/elisa/model/RescheduleTask.java @@ -0,0 +1,101 @@ +package seedu.elisa.model; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.LinkedList; +import java.util.TimerTask; +import java.util.logging.Logger; + +import javafx.application.Platform; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.logic.LogicManager; + +/** + * Task to reschedule an event. + */ +public class RescheduleTask extends TimerTask { + private static LinkedList allTasks = new LinkedList<>(); + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + private ItemModel model; + private Item item; + private Event event; + private AutoReschedulePeriod period; + + /** + * Creates a RescheduleTask to reschedule this item automatically, in intervals of the given period. + * Requirements: Item must have an Event. + * @param item who's event is to be rescheduled + * @param period interval of rescheduling + * @param model which consist of this item + */ + public RescheduleTask(Item item, AutoReschedulePeriod period, ItemModel model) { + this.item = item; + this.event = item.getEvent().get(); + this.period = period; + this.model = model; + } + + /** + * Adds this {@code task} to the list of allTasks. + * @param task to be added + */ + public static void addToAllTasks(RescheduleTask task) { + allTasks.add(task); + } + + /** + * Removes the RescheduleTask of this event from the list of allTasks. + * @param event to be removed + */ + public static void removeFromAllTasks(Event event) { + for (RescheduleTask task : RescheduleTask.allTasks) { + if (task.getEvent().equals(event)) { + task.cancel(); + RescheduleTask.allTasks.remove(task); + } + } + } + + public LocalDateTime getStartTime() { + return event.getStartDateTime(); + } + + public long getLongPeriod() { + return period.getPeriod(); + } + + public Event getEvent() { + return this.event; + } + + /** + * Executes this task. + * Update this event's startDateTime with the new startDateTime, given the period of reccurence. + * Update the model with the new item. + * Refreshes the view on the Ui to reflect the new startDateTime of this event. + */ + public void run() { + logger.info("----------[INFO] " + "Reschedule runs again. Old event: " + event.toString()); + Item oldItem = item; + long period = event.getPeriod().getPeriod(); + LocalDateTime newStart = LocalDateTime.now().plusNanos(Duration.ofMillis(period).toNanos()); + Event newEvent = event.changeStartDateTime(newStart); + Item newItem = item.changeEvent(newEvent);; + + Platform.runLater(new Runnable() { + @Override + public void run() { + model.replaceItem(oldItem, newItem); + model.repopulateLists(); + model.setVisualizeList(model.getVisualList()); // to refresh the view + } + }); + + this.item = newItem; + this.event = newEvent; + + logger.info("-----------[INFO] " + "End of run. New event: " + newEvent.toString()); + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/elisa/model/UserPrefs.java similarity index 85% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/elisa/model/UserPrefs.java index 25a5fd6eab9..01ceda79022 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/elisa/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package seedu.elisa.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import seedu.elisa.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "itemstorage.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setItemStorageFilePath(newUserPrefs.getItemStorageFilePath()); } public GuiSettings getGuiSettings() { @@ -47,11 +47,11 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { + public Path getItemStorageFilePath() { return addressBookFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { + public void setItemStorageFilePath(Path addressBookFilePath) { requireNonNull(addressBookFilePath); this.addressBookFilePath = addressBookFilePath; } diff --git a/src/main/java/seedu/elisa/model/exceptions/IllegalListException.java b/src/main/java/seedu/elisa/model/exceptions/IllegalListException.java new file mode 100644 index 00000000000..08d4843731b --- /dev/null +++ b/src/main/java/seedu/elisa/model/exceptions/IllegalListException.java @@ -0,0 +1,7 @@ +package seedu.elisa.model.exceptions; + +/** + * The exception thrown when a command is executed on a list that it is not able to execute on. + */ +public class IllegalListException extends Exception { +} diff --git a/src/main/java/seedu/elisa/model/item/ActiveRemindersList.java b/src/main/java/seedu/elisa/model/item/ActiveRemindersList.java new file mode 100644 index 00000000000..ada108ad55c --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/ActiveRemindersList.java @@ -0,0 +1,46 @@ +package seedu.elisa.model.item; + +import java.util.Collection; +import java.util.NoSuchElementException; + +import javafx.beans.property.ListPropertyBase; +import seedu.elisa.commons.core.item.Item; + +/** + * TODO: Make this javadoc prettier. + * An ActiveReminderList that extends from ListPropertyBase in order to be observable. + */ +public class ActiveRemindersList extends ListPropertyBase { + + public ActiveRemindersList (ReminderList reminderList) { + super(reminderList); + } + + @Override + public Object getBean() { + return null; + } + + @Override + public String getName() { + return null; + } + + /** + * Adds a reminder to ActiveRemindersList + * + * @param reminders Collection of reminders to be added to the list. + */ + public synchronized void addReminders(Collection reminders) { + for (Item item:reminders) { + add(item); + } + } + + public Item getLatestOccurredReminder() throws NoSuchElementException { + if (size() <= 0) { + throw new NoSuchElementException("No reminders have occurred yet!"); + } + return get(size() - 1); + } +} diff --git a/src/main/java/seedu/elisa/model/item/CalendarList.java b/src/main/java/seedu/elisa/model/item/CalendarList.java new file mode 100644 index 00000000000..862d1baf1c0 --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/CalendarList.java @@ -0,0 +1,33 @@ +package seedu.elisa.model.item; + +import seedu.elisa.commons.core.item.Item; + +/** + * Object class to store all the items that are part of the calendars within the program. + */ +public class CalendarList extends VisualizeList { + public CalendarList() { + super(); + } + + @Override + public VisualizeList find(String[] searchString) { + return super.find(searchString, new CalendarList()); + } + + @Override + public VisualizeList deepCopy() { + return super.deepCopy(new CalendarList()); + } + + @Override + public VisualizeList sort() { + // calendar list is used in the panel and so their sorting will have no value + return this; + } + + public boolean belongToList(Item item) { + return item.hasEvent(); + } + +} diff --git a/src/main/java/seedu/elisa/model/item/EventList.java b/src/main/java/seedu/elisa/model/item/EventList.java new file mode 100644 index 00000000000..6fb0b9f3dad --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/EventList.java @@ -0,0 +1,47 @@ +package seedu.elisa.model.item; + +import java.util.List; + +import seedu.elisa.commons.core.item.Item; + +/** + * Object class to store all the items that are events within the program + */ +public class EventList extends VisualizeList { + public EventList() { + super(); + } + + public EventList(List list) { + super(list); + } + + /** + * Sorts the event list based on the date of the event. + * @return a sorted EventList of the current list + */ + public VisualizeList sort() { + EventList el = new EventList(list); + el.sort((item1, item2) -> item1.getEvent().get().getStartDateTime() + .compareTo(item2.getEvent().get().getStartDateTime())); + return el; + } + + /** + * Finds a substring within the description of an item. + * @param searchString a string to be search for within the description of an item + * @return a new EventList only containing the items that have the search string in their description + */ + public VisualizeList find(String[] searchString) { + return find(searchString, new EventList()); + } + + @Override + public VisualizeList deepCopy() { + return super.deepCopy(new EventList()); + } + + public boolean belongToList(Item item) { + return item.hasEvent(); + } +} diff --git a/src/main/java/seedu/elisa/model/item/FutureRemindersList.java b/src/main/java/seedu/elisa/model/item/FutureRemindersList.java new file mode 100644 index 00000000000..f31ab295959 --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/FutureRemindersList.java @@ -0,0 +1,18 @@ +package seedu.elisa.model.item; + +import java.util.ArrayList; + +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.ItemReminderDateTimeComparator; + +/** + * A data structure to hold Items with Reminders that have not yet been prompted to the user. + */ +public class FutureRemindersList extends ArrayList { + @Override + public boolean add(Item item) { + boolean result = super.add(item); + super.sort(new ItemReminderDateTimeComparator()); + return result; + } +} diff --git a/src/main/java/seedu/elisa/model/item/ReminderList.java b/src/main/java/seedu/elisa/model/item/ReminderList.java new file mode 100644 index 00000000000..0cb5503a3fe --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/ReminderList.java @@ -0,0 +1,48 @@ +package seedu.elisa.model.item; + +import java.util.List; + +import seedu.elisa.commons.core.item.Item; + +/** + * Object class to store all the items that are reminders within the program + */ +public class ReminderList extends VisualizeList { + + public ReminderList() { + super(); + } + + public ReminderList(List list) { + super(list); + } + + /** + * Sorts the Reminders by the date. + * @return a new ReminderList with the reminders within sorted. + */ + public VisualizeList sort() { + ReminderList rl = new ReminderList(list); + rl.sort((item1, item2) -> item1.getReminder().get().getDefaultDateTime() + .compareTo(item2.getReminder().get().getDefaultDateTime())); + return rl; + } + + /** + * Finds a substring within the description of an item. + * @param searchString a string to be search for within the description of an item + * @return a new ReminderList only containing the items that have the search string in their description + */ + public VisualizeList find(String[] searchString) { + return super.find(searchString, new ReminderList()); + } + + @Override + public VisualizeList deepCopy () { + return super.deepCopy(new ReminderList()); + } + + public boolean belongToList(Item item) { + return item.hasReminder(); + } +} diff --git a/src/main/java/seedu/elisa/model/item/TaskList.java b/src/main/java/seedu/elisa/model/item/TaskList.java new file mode 100644 index 00000000000..f840f42a675 --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/TaskList.java @@ -0,0 +1,61 @@ +package seedu.elisa.model.item; + +import java.util.Comparator; +import java.util.List; + +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Task; + +/** + * Object class to store all the items that are task within the program + */ +public class TaskList extends VisualizeList { + public static final Comparator COMPARATOR = (item1, item2) -> { + Task task1 = item1.getTask().get(); + Task task2 = item2.getTask().get(); + if (task1.isComplete() && !task2.isComplete()) { + return 1; + } else if (!task1.isComplete() && task2.isComplete()) { + return -1; + } else { + return item1.getPriority().compareTo(item2.getPriority()); + } + }; + + public TaskList() { + super(); + } + + public TaskList(List list) { + super(list); + } + + /** + * Sort the items in the task list. The items are first sorted by whether they are + * done or not and then by their priority. + * @return an VisualizeList of all the items sorted + */ + public VisualizeList sort() { + TaskList tl = new TaskList(list); + tl.sort(COMPARATOR); + return tl; + } + + /** + * Finds a substring within the description of an item. + * @param searchString a string to be search for within the description of an item + * @return a new TaskList containing only the items that have the search string in their description + */ + public VisualizeList find(String[] searchString) { + return find(searchString, new TaskList()); + } + + @Override + public VisualizeList deepCopy() { + return super.deepCopy(new TaskList()); + } + + public boolean belongToList(Item item) { + return item.hasTask(); + } +} diff --git a/src/main/java/seedu/elisa/model/item/VisualizeList.java b/src/main/java/seedu/elisa/model/item/VisualizeList.java new file mode 100644 index 00000000000..32200ebad92 --- /dev/null +++ b/src/main/java/seedu/elisa/model/item/VisualizeList.java @@ -0,0 +1,184 @@ +package seedu.elisa.model.item; + +import java.util.ArrayList; +import java.util.List; + +import javafx.collections.ModifiableObservableListBase; +import seedu.elisa.commons.core.item.Item; + +/** + * An object to hold items. Parent class for TaskList, EventList and ReminderList. + */ +public abstract class VisualizeList extends ModifiableObservableListBase { + protected ArrayList list; + + public VisualizeList() { + this.list = new ArrayList<>(); + } + + public VisualizeList(List list) { + this(); + this.list.addAll(list); + } + + /** + * Add an item into the list. The item will not be added if it is already in the list + * or it does not belong to the list. + * @param item the item to be added into the list + */ + public boolean add(Item item) { + if (hasItem(item) || !belongToList(item)) { + return false; + } else { + return super.add(item); + } + } + + /** + * add item into specified index + * */ + + public void addToIndex(int targetIndex, Item item) { + if (hasItem(item) || !belongToList(item)) { + return; + } else { + super.add(targetIndex, item); + } + } + + /** + * Check if the list contains the item. + * @param item the item to be checked for. + * @return true if the item is in the list, else return false. + */ + public boolean hasItem(Item item) { + return super.contains(item); + } + + @Override + public void doAdd(int index, Item item) { + list.add(index, item); + } + + @Override + public Item doSet(int index, Item item) { + return list.set(index, item); + } + + @Override + public Item doRemove(int index) { + return list.remove(index); + } + + /** + * Get the list of the item list. + * @return an ArrayList of all the items in the VisualizeList + */ + public ArrayList getList() { + return this.list; + } + + public abstract VisualizeList find(String[] searchString); + + /** + * Helper function to find an item based on their description. + * @param searchStrings an array of string to be search for within the description of an item + * @param il the item list that will hold the items that contain the string within its description + * @return the item list that was given with the found items added + */ + protected VisualizeList find(String[] searchStrings, VisualizeList il) { + for (String searchString : searchStrings) { + for (Item i : list) { + if (il.contains(i)) { + continue; + } + + if (i.getItemDescription().getDescription() + .toLowerCase().contains(searchString.toLowerCase())) { + il.add(i); + } + } + } + return il; + } + + /** + * Deep copy a list. + * @return a list with all the items within it a deep copy of their original item. + */ + public abstract VisualizeList deepCopy(); + + /** + * Helper function to return a deep copy of the list. + * @param vl the list to be returned + * @return + */ + protected VisualizeList deepCopy(VisualizeList vl) { + for (Item i : list) { + try { + vl.add(i.deepCopy()); + } catch (Exception e) { + // not supposed to happen + } + } + return vl; + } + + public int indexOf(Item item) { + return super.indexOf(item); + } + + public Item setItem(int index, Item item) { + return super.set(index, item); + } + + /** + * Returns the item at the index within the list. + * @param index the index of the item within the list + * @return the item that has that index in the list + */ + public Item get(int index) { + return list.get(index); + } + + /** + * The size of the list. + * @return the integer value of the size of the list + */ + @Override + public int size() { + return list.size(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else { + if (!(other instanceof VisualizeList)) { + return false; + } + + VisualizeList otherIl = (VisualizeList) other; + return this.list.equals(otherIl.list); + } + } + + /** + * Sorts the items in the list. + * @return the item list in the sorted order + */ + public abstract VisualizeList sort(); + + public void clear() { + super.clear(); + } + + /** + * Checks if an item belongs to this list. + * @param item the item to be check + * @return a boolean true if the item belong to this list and false otherwise + */ + public abstract boolean belongToList(Item item); + +} diff --git a/src/main/java/seedu/elisa/storage/GameStorage.java b/src/main/java/seedu/elisa/storage/GameStorage.java new file mode 100644 index 00000000000..f872d176f0c --- /dev/null +++ b/src/main/java/seedu/elisa/storage/GameStorage.java @@ -0,0 +1,78 @@ +package seedu.elisa.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; + +import java.nio.file.Path; +import java.util.TreeSet; + +import seedu.elisa.model.exceptions.IllegalListException; + +/** + * Game Storage class to store high score of game. + */ +public class GameStorage { + + private String filePath; + private File file; + private TreeSet scorelist; + + public GameStorage(Path filePath) { + this.filePath = filePath.toString(); + + scorelist = new TreeSet<>(); + } + + public TreeSet getScorelist() { + return this.scorelist; + } + + /** + * Method to update scorelist. + * @param score + */ + public void updateScoreList(int score) { + scorelist.add(score); + } + + /** + * Methods to save to file. + * @throws Exception + */ + public void save() throws Exception { + try { + FileWriter writer = new FileWriter(file); + for (Integer score: scorelist) { + writer.write(score + "\n"); + } + writer.close(); + } catch (Exception e) { + // This is not supposed to happen. + } + } + + /** + * Method to load the file. + * @throws Exception + */ + public void load() throws Exception { + try { + file = new File(filePath); + file.createNewFile(); + BufferedReader br = new BufferedReader(new FileReader(file)); + + String str; + + while ((str = br.readLine()) != null) { + int score = Integer.parseInt(str); + scorelist.add(score); + } + + } catch (FileNotFoundException e) { + throw new IllegalListException(); + } + } +} diff --git a/src/main/java/seedu/elisa/storage/ItemListStorage.java b/src/main/java/seedu/elisa/storage/ItemListStorage.java new file mode 100644 index 00000000000..f6f323f9f89 --- /dev/null +++ b/src/main/java/seedu/elisa/storage/ItemListStorage.java @@ -0,0 +1,33 @@ +package seedu.elisa.storage; + +import java.io.IOException; +import java.nio.file.Path; + +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.model.ItemStorage; + +/** + * Represents a storage for ELISA. + */ +public interface ItemListStorage { + + /** + * Returns the file path of the data file. + */ + Path getItemListFilePath(); + + /** + * Saves the given {@link ItemStorage} to the storage. + * @param itemStorage cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveItemStorage(ItemStorage itemStorage) throws IOException; + + /** + * @see #saveItemStorage(ItemStorage itemStorage) + */ + void saveItemStorage(ItemStorage itemStorage, Path filePath) throws IOException; + + ItemStorage toModelType() throws IOException, DataConversionException; + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/elisa/storage/JsonAdaptedTag.java similarity index 88% rename from src/main/java/seedu/address/storage/JsonAdaptedTag.java rename to src/main/java/seedu/elisa/storage/JsonAdaptedTag.java index 0df22bdb754..183874bbfb3 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/elisa/storage/JsonAdaptedTag.java @@ -1,10 +1,10 @@ -package seedu.address.storage; +package seedu.elisa.storage; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import seedu.elisa.commons.core.item.tag.Tag; +import seedu.elisa.commons.exceptions.IllegalValueException; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/seedu/elisa/storage/JsonItemStorage.java b/src/main/java/seedu/elisa/storage/JsonItemStorage.java new file mode 100644 index 00000000000..6a80db6d451 --- /dev/null +++ b/src/main/java/seedu/elisa/storage/JsonItemStorage.java @@ -0,0 +1,76 @@ +package seedu.elisa.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.commons.util.FileUtil; +import seedu.elisa.model.ItemStorage; + +/** + * A class that contains all the JSON representation of the string + */ +public class JsonItemStorage implements ItemListStorage { + + public static final String MESSAGE_DUPLICATE_ITEM = "Items list contains duplicate items"; + + private static final Logger logger = LogsCenter.getLogger(JsonItemStorage.class); + + private Path itemListFilePath; + + public JsonItemStorage (Path path) { + this.itemListFilePath = path; + } + + /** + * Returns the file path of the data file. + */ + public Path getItemListFilePath() { + return this.itemListFilePath; + } + + + /** + * Saves the given {@link ItemStorage} to the storage. + * @param itemStorage cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + public void saveItemStorage(ItemStorage itemStorage) throws IOException { + saveItemStorage(itemStorage, itemListFilePath); + } + + /** + * Saves the given {@link ItemStorage} to the storage. + * @param itemStorage cannot be null. + * @param filePath the path of the save file. Cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + public void saveItemStorage(ItemStorage itemStorage, Path filePath) throws IOException { + requireNonNull(itemStorage); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + FileUtil.writeToFile(filePath, itemStorage.toJson()); + } + + /** + * Converts this JSON item storage into the model's storage. + * @return an ItemStorage with all the items + * @throws IllegalValueException if there were any data constraints violated. + * @throws IOException if there are any problem with reading from the string. + */ + public ItemStorage toModelType() throws IOException, DataConversionException { + String jsonString = FileUtil.readFromFile(itemListFilePath); + try { + return ItemStorage.fromJson(jsonString); + } catch (DataConversionException e) { + logger.info("Data from save file corrupted."); + throw e; + } + } +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/elisa/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/elisa/storage/JsonUserPrefsStorage.java index bc2bbad84aa..79ab9c0209a 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/elisa/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package seedu.elisa.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.commons.util.JsonUtil; +import seedu.elisa.model.ReadOnlyUserPrefs; +import seedu.elisa.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/elisa/storage/Storage.java b/src/main/java/seedu/elisa/storage/Storage.java new file mode 100644 index 00000000000..ca4392fe536 --- /dev/null +++ b/src/main/java/seedu/elisa/storage/Storage.java @@ -0,0 +1,25 @@ +package seedu.elisa.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.model.ReadOnlyUserPrefs; +import seedu.elisa.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends ItemListStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getItemListFilePath(); + +} diff --git a/src/main/java/seedu/elisa/storage/StorageManager.java b/src/main/java/seedu/elisa/storage/StorageManager.java new file mode 100644 index 00000000000..d53a6fd3ff6 --- /dev/null +++ b/src/main/java/seedu/elisa/storage/StorageManager.java @@ -0,0 +1,71 @@ +package seedu.elisa.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.model.ItemStorage; +import seedu.elisa.model.ReadOnlyUserPrefs; +import seedu.elisa.model.UserPrefs; + +/** + * Manages storage of AddressBook data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private ItemListStorage itemListStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(ItemListStorage itemListStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.itemListStorage = itemListStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public Path getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ AddressBook methods ============================== + + @Override + public Path getItemListFilePath() { + return itemListStorage.getItemListFilePath(); + } + + + @Override + public void saveItemStorage(ItemStorage itemStorage) throws IOException { + saveItemStorage(itemStorage, itemListStorage.getItemListFilePath()); + } + + @Override + public void saveItemStorage(ItemStorage itemStorage, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + itemListStorage.saveItemStorage(itemStorage, filePath); + } + + public ItemStorage toModelType() throws IOException, DataConversionException { + return itemListStorage.toModelType(); + } + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/elisa/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/elisa/storage/UserPrefsStorage.java index 29eef178dbc..d8c66caeea5 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/elisa/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package seedu.elisa.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.elisa.commons.exceptions.DataConversionException; +import seedu.elisa.model.ReadOnlyUserPrefs; +import seedu.elisa.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.elisa.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link seedu.elisa.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/elisa/ui/CalendarPanel.java b/src/main/java/seedu/elisa/ui/CalendarPanel.java new file mode 100644 index 00000000000..8c697e12988 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/CalendarPanel.java @@ -0,0 +1,265 @@ +package seedu.elisa.ui; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.format.TextStyle; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; + +/** + * The calendar panel for Elisa + */ +public class CalendarPanel extends UiPart { + private static final String FXML = "CalendarPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(CalendarPanel.class); + private int year; + private int month; + + @FXML + private Label monthAndYear; + + @FXML + private GridPane calendarGrid; + + /** + * Creates a calendar panel base on the current date. + * @param visualList + */ + public CalendarPanel(ObservableList visualList) { + super(FXML); + LocalDateTime currentTime = LocalDateTime.now(); + this.month = currentTime.getMonthValue(); + this.year = currentTime.getYear(); + monthAndYear.setText(String.format("%s, %s", Month.of(month), String.valueOf(year))); + initializeCalendarGrid(); + loadData(visualList); + visualList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + System.out.println("Change"); + clearCells(); + generateDate(); + loadData(visualList); + } + }); + } + + private void initializeCalendarGrid() { + generateHeader(); + generateDate(); + } + + /** + * Load the events from the visualization list to the calendar. + * @param visualList the list containing all the data to be loaded from. + */ + private void loadData(ObservableList visualList) { + HashMap> monthEvents = new HashMap<>(); + for (Item item : visualList) { + if (!isMonth(item)) { + continue; + } else { + int day = getDate(item); + if (!monthEvents.containsKey(day)) { + monthEvents.put(day, new ArrayList()); + } + monthEvents.get(day).add(item); + } + } + + for (Integer i : monthEvents.keySet()) { + ArrayList temp = monthEvents.get(i); + int size = temp.size() - 1; + Label lbl = new Label(); + lbl.setPadding(new Insets(0, 5, 0, 5)); + Item item = temp.get(0); + if (size > 0) { + lbl.setText(String.format("%s + %d event(s)", item.getItemDescription().getDescription(), size)); + } else { + lbl.setText(String.format("%s", item.getItemDescription().getDescription())); + } + String priority = item.getPriority().toString(); + switch (priority) { + case "HIGH": + lbl.setStyle("-fx-background-color: red; -fx-background-radius: 15, 15, 15, 15"); + lbl.setTextFill(Color.WHITE); + break; + case "MEDIUM": + lbl.setStyle("-fx-background-color: orange; -fx-background-radius: 15, 15, 15, 15"); + break; + case "LOW": + lbl.setStyle("-fx-background-color: green; -fx-background-radius: 15, 15, 15, 15"); + break; + default: + } + Node node = calendarGrid.lookup("#" + Integer.toString(i)); + VBox pane = (VBox) node; + pane.getChildren().add(lbl); + } + + + /* + ObservableList eventList = visualList.filtered(x -> x.hasEvent()); + System.out.println("check here?"); + ObservableList monthEvent = eventList.filtered(x -> x.getEvent() + .get().getStartDateTime().getMonthValue() == month); + + HashMap checker = new HashMap<>(); + if (!monthEvent.isEmpty()) { + for (Item item: monthEvent) { + int date = item.getEvent().get().getStartDateTime().getDayOfMonth(); + if (checker.containsKey(date)) { + checker.put(date, checker.get(date) + 1); + if (checker.get(date) > 2) { + continue; + } + } else { + checker.put(date, 1); + } + Node node = calendarGrid.lookup("#" + Integer.toString(date)); + VBox pane = (VBox) node; + + Label lbl = new Label(); + lbl.setText(item.getItemDescription().toString()); + lbl.setPadding(new Insets(0, 5, 0, 5)); + + String priority = item.getPriority().toString(); + switch (priority) { + case "HIGH": + lbl.setStyle("-fx-background-color: red; -fx-background-radius: 15, 15, 15, 15"); + break; + case "MEDIUM": + lbl.setStyle("-fx-background-color: orange; -fx-background-radius: 15, 15, 15, 15"); + break; + case "LOW": + lbl.setStyle("-fx-background-color: green; -fx-background-radius: 15, 15, 15, 15"); + break; + default: + } + + if (node instanceof VBox) { + pane.getChildren().add(lbl); + } + } + } + */ + } + + /** + * Check if an item belongs to the current month. + * @param item the item to be checked for. + * @return a boolean value if the item is happening within the month. + */ + private boolean isMonth(Item item) { + if (!item.hasEvent()) { + return false; + } else { + return item.getEvent().get().getStartDateTime().getMonthValue() == month; + } + } + + private int getDate(Item item) { + return item.getEvent().get().getStartDateTime().getDayOfMonth(); + } + + /** + * Generates the header of the calendar + */ + private void generateHeader() { + for (int i = 0; i < 7; i++) { + VBox vPane = new VBox(); + vPane.getStyleClass().add("calendar_pane"); + GridPane.setVgrow(vPane, Priority.NEVER); + Label day = new Label(); + day.setAlignment(Pos.CENTER); + day.setText(DayOfWeek.of(i + 1).getDisplayName(TextStyle.SHORT, Locale.ENGLISH)); + vPane.getChildren().add(day); + calendarGrid.add(vPane, i, 0); + } + } + + /** + * Generates the date on the calendar base on the current month. + */ + private void generateDate() { + LocalDateTime startOfMonth = LocalDateTime.of(year, month, 1, 0, 0); + int firstDay = startOfMonth.getDayOfWeek().getValue(); + int daysInMonth = startOfMonth.getMonth().maxLength(); + if (year % 4 != 0 && (startOfMonth.getMonth() == Month.FEBRUARY)) { + daysInMonth = 28; + } + + double height = calendarGrid.getHeight(); + double maxHeight = height / 7; + + int offset = firstDay - 1; + int lblCount = 1; + + for (int i = 0; i < 7; i++) { + VBox vPane = new VBox(); + vPane.getStyleClass().add("calendar_pane"); + GridPane.setVgrow(vPane, Priority.ALWAYS); + if (i < offset) { + vPane.setStyle("-fx-background-color: #E9F2F5"); + } else { + vPane.setId(Integer.toString(lblCount)); + Label lbl = new Label(Integer.toString(lblCount)); + lbl.setPadding(new Insets(5)); + lbl.setStyle("-fx-text-fill:darkslategray"); + vPane.getChildren().add(lbl); + lblCount++; + } + calendarGrid.add(vPane, i, 1); + } + + for (int i = 2; i < 7; i++) { + for (int j = 0; j < 7; j++) { + VBox vPane = new VBox(); + vPane.getStyleClass().add("calendar_pane"); + GridPane.setVgrow(vPane, Priority.ALWAYS); + if (lblCount <= daysInMonth) { + vPane.setId(Integer.toString(lblCount)); + Label lbl = new Label(Integer.toString(lblCount)); + lbl.setPadding(new Insets(5)); + lbl.setStyle("-fx-text-fill:darkslategray"); + vPane.getChildren().add(lbl); + lblCount++; + } else { + vPane.setStyle("-fx-background-color: #E9F2F5"); + } + calendarGrid.add(vPane, j, i); + } + } + } + + /** + * Helper method to clear all the cells so that it can be updated. + */ + private void clearCells() { + ObservableList allCells = calendarGrid.getChildren(); + for (Node cell: allCells) { + if (GridPane.getRowIndex(cell) != 0) { + VBox pane = (VBox) cell; + pane.getChildren().clear(); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/elisa/ui/CommandBox.java similarity index 83% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/elisa/ui/CommandBox.java index 7d76e691f52..88290259f53 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/elisa/ui/CommandBox.java @@ -1,12 +1,12 @@ -package seedu.address.ui; +package seedu.elisa.ui; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.elisa.logic.commands.CommandResult; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -38,6 +38,8 @@ private void handleCommandEntered() { commandTextField.setText(""); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); + } catch (Exception e) { + e.printStackTrace(); } } @@ -69,9 +71,9 @@ public interface CommandExecutor { /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see seedu.elisa.logic.Logic#execute(String) */ - CommandResult execute(String commandText) throws CommandException, ParseException; + CommandResult execute(String commandText) throws Exception; } } diff --git a/src/main/java/seedu/elisa/ui/ElisaDialogBox.java b/src/main/java/seedu/elisa/ui/ElisaDialogBox.java new file mode 100644 index 00000000000..311c71ff17b --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ElisaDialogBox.java @@ -0,0 +1,34 @@ +package seedu.elisa.ui; + +import java.io.IOException; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; + + +/** + * This class is the Ui for UserDialogBox + */ +class ElisaDialogBox extends HBox { + + @FXML + private Label dialog; + + private ElisaDialogBox(String text) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/ElisaDialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); } + + static ElisaDialogBox getElisaDialog(String text) { + return new ElisaDialogBox(text); + } +} diff --git a/src/main/java/seedu/elisa/ui/ElisaReminderBox.java b/src/main/java/seedu/elisa/ui/ElisaReminderBox.java new file mode 100644 index 00000000000..1a74b7d9700 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ElisaReminderBox.java @@ -0,0 +1,35 @@ +package seedu.elisa.ui; + +import java.io.IOException; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; + + + +/** + * This class is the Ui for UserDialogBox + */ +class ElisaReminderBox extends HBox { + + @FXML + private Label dialog; + + private ElisaReminderBox(String text) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/ElisaReminderBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); } + + static ElisaReminderBox getElisaDialog(String text) { + return new ElisaReminderBox(text); + } +} diff --git a/src/main/java/seedu/elisa/ui/EventListCard.java b/src/main/java/seedu/elisa/ui/EventListCard.java new file mode 100644 index 00000000000..9a73ab238cc --- /dev/null +++ b/src/main/java/seedu/elisa/ui/EventListCard.java @@ -0,0 +1,96 @@ +package seedu.elisa.ui; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.item.Event; +import seedu.elisa.commons.core.item.Item; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class EventListCard 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 AddressBook level 4 + */ + + public final Item item; + + @FXML + private HBox cardPane; + @FXML + private Label date; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label priorityLabel; + @FXML + private Label time; + + public EventListCard(Item item, int displayedIndex) { + super(FXML); + this.item = item; + id.setText(displayedIndex + ". "); + description.setText(item.getItemDescription().toString()); + Event event = item.getEvent().get(); + date.setText(String.valueOf(event.getStartDateTime().getDayOfMonth()) + + " " + String.valueOf(event.getStartDateTime().getMonth()).substring(0, 3)); + time.setText(String.valueOf(event.getStartDateTime().format(DateTimeFormatter.ofPattern("HH:mm")))); + + String priority = item.getPriority().toString(); + priorityLabel.setText(priority); + priorityLabel.setAlignment(Pos.CENTER); + priorityLabel.setPadding(new Insets(5, 10, 5, 10)); + switch(priority) { + case "HIGH": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: red; " + + "-fx-background-radius: 15, 15, 15, 15"); + break; + case "MEDIUM": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: orange; " + + "-fx-background-radius: 15, 15, 15, 15"); + priorityLabel.setText("MED"); + break; + case "LOW": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: green; " + + "-fx-background-radius: 15, 15, 15, 15"); + break; + default: + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ItemCard)) { + return false; + } + + // state check + EventListCard card = (EventListCard) other; + return id.getText().equals(card.id.getText()) + && item.equals(card.item); + } +} diff --git a/src/main/java/seedu/elisa/ui/EventListPanel.java b/src/main/java/seedu/elisa/ui/EventListPanel.java new file mode 100644 index 00000000000..fd295162e20 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/EventListPanel.java @@ -0,0 +1,81 @@ +package seedu.elisa.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; + +/** + * Panel containing the list of items. + */ +public class EventListPanel extends UiPart { + + //We decided to set the number of items to scroll to 5 because that is + //the number of items displayed in the minimum window size. + private static final int NUM_OF_ITEMS_TO_SCROLL = 5; + private static int currentPosition; + + private static int itemSize; + private static final String FXML = "EventListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(EventListPanel.class); + + @FXML + private ListView eventListView; + + public EventListPanel(ObservableList itemList) { + super(FXML); + eventListView.setItems(itemList); + eventListView.setCellFactory(listView -> new EventListViewCell()); + itemSize = eventListView.getItems().size(); + eventListView.scrollTo(itemSize); + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Item} using a {@code ItemCard}. + */ + class EventListViewCell extends ListCell { + @Override + protected void updateItem(Item item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null || !item.hasEvent()) { + setGraphic(null); + setText(null); + } else { + setGraphic(new EventListCard(item, getIndex() + 1).getRoot()); + } + } + } + + /** + * Scrolls up. + */ + public void scrollUp() { + if (currentPosition - NUM_OF_ITEMS_TO_SCROLL <= 0) { + currentPosition = 0; + } else { + currentPosition = currentPosition - NUM_OF_ITEMS_TO_SCROLL; + } + eventListView.scrollTo(currentPosition); + + } + + /** + * Scrolls down. + */ + public void scrollDown() { + if (currentPosition + NUM_OF_ITEMS_TO_SCROLL >= itemSize) { + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } else { + currentPosition = currentPosition + NUM_OF_ITEMS_TO_SCROLL; + } + eventListView.scrollTo(currentPosition); + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/elisa/ui/ItemCard.java similarity index 59% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/elisa/ui/ItemCard.java index 0684b088868..56c02c48452 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/elisa/ui/ItemCard.java @@ -1,20 +1,18 @@ -package seedu.address.ui; - -import java.util.Comparator; +package seedu.elisa.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; +import seedu.elisa.commons.core.item.Item; /** * An UI component that displays information of a {@code Person}. */ -public class PersonCard extends UiPart { +public class ItemCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "ItemListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +22,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Item item; @FXML private HBox cardPane; @@ -41,17 +39,17 @@ public class PersonCard extends UiPart { @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + public ItemCard(Item item, int displayedIndex) { super(FXML); - this.person = person; + this.item = item; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + name.setText(item.getItemDescription().toString()); + //phone.setText(person.getPhone().value); + //address.setText(person.getAddress().value); + //email.setText(person.getEmail().value); + //person.getTags().stream() + // .sorted(Comparator.comparing(tag -> tag.tagName)) + // .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @Override @@ -62,13 +60,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof ItemCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + ItemCard card = (ItemCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && item.equals(card.item); } } diff --git a/src/main/java/seedu/elisa/ui/ItemListPanel.java b/src/main/java/seedu/elisa/ui/ItemListPanel.java new file mode 100644 index 00000000000..2289b393d15 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ItemListPanel.java @@ -0,0 +1,48 @@ +package seedu.elisa.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; + +/** + * Panel containing the list of items. + */ +public class ItemListPanel extends UiPart { + private static final String FXML = "ItemListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + + @FXML + private ListView itemListView; + + public ItemListPanel(ObservableList itemList) { + super(FXML); + itemListView.setItems(itemList); + itemListView.setCellFactory(listView -> new ItemListViewCell()); + itemListView.scrollTo(itemList.size()); + + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Item} using a {@code ItemCard}. + */ + class ItemListViewCell extends ListCell { + @Override + protected void updateItem(Item item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ItemCard(item, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/elisa/ui/MainWindow.java b/src/main/java/seedu/elisa/ui/MainWindow.java new file mode 100644 index 00000000000..6e558f60867 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/MainWindow.java @@ -0,0 +1,700 @@ +package seedu.elisa.ui; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.TreeSet; +import java.util.logging.Logger; + +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.effect.GaussianBlur; +import javafx.scene.effect.Glow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; +import javafx.scene.media.AudioClip; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.stage.Popup; +import javafx.stage.Stage; +import seedu.elisa.commons.core.GuiSettings; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.exceptions.IllegalValueException; +import seedu.elisa.game.GameLoop; +import seedu.elisa.game.Grid; +import seedu.elisa.game.Painter; +import seedu.elisa.game.Snake; +import seedu.elisa.logic.Logic; +import seedu.elisa.logic.commands.ClearScreenCommandResult; +import seedu.elisa.logic.commands.CloseCommand; +import seedu.elisa.logic.commands.CloseCommandResult; +import seedu.elisa.logic.commands.CommandResult; +import seedu.elisa.logic.commands.DownCommandResult; +import seedu.elisa.logic.commands.GameCommandResult; +import seedu.elisa.logic.commands.GameHardCommandResult; +import seedu.elisa.logic.commands.OpenCommandResult; +import seedu.elisa.logic.commands.PriorityCommand; +import seedu.elisa.logic.commands.ThemeCommandResult; +import seedu.elisa.logic.commands.UpCommandResult; +import seedu.elisa.logic.commands.exceptions.CommandException; +import seedu.elisa.logic.parser.exceptions.FocusModeException; +import seedu.elisa.logic.parser.exceptions.ParseException; +import seedu.elisa.model.item.CalendarList; +import seedu.elisa.model.item.EventList; +import seedu.elisa.model.item.ReminderList; +import seedu.elisa.model.item.TaskList; +import seedu.elisa.model.item.VisualizeList; +import seedu.elisa.storage.GameStorage; + + +/** + * The Main Window. Provides the basic application layout containing a menu bar + * and space where other JavaFX elements can be placed. + */ +public class MainWindow extends UiPart { + + private static final int WIDTH = 500; + private static final int HEIGHT = 500; + + private static final String FXML = "MainWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + private final Image redElisa = new Image(getClass().getClassLoader() + .getResource("images/FocusElisa.PNG").toString()); + private final Image blueElisa = new Image(getClass().getClassLoader() + .getResource("images/ElisaImageWithoutWords.PNG").toString()); + private final Path gamefilePath = Paths.get("data", "gamescore.json"); + private Stage primaryStage; + private Logic logic; + + // Elements for Game + private GameStorage gameStorage; + private GameLoop loop; + private Grid grid; + private GraphicsContext context; + private TreeSet scorelist; + + // Independent Ui parts residing in this Ui container + private EventListPanel eventListPanel; + private TaskListPanel taskListPanel; + private ReminderListPanel reminderListPanel; + private CalendarPanel calendarPanel; + private ResultDisplay resultDisplay; + private Popup popup; + + private String reminderAlarmUrl = getClass().getClassLoader().getResource("sounds/alertChime.mp3").toString(); + private AudioClip reminderAlarm = new AudioClip(reminderAlarmUrl); + + @FXML + private Scene scene; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private StackPane taskListPanelPlaceholder; + + @FXML + private StackPane eventListPanelPlaceholder; + + @FXML + private StackPane reminderListPanelPlaceholder; + + @FXML + private StackPane resultDisplayPlaceholder; + + @FXML + private StackPane calendarPanelPlaceholder; + + @FXML + private StackPane statusbarPlaceholder; + + @FXML + private StackPane openItemPlaceholder; + + @FXML + private TabPane viewsPlaceholder; + + @FXML + private ImageView elisaImage; + + @FXML + private Text elisaText; + + @FXML + private Text elisaDescription; + + @FXML + private Text elisaDescription2; + + private final Paint elisaTextBlueColor = elisaText.getFill(); + private final Paint elisaDescBlueColor = elisaDescription.getFill(); + private final Paint elisaTextRedColor = Paint.valueOf("ff8080"); + private final Paint elisaDescRedColor = Paint.valueOf("ffb4b4"); + + public MainWindow(Stage primaryStage, Logic logic) { + super(FXML, primaryStage); + + gameStorage = new GameStorage(gamefilePath); + try { + gameStorage.load(); + } catch (Exception e) { + e.printStackTrace(); + } + scorelist = gameStorage.getScorelist(); + scorelist.add(0); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + + // Configure the UI + setWindowDefaultSize(logic.getGuiSettings()); + + // Listen to changes in focus of stage + try { + primaryStage.focusedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue o, Boolean old, Boolean n) { + if (!primaryStage.focusedProperty().get() && popup != null) { + popup.hide(); + } else if (popup != null) { + popup.show(primaryStage); + } else { + // do nothing to the popup + } + } + }); + } catch (Throwable t) { + logger.warning("Error with adding listener to primary stage for popup"); + } + + //Listen to changes in tab selection + viewsPlaceholder.getSelectionModel().selectedItemProperty().addListener( + new ChangeListener() { + @Override + public void changed(ObservableValue ov, Tab t, Tab t1) { + try { + if (logic.isFocusMode() && !t1.getId().equalsIgnoreCase("T")) { + throw new FocusModeException(); + } + logic.getModel().setVisualList(t1.getId()); + updatePanels(logic.getVisualList()); + } catch (FocusModeException e) { + viewsPlaceholder.getSelectionModel().select(t); + resultDisplay.setFeedbackToUser(e.getMessage()); + } catch (IllegalValueException e) { + e.printStackTrace(); + } + } + } + ); + + logic.getPriorityMode().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + if (newValue) { + setRed(); + } else { + setBlue(); + if (logic.isSystemToggle()) { + Platform.runLater(() -> { + String feedback; + switch (logic.getExitStatus()) { + case PRIORITY_TIMEOUT: feedback = PriorityCommand.TIME_OUT; + break; + case ALL_TASK_COMPLETED: feedback = PriorityCommand.FINISHED_ALL_TASKS; + break; + default: + // will never reach here as there are only two cases + feedback = ""; + } + + resultDisplay.setFeedbackToUser(feedback); + updatePanels(logic.getVisualList()); + }); + } + } + } + }); + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + private void setRed() { + elisaImage.setImage(redElisa); + elisaText.setFill(elisaTextRedColor); + elisaText.setEffect(new Glow(0.2)); + elisaText.setStroke(elisaDescRedColor); + elisaDescription.setFill(elisaDescRedColor); + elisaDescription2.setFill(elisaDescRedColor); + } + + private void setBlue() { + elisaImage.setImage(blueElisa); + elisaText.setFill(elisaTextBlueColor); + elisaText.setEffect(null); + elisaDescription.setFill(elisaDescBlueColor); + elisaDescription2.setFill(elisaDescBlueColor); + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + updatePanels(logic.getVisualList()); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + + //Create a ListChangeListener for activeReminders + ListChangeListener activeRemindersListener = new ListChangeListener() { + @Override + public void onChanged(Change c) { + while (c.next()) { + createReminders(c); + } + } + + private void createReminders(Change c) { + if (c.getAddedSize() > 0) { + //Plays sound + reminderAlarm.play(30); + for (Item newItem : c.getAddedSubList()) { + Platform.runLater(() -> { + //Populate resultDisplay with reminder textbox + resultDisplay.setReminderToUser(newItem.getReminderMessage()); + }); + } + } + } + }; + + + //Binds a ListChangeListener to activeRemindersList + logic.getActiveRemindersListProperty().addListener(activeRemindersListener); + + CommandBox commandBox = new CommandBox(this::executeCommand); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + } + + /** + * Sets the default size based on {@code guiSettings}. + */ + private void setWindowDefaultSize(GuiSettings guiSettings) { + primaryStage.setHeight(guiSettings.getWindowHeight()); + primaryStage.setWidth(guiSettings.getWindowWidth()); + if (guiSettings.getWindowCoordinates() != null) { + primaryStage.setX(guiSettings.getWindowCoordinates().getX()); + primaryStage.setY(guiSettings.getWindowCoordinates().getY()); + } + } + + void show() { + primaryStage.show(); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), + (int) primaryStage.getX(), (int) primaryStage.getY()); + logic.setGuiSettings(guiSettings); + primaryStage.hide(); + } + + /** + * Switches the view. + * + * @param list + */ + private void handleSwitchView(VisualizeList list) { + if (list instanceof TaskList) { + viewsPlaceholder.getSelectionModel().select(0); + } else if (list instanceof EventList) { + viewsPlaceholder.getSelectionModel().select(1); + } else if (list instanceof ReminderList) { + viewsPlaceholder.getSelectionModel().select(2); + } else if (list instanceof CalendarList) { + viewsPlaceholder.getSelectionModel().select(3); + } + } + + /** + * Scrolls the target pane up + * + * @param pane + */ + private void scrollUp(String pane) { + switch(pane) { + case "resultDisplay": + resultDisplay.scrollUp(); + break; + case "tabPane": + if (logic.getVisualList() instanceof TaskList) { + taskListPanel.scrollUp(); + } else if (logic.getVisualList() instanceof EventList) { + eventListPanel.scrollUp(); + } else if (logic.getVisualList() instanceof ReminderList) { + reminderListPanel.scrollUp(); + } + break; + default: + } + } + + /** + * Scrolls the target pane down + * + * @param pane + */ + private void scrollDown(String pane) { + switch(pane) { + case "resultDisplay": + resultDisplay.scrollDown(); + break; + case "tabPane": + if (logic.getVisualList() instanceof TaskList) { + taskListPanel.scrollDown(); + } else if (logic.getVisualList() instanceof EventList) { + eventListPanel.scrollDown(); + } else if (logic.getVisualList() instanceof ReminderList) { + reminderListPanel.scrollDown(); + } + break; + default: + } + } + + /** + * Generates an appropriate popup with the given item, formatted accordingly + * @param item details to fill in the popup with + */ + private void openUp(Item item) { + Popup popup = new Popup(); + popup.getContent().add(new OpenItem(item).getRoot()); + popup.setHeight(1000); + popup.setWidth(500); + popup.setHideOnEscape(false); + + this.popup = popup; + popup.show(primaryStage); + } + + /** + * Carries out the operations to generate a popup which expands the view of the given item + * @param cr containing the item to open + * @return the result of executing this command + */ + private CommandResult executeOpen(CommandResult cr) { + CommandResult commandResult = cr; + if (popup != null) { + // Previous popup still exists + commandResult = new CommandResult("Hey, close the previous one first!"); + } else { + // Open new popup to show the item + OpenCommandResult result = (OpenCommandResult) commandResult; + openUp(result.getItem()); + viewsPlaceholder.setEffect(new GaussianBlur()); + } + return commandResult; + } + + /** + * Carries out operations to close the current popup. + * @param cr to carry out + * @return result of executing this command + */ + private CommandResult executeClose(CommandResult cr) { + CommandResult commandResult = cr; + if (popup == null) { + // Nothing to close + commandResult = new CloseCommandResult(CloseCommand.MESSAGE_FAILURE); + } else { + popup.hide(); + this.popup = null; + viewsPlaceholder.setEffect(null); + } + return commandResult; + } + + + /** + * Updates the panels to display the correct list of item. + */ + public void updatePanels(VisualizeList targetList) { + if (targetList instanceof TaskList) { + taskListPanel = new TaskListPanel(logic.getVisualList()); + taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + } else if (targetList instanceof EventList) { + eventListPanel = new EventListPanel(logic.getVisualList()); + eventListPanelPlaceholder.getChildren().add(eventListPanel.getRoot()); + } else if (targetList instanceof ReminderList) { + reminderListPanel = new ReminderListPanel(logic.getVisualList()); + reminderListPanelPlaceholder.getChildren().add(reminderListPanel.getRoot()); + } else { } + calendarPanel = new CalendarPanel(logic.getVisualList()); + calendarPanelPlaceholder.getChildren().add(calendarPanel.getRoot()); + } + + public TaskListPanel getTaskListPanel() { + return taskListPanel; + } + + public EventListPanel getEventListPanel() { + return eventListPanel; + } + + public ReminderListPanel getReminderListPanel() { + return reminderListPanel; + } + + /** + * Changes the theme + * + * @param theme + */ + private void changeTheme(String theme) { + scene.getStylesheets().remove(0); + scene.getStylesheets().remove(0); + switch(theme.trim()) { + case "white": + scene.getStylesheets().add("view/WhiteTheme.css"); + scene.getStylesheets().add("view/Extensions.css"); + break; + case "black": + scene.getStylesheets().add("view/DarkTheme.css"); + scene.getStylesheets().add("view/Extensions.css"); + break; + default: + } + } + + /** + * Executes the command and returns the result. + * + * @see seedu.elisa.logic.Logic#execute(String) + */ + @FXML + private CommandResult executeCommand(String commandText) throws Exception { + try { + resultDisplay.setMessageFromUser(commandText); + CommandResult commandResult = logic.execute(commandText); + logger.info("Result: " + commandResult.getFeedbackToUser()); + + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + if (commandResult.isExit()) { + handleExit(); + } + + handleSwitchView(logic.getVisualList()); + + if (commandResult instanceof UpCommandResult) { + scrollUp(commandResult.getPane()); + return commandResult; + } + + if (commandResult instanceof DownCommandResult) { + scrollDown(commandResult.getPane()); + return commandResult; + } + + if (commandResult instanceof ThemeCommandResult) { + changeTheme(commandResult.getTheme()); + return commandResult; + } + + if (commandResult instanceof OpenCommandResult) { + commandResult = executeOpen(commandResult); + return commandResult; + } + + if (commandResult instanceof CloseCommandResult) { + commandResult = executeClose(commandResult); + return commandResult; + } + + if (commandResult instanceof ClearScreenCommandResult) { + resultDisplay.clear(); + } + + if (commandResult instanceof GameCommandResult) { + startgame(primaryStage); + } + + if (commandResult instanceof GameHardCommandResult) { + startgameHard(primaryStage); + } + + updatePanels(logic.getVisualList()); + return commandResult; + } catch (CommandException | ParseException e) { + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } catch (Exception e) { + throw e; + } + } + + /** + * Starts easy mode of the game + * @param primaryStage + * @throws Exception + */ + public void startgame(Stage primaryStage) throws Exception { + + StackPane root = new StackPane(); + Canvas canvas = new Canvas(WIDTH, HEIGHT); + context = canvas.getGraphicsContext2D(); + + canvas.setFocusTraversable(true); + + gameCheck(canvas); + + resetgame(); + + root.getChildren().add(canvas); + + Scene gamescene = new Scene(root); + + primaryStage.setResizable(true); + primaryStage.setHeight(HEIGHT + 100); + primaryStage.setWidth(WIDTH + 100); + primaryStage.setTitle("Snake Game"); + primaryStage.setOnCloseRequest(e -> System.exit(0)); + primaryStage.setScene(gamescene); + primaryStage.show(); + + Thread t = new Thread(loop); + t.start(); + } + + /** + * Checks the key pressed in the game. + * @param canvas + */ + private void gameCheck(Canvas canvas) { + canvas.setOnKeyPressed(e -> { + Snake snake = grid.getSnake(); + if (loop.isKeyPressed()) { + return; + } + if (loop.isPaused()) { + scorelist.add(loop.getCurrentScore()); + gameStorage.updateScoreList(loop.getCurrentScore()); + try { + gameStorage.save(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + switch (e.getCode()) { + case UP: + snake.setUp(); + break; + case DOWN: + snake.setDown(); + break; + case LEFT: + snake.setLeft(); + break; + case RIGHT: + snake.setRight(); + break; + case ENTER: + if (loop.isPaused()) { + resetgame(); + Thread thread = new Thread(loop); + thread.start(); + } + break; + case ESCAPE: + exitgame(); + break; + case E: + if (loop.isPaused()) { + resetgameEasy(); + Thread thread = new Thread(loop); + thread.start(); + } + break; + case H: + if (loop.isPaused()) { + resetgameHard(); + Thread thread = new Thread(loop); + thread.start(); + } + break; + default: + } + }); + } + /** + * Starts hard mode of the game + * @param primaryStage + * @throws Exception + */ + public void startgameHard(Stage primaryStage) throws Exception { + StackPane root = new StackPane(); + Canvas canvas = new Canvas(WIDTH, HEIGHT); + context = canvas.getGraphicsContext2D(); + + canvas.setFocusTraversable(true); + + gameCheck(canvas); + + resetgameHard(); + root.getChildren().add(canvas); + + Scene gamescene = new Scene(root); + + primaryStage.setResizable(true); + primaryStage.setHeight(HEIGHT + 100); + primaryStage.setWidth(WIDTH + 100); + primaryStage.setTitle("Snake Game"); + primaryStage.setOnCloseRequest(e -> System.exit(0)); + primaryStage.setScene(gamescene); + primaryStage.show(); + + Thread t = new Thread(loop); + t.start(); + } + + private void resetgame() { + grid = new Grid(WIDTH, HEIGHT); + loop = new GameLoop(grid, context, scorelist); + Painter.paint(grid, context); + } + + private void resetgameEasy() { + resetgame(); + } + + private void resetgameHard() { + grid = new Grid(WIDTH, HEIGHT, true); + loop = new GameLoop(grid, context, scorelist); + Painter.paint(grid, context); + } + + private void exitgame() { + primaryStage.setScene(scene); + primaryStage.setHeight(primaryStage.getMinHeight()); + primaryStage.setWidth(primaryStage.getMinWidth()); + } +} diff --git a/src/main/java/seedu/elisa/ui/OpenItem.java b/src/main/java/seedu/elisa/ui/OpenItem.java new file mode 100644 index 00000000000..fe319f85e03 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/OpenItem.java @@ -0,0 +1,57 @@ +package seedu.elisa.ui; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.item.Item; + +/** + * Serves as a controller for the OpenItem.fxml. + */ +public class OpenItem extends UiPart { + private static final String FXML = "OpenItem.fxml"; + + @FXML + public final Item item; + + @FXML + private HBox cardPane; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label itemdetails; + @FXML + private Label completed; + @FXML + private Label priority; + @FXML + private Label startdate; + @FXML + private Label enddate; + @FXML + private Label reminderdate; + @FXML + private FlowPane tags; + + public OpenItem(Item item) { + super(FXML); + this.item = item; + description.setText(item.getItemDescription().toString()); + itemdetails.setText(item.toString()); + } + + public OpenItem(ObservableList items) { + super(FXML); + this.item = items.get(0); // only 1 item + description.setText(item.getItemDescription().toString()); + priority.setText("Priority: " + item.getPriority().toString()); + startdate.setText("Start Date: " + item.getEvent().get().getStartDateTime().toString()); + enddate.setText("End Date: " + item.getEvent().get().getEndDateTime().toString()); + } + +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/elisa/ui/PersonListPanel.java similarity index 60% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/seedu/elisa/ui/PersonListPanel.java index 1328917096e..5c063940320 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/elisa/ui/PersonListPanel.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.elisa.ui; import java.util.logging.Logger; @@ -7,8 +7,8 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; /** * Panel containing the list of persons. @@ -18,27 +18,27 @@ public class PersonListPanel extends UiPart { private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); @FXML - private ListView personListView; + private ListView personListView; - public PersonListPanel(ObservableList personList) { + public PersonListPanel(ObservableList personList) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new PersonListViewCell()); } /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code ItemCard}. */ - class PersonListViewCell extends ListCell { + class PersonListViewCell extends ListCell { @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); + protected void updateItem(Item item, boolean empty) { + super.updateItem(item, empty); - if (empty || person == null) { + if (empty || item == null) { setGraphic(null); setText(null); } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + setGraphic(new ItemCard(item, getIndex() + 1).getRoot()); } } } diff --git a/src/main/java/seedu/elisa/ui/ReminderListCard.java b/src/main/java/seedu/elisa/ui/ReminderListCard.java new file mode 100644 index 00000000000..ab23ebd0b36 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ReminderListCard.java @@ -0,0 +1,68 @@ +package seedu.elisa.ui; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.item.Item; +import seedu.elisa.commons.core.item.Reminder; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class ReminderListCard extends UiPart { + + private static final String FXML = "ReminderListCard.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 Item item; + + @FXML + private HBox cardPane; + @FXML + private Label date; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label time; + + public ReminderListCard(Item item, int displayedIndex) { + super(FXML); + this.item = item; + id.setText(displayedIndex + ". "); + description.setText(item.getItemDescription().toString()); + Reminder reminder = item.getReminder().get(); + date.setText(String.valueOf(reminder.getOccurrenceDateTime().getDayOfMonth()) + + " " + String.valueOf(reminder.getOccurrenceDateTime().getMonth()).substring(0, 3)); + time.setText(String.valueOf(reminder.getOccurrenceDateTime().format(DateTimeFormatter.ofPattern("HH:mm")))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ItemCard)) { + return false; + } + + // state check + ReminderListCard card = (ReminderListCard) other; + return id.getText().equals(card.id.getText()) + && item.equals(card.item); + } +} diff --git a/src/main/java/seedu/elisa/ui/ReminderListPanel.java b/src/main/java/seedu/elisa/ui/ReminderListPanel.java new file mode 100644 index 00000000000..0cfd8fca2cc --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ReminderListPanel.java @@ -0,0 +1,81 @@ +package seedu.elisa.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; + +/** + * Panel containing the list of items. + */ +public class ReminderListPanel extends UiPart { + + //We decided to set the number of items to scroll to 5 because that is + //the number of items displayed in the minimum window size. + private static final int NUM_OF_ITEMS_TO_SCROLL = 5; + private static final String FXML = "ReminderListPanel.fxml"; + + private static int currentPosition; + private static int itemSize; + + private final Logger logger = LogsCenter.getLogger(ReminderListPanel.class); + + @FXML + private ListView reminderListView; + + public ReminderListPanel(ObservableList itemList) { + super(FXML); + reminderListView.setItems(itemList); + reminderListView.setCellFactory(listView -> new ReminderListViewCell()); + itemSize = reminderListView.getItems().size(); + reminderListView.scrollTo(itemSize); + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Item} using a {@code ItemCard}. + */ + class ReminderListViewCell extends ListCell { + @Override + protected void updateItem(Item item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null || !item.hasReminder()) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ReminderListCard(item, getIndex() + 1).getRoot()); + } + } + } + + /** + * Scrolls up. + */ + public void scrollUp() { + if (currentPosition - NUM_OF_ITEMS_TO_SCROLL <= 0) { + currentPosition = 0; + } else { + currentPosition = currentPosition - NUM_OF_ITEMS_TO_SCROLL; + } + reminderListView.scrollTo(currentPosition); + + } + + /** + * Scrolls down. + */ + public void scrollDown() { + if (currentPosition + NUM_OF_ITEMS_TO_SCROLL >= itemSize) { + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } else { + currentPosition = currentPosition + NUM_OF_ITEMS_TO_SCROLL; + } + reminderListView.scrollTo(currentPosition); + } +} diff --git a/src/main/java/seedu/elisa/ui/ResultDisplay.java b/src/main/java/seedu/elisa/ui/ResultDisplay.java new file mode 100644 index 00000000000..c74412f5225 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/ResultDisplay.java @@ -0,0 +1,66 @@ +package seedu.elisa.ui; + +import static java.util.Objects.requireNonNull; + +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/** + * A ui for the status bar that is displayed at the header of the application. + */ +public class ResultDisplay extends UiPart { + + private static final String FXML = "ResultDisplay.fxml"; + + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + + public ResultDisplay() { + super(FXML); + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + } + + public void setFeedbackToUser(String feedbackToUser) { + requireNonNull(feedbackToUser); + dialogContainer.getChildren().addAll( + ElisaDialogBox.getElisaDialog(feedbackToUser) + ); + } + + public void setReminderToUser(String feedbackToUser) { + requireNonNull(feedbackToUser); + dialogContainer.getChildren().addAll( + ElisaReminderBox.getElisaDialog(feedbackToUser) + ); + } + + public void setMessageFromUser(String messageFromUser) { + requireNonNull(messageFromUser); + dialogContainer.getChildren().add( + UserDialogBox.getUserDialog(messageFromUser) + ); + } + + public void clear() { + dialogContainer.getChildren().clear(); + } + + /** + * Scrolls up. + */ + public void scrollUp() { + scrollPane.setVvalue(scrollPane.getVvalue() + 5); + System.out.println(scrollPane.getVvalue()); + } + + /** + * Scrolls down. + */ + public void scrollDown() { + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/elisa/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/elisa/ui/StatusBarFooter.java index 7e17911323f..dea7763414c 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/elisa/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.elisa.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/elisa/ui/TaskListCard.java b/src/main/java/seedu/elisa/ui/TaskListCard.java new file mode 100644 index 00000000000..35676765da4 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/TaskListCard.java @@ -0,0 +1,93 @@ +package seedu.elisa.ui; + +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.item.Item; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class TaskListCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Item item; + + @FXML + private HBox cardPane; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label priorityLabel; + @FXML + private ImageView statusIcon; + + public TaskListCard(Item item, int displayedIndex) { + super(FXML); + this.item = item; + id.setText(displayedIndex + ". "); + description.setText(item.getItemDescription().toString()); + if (item.getTask().get().isComplete()) { + statusIcon.setImage(new Image(TaskListCard.class.getResourceAsStream("/images/Completed.PNG"))); + } else { + statusIcon.setImage(new Image(TaskListCard.class.getResourceAsStream("/images/Uncompleted.PNG"))); + } + String priority = item.getPriority().toString(); + priorityLabel.setText(priority); + priorityLabel.setPadding(new Insets(5, 10, 5, 10)); + priorityLabel.setAlignment(Pos.CENTER); + switch(priority) { + case "HIGH": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: red; " + + "-fx-background-radius: 15, 15, 15, 15"); + break; + case "MEDIUM": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: orange; " + + "-fx-background-radius: 15, 15, 15, 15"); + priorityLabel.setText("MED"); + break; + case "LOW": + priorityLabel.setStyle("-fx-font-family: 'Arial Black'; " + + "-fx-background-color: green; " + + "-fx-background-radius: 15, 15, 15, 15"); + break; + default: + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ItemCard)) { + return false; + } + + // state check + TaskListCard card = (TaskListCard) other; + return id.getText().equals(card.id.getText()) + && item.equals(card.item); + } +} diff --git a/src/main/java/seedu/elisa/ui/TaskListPanel.java b/src/main/java/seedu/elisa/ui/TaskListPanel.java new file mode 100644 index 00000000000..670c739a2c8 --- /dev/null +++ b/src/main/java/seedu/elisa/ui/TaskListPanel.java @@ -0,0 +1,81 @@ +package seedu.elisa.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.core.item.Item; + +/** + * Panel containing the list of items. + */ +public class TaskListPanel extends UiPart { + + //We decided to set the number of items to scroll to 5 because that is + //the number of items displayed in the minimum window size. + private static final int NUM_OF_ITEMS_TO_SCROLL = 5; + private static final String FXML = "TaskListPanel.fxml"; + + private static int currentPosition; + private static int itemSize; + + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView taskListView; + + public TaskListPanel(ObservableList itemList) { + super(FXML); + taskListView.setItems(itemList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + itemSize = taskListView.getItems().size(); + taskListView.scrollTo(itemSize); + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Item} using a {@code ItemCard}. + */ + class TaskListViewCell extends ListCell { + @Override + protected void updateItem(Item item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null || !item.hasTask()) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskListCard(item, getIndex() + 1).getRoot()); + } + } + } + + /** + * Scrolls up. + */ + public void scrollUp() { + if (currentPosition - NUM_OF_ITEMS_TO_SCROLL <= 0) { + currentPosition = 0; + } else { + currentPosition = currentPosition - NUM_OF_ITEMS_TO_SCROLL; + } + taskListView.scrollTo(currentPosition); + + } + + /** + * Scrolls down. + */ + public void scrollDown() { + if (currentPosition + NUM_OF_ITEMS_TO_SCROLL >= itemSize) { + currentPosition = itemSize - NUM_OF_ITEMS_TO_SCROLL; + } else { + currentPosition = currentPosition + NUM_OF_ITEMS_TO_SCROLL; + } + taskListView.scrollTo(currentPosition); + } +} diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/elisa/ui/Ui.java similarity index 86% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/elisa/ui/Ui.java index 17aa0b494fe..27554b4e167 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/elisa/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.elisa.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/elisa/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/elisa/ui/UiManager.java index 876621d79b9..5ac6011675d 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/elisa/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.elisa.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; +import seedu.elisa.MainApp; +import seedu.elisa.commons.core.LogsCenter; +import seedu.elisa.commons.util.StringUtil; +import seedu.elisa.logic.Logic; /** * The manager of the UI component. @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/ElisaIcon.PNG"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/elisa/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/elisa/ui/UiPart.java index fc820e01a9c..907e8ef458b 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/elisa/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.elisa.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import seedu.elisa.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/java/seedu/elisa/ui/UserDialogBox.java b/src/main/java/seedu/elisa/ui/UserDialogBox.java new file mode 100644 index 00000000000..e7d7dffe8bc --- /dev/null +++ b/src/main/java/seedu/elisa/ui/UserDialogBox.java @@ -0,0 +1,34 @@ +package seedu.elisa.ui; + +import java.io.IOException; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; + +/** + * This class is the Ui for UserDialogBox + */ +class UserDialogBox extends HBox { + + @FXML + private Label dialog; + + private UserDialogBox(String text) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/UserDialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + } + + static UserDialogBox getUserDialog(String text) { + return new UserDialogBox(text); + } +} diff --git a/src/main/resources/documents/jokes.txt b/src/main/resources/documents/jokes.txt new file mode 100644 index 00000000000..37827a34ce6 --- /dev/null +++ b/src/main/resources/documents/jokes.txt @@ -0,0 +1,100 @@ +You. +I used to breed rabbits. Then I realized they can handle it themselves. +My dog is an awesome fashion adviser. Every time I ask him what I look like in my clothes, he says “WOW!” +Why are eggs not very much into jokes? Because they could crack up. +Barkeep: I’m sorry, we never serve time travelers. A time traveler walks into a bar. +What is written on a dentist’s grave? He’s filling his last cavity. +Meanwhile in a parallel universe: “Oh for God’s sake! Where are all these extra single socks coming from?!” +What do you call a boomerang that doesn't come back? A stick. +How many tickles does it take to make an octopus laugh? 10-tickles. +How does Moses make coffee? Hebrews it. +I’m positive I just lost an electron. Better keep an ion that. +How do you invite a dinosaur for lunch? Tea, Rex? +Who invented King Arthur’s round table? Sir Cumference. +I’m super friendly with 25 letters of the alphabet. I just don’t know why. +How do turtles communicate with each other? With shell phones. +What’s so great about whiteboards? If you think about it, they’re pretty re-markable! +Why did the mathematician work from home? Because he could only function in his domain. +Will glass coffins be a success? Remains to be seen. +You know what really bugs me? Insect puns. +I was wondering why the ball was getting bigger. Then it hit me. +"I have a split personality," said Tom, being frank. +What’s the difference between a hippo and a zippo? One is really heavy, and the other is a little lighter. +Last night, I dreamed I was swimming in an ocean of orange soda. But it was just a Fanta sea. +I can’t believe I got fired from the calendar factory. All I did was take a day off! +What do you call a bee that can’t make up its mind? A maybe. +I tried to sue the airline for losing my luggage. I lost my case. +A mean crook going down stairs = A condescending con, descending +How do you throw a space party? You planet. +what did the astronaut say when he was interviewed? No comet. +What washes up on tiny beaches? Microwaves. +I was going to make a chemistry joke, but all the good ones Argon. +Sleeping comes so naturally to me, I could do it with my eyes closed. +What do you call an alligator in a vest? An investigator +I used to be indecisive; now I'm not so sure. +I got a new thesaurus; not only is it bad, it’s bad. +Why are ghost such bad liars? Because they are easy to see through. +If I got 50 cents for every failed math exam, I’d have $ 6.30 now. +Do you know a tree’s favorite drink? Root beer! +What happens to a frog's car when it breaks down? It gets toad away. +Why couldn't the leopard play hide and seek? Because he was always spotted. +What starts with E, ends with E, and has only 1 letter in it? Envelope. +Whoever invented knock knock jokes should get a no bell prize. +Why is six afraid of seven? Now, I assume it's because seven is a prime number, and prime numbers can be intimidating. +What's the best thing about Switzerland? I don't know, but the flag is a big plus. +My wife told me I had to stop acting like a flamingo. So I had to put my foot down. +What's the difference between a hippo and a Zippo? One is really heavy, and the other is a little lighter. +Doctor, there's a patient on line 1 that says he's invisible. Well, tell him I can't see him right now. +Don't trust atoms, they make up everything. +To the mathematicians who thought of the idea of zero, thanks for nothing! +I named my dog 6 miles so I can tell people that I walk 6 miles every single day. +I ordered a chicken and an egg from Amazon. I'll let you know. +Pavlov walks into a bar. The phone rings, and he says, "Damn, I forgot to feed the dog." +I heard you like bad girls. I'm bad...at everything. +What's worse than raining cats and dogs? Hailing Taxis. +I hate Russian dolls, they're so full of themselves. +I recently decided to sell my vacuum cleaner as all it was doing was gathering dust. +As I watched the dog chasing his tail I thought "Dogs are easily amused", then I realized I was watching the dog chasing his tail. +Hedgehogs, eh? Why can't they just share the hedge? +Velcro - what a rip-off! +What do you call a patronising criminal going down a staircase? A condescending con descending. +"Dad, can you tell me what a solar eclipse is?" No sun. +I tried to sue the airline for misplacing my luggage. I lost my case. +I, for one, like Roman numerals. +I came up with a new word yesterday. Plagiarism. +A sandwich walks into a bar. The barman says, ‘Sorry we don’t serve food in here.' +The depressing thing about tennis is that no matter how much I play, I'll never be as good as a wall. I played a wall once. They're relentless. +Escalators don’t break down… they just turn into stairs +I intend to live forever… or die trying. +I used to think the brain was the most important organ. Then I thought, look what’s telling me that. +Why aren’t koalas actual bears? They don’t meet the koalafications. +As a scarecrow, people say I’m outstanding in my field. But hay, it’s in my jeans. +What’s a pirates favorite letter? You think it’s R but it be the C. +Knowledge is knowing a tomato is a fruit; Wisdom is not putting it in a fruit salad. +Haikus are easy. But sometimes they don’t make sense. Refrigerator. +I don’t have an attitude; I have a personality you can’t handle. +Hippopotomonstrosesquippedaliophobia: Fear of long words. +Discretion is being able to raise your eyebrow instead of your voice. +I have to exercise early in the morning before my brain figures out what I’m doing. +If the number 2 pencil is the most popular, why is it still number 2? +We are all time travelers moving at the speed of exactly 60 minutes per hour +There are three types of people: those who can count and those who can’t. +Leopards are terrible at hide-and-seek because they’re always spotted. +There was a kidnapping on a school bus but it’s fine. He woke up. +One bird can’t make a pun. But toucan. +Thanks for explaining the word “many” to me — it means a lot. +What’s purple and 5,000 miles long? The Grape Wall of China. +When does a joke become a Dad joke? When it’s fully groan. +I applied for a job at the local restaurant. I’m still waiting. +Did you hear about those new reversible jackets? I’m excited to see how they turn out. +Do I like wind turbines? Yes, I’m a big fan. +What do you call a pile of kittens A meowntain. +What do you call a baby monkey? A Chimp off the old block. +Why can't you give Elsa a balloon? Because she will 'Let it go' 'Into the unknown'. +What do you call an alligator in a vest? An Investigator +What do you call a laughing jar of mayonnaise? LMAYO +What did the baby corn say to the mama corn? "Where's Popcorn?" +Why did the birdie go to the hospital? To get a tweetment. +Can a kangaroo jump higher than the Empire State Building? Of course. The Empire State Building can't jump. +Instead of "the John," I call my toilet "the Jim." That way it sounds better when I say I go to the Jim first thing every morning. +The energizer bunny was arrested on a charge of battery. diff --git a/src/main/resources/images/Completed.PNG b/src/main/resources/images/Completed.PNG new file mode 100644 index 00000000000..fa7efce5bbe Binary files /dev/null and b/src/main/resources/images/Completed.PNG differ diff --git a/src/main/resources/images/ElisaIcon.PNG b/src/main/resources/images/ElisaIcon.PNG new file mode 100644 index 00000000000..66db0e93317 Binary files /dev/null and b/src/main/resources/images/ElisaIcon.PNG differ diff --git a/src/main/resources/images/ElisaImage.PNG b/src/main/resources/images/ElisaImage.PNG new file mode 100644 index 00000000000..f6958294c12 Binary files /dev/null and b/src/main/resources/images/ElisaImage.PNG differ diff --git a/src/main/resources/images/ElisaImageWithoutWords.PNG b/src/main/resources/images/ElisaImageWithoutWords.PNG new file mode 100644 index 00000000000..928a882087d Binary files /dev/null and b/src/main/resources/images/ElisaImageWithoutWords.PNG differ diff --git a/src/main/resources/images/ElisaName.PNG b/src/main/resources/images/ElisaName.PNG new file mode 100644 index 00000000000..98b8344cc7a Binary files /dev/null and b/src/main/resources/images/ElisaName.PNG differ diff --git a/src/main/resources/images/EventIcon.PNG b/src/main/resources/images/EventIcon.PNG new file mode 100644 index 00000000000..8a852383dcd Binary files /dev/null and b/src/main/resources/images/EventIcon.PNG differ diff --git a/src/main/resources/images/FocusElisa.PNG b/src/main/resources/images/FocusElisa.PNG new file mode 100644 index 00000000000..83a51365457 Binary files /dev/null and b/src/main/resources/images/FocusElisa.PNG differ diff --git a/src/main/resources/images/Uncompleted.PNG b/src/main/resources/images/Uncompleted.PNG new file mode 100644 index 00000000000..827db4a8384 Binary files /dev/null and b/src/main/resources/images/Uncompleted.PNG differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.PNG similarity index 100% rename from src/main/resources/images/address_book_32.png rename to src/main/resources/images/address_book_32.PNG diff --git a/src/main/resources/images/calendar.png b/src/main/resources/images/calendar.PNG similarity index 100% rename from src/main/resources/images/calendar.png rename to src/main/resources/images/calendar.PNG diff --git a/src/main/resources/images/clock.png b/src/main/resources/images/clock.PNG similarity index 100% rename from src/main/resources/images/clock.png rename to src/main/resources/images/clock.PNG diff --git a/src/main/resources/images/fail.png b/src/main/resources/images/fail.PNG similarity index 100% rename from src/main/resources/images/fail.png rename to src/main/resources/images/fail.PNG diff --git a/src/main/resources/images/help_icon.png b/src/main/resources/images/help_icon.PNG similarity index 100% rename from src/main/resources/images/help_icon.png rename to src/main/resources/images/help_icon.PNG diff --git a/src/main/resources/images/info_icon.png b/src/main/resources/images/info_icon.PNG similarity index 100% rename from src/main/resources/images/info_icon.png rename to src/main/resources/images/info_icon.PNG diff --git a/src/main/resources/sounds/alertChime.mp3 b/src/main/resources/sounds/alertChime.mp3 new file mode 100644 index 00000000000..4c18fd841c7 Binary files /dev/null and b/src/main/resources/sounds/alertChime.mp3 differ diff --git a/src/main/resources/view/CalendarPanel.fxml b/src/main/resources/view/CalendarPanel.fxml new file mode 100644 index 00000000000..ffeeac169f8 --- /dev/null +++ b/src/main/resources/view/CalendarPanel.fxml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..eb5c967473a 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -3,13 +3,6 @@ background-color: #383838; /* Used in the default.html file */ } -.label { - -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; - -fx-opacity: 0.9; -} - .label-bright { -fx-font-size: 11pt; -fx-font-family: "Segoe UI Semibold"; @@ -29,62 +22,37 @@ -fx-font-family: "Segoe UI Semibold"; } -.tab-pane { - -fx-padding: 0 0 0 1; +.tab-pane .tab-header-area .tab-header-background { + -fx-opacity: 0; } -.tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; - -fx-min-height: 0; - -fx-max-height: 0; +.tab-pane { + -fx-tab-min-width:90px; } -.table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; - -fx-table-cell-border-color: transparent; - -fx-table-header-border-color: transparent; - -fx-padding: 5; +.tab { + -fx-background-insets: 0 1 0 1,0,0; } -.table-view .column-header-background { - -fx-background-color: transparent; -} +.tab-pane .tab { + -fx-background-color: #3c3c3c; -.table-view .column-header, .table-view .filler { - -fx-size: 35; - -fx-border-width: 0 0 1 0; - -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; - -fx-border-insets: 0 10 1 0; } -.table-view .column-header .label { - -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-alignment: center-left; - -fx-opacity: 1; +.tab-pane .tab:selected { + -fx-background-color: #e6e6e6; } -.table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; -} - -.split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; +.tab .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: #828282; + -fx-font-size: 12px; + -fx-font-weight: bold; } -.split-pane { - -fx-border-radius: 1; - -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); +.tab:selected .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: #00A5F0; } .list-view { @@ -120,6 +88,11 @@ -fx-text-fill: white; } +.list-cell:empty { + /* Empty cells will not have alternating colours */ + -fx-background: #383838; +} + .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; @@ -132,6 +105,18 @@ -fx-text-fill: #010504; } +.event_label { + -fx-font-family: "Arial Black"; + -fx-font-size: 20px; + -fx-text-fill: #00A5F0 !important; +} + +.eventtime_label { + -fx-font-family: "Arial Black"; + -fx-font-size: 18px; + -fx-text-fill: #8de7fd !important; +} + .stack-pane { -fx-background-color: derive(#1d1d1d, 20%); } @@ -142,10 +127,6 @@ -fx-border-top-width: 1px; } -.status-bar { - -fx-background-color: derive(#1d1d1d, 30%); -} - .result-display { -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; @@ -155,23 +136,11 @@ .result-display .label { -fx-text-fill: black !important; + -fx-font-family: "Comic Sans MS" !important; } -.status-bar .label { - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-padding: 4px; - -fx-pref-height: 30px; -} - -.status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); - -fx-border-width: 1px; -} - -.status-bar-with-border .label { - -fx-text-fill: white; +.dialog-container { + -fx-background-color: #383838; } .grid-pane { @@ -184,103 +153,6 @@ -fx-background-color: derive(#1d1d1d, 30%); } -.context-menu { - -fx-background-color: derive(#1d1d1d, 50%); -} - -.context-menu .label { - -fx-text-fill: white; -} - -.menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-opacity: 0.9; -} - -.menu .left-container { - -fx-background-color: black; -} - -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ -.button { - -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; - -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; - -fx-background-insets: 0 0 0 0, 0, 1, 2; -} - -.button:hover { - -fx-background-color: #3a3a3a; -} - -.button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; -} - -.button:focused { - -fx-border-color: white, white; - -fx-border-width: 1, 1; - -fx-border-style: solid, segments(1, 1); - -fx-border-radius: 0, 0; - -fx-border-insets: 1 1 1 1, 0; -} - -.button:disabled, .button:default:disabled { - -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; -} - -.button:default { - -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; -} - -.button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); -} - -.dialog-pane { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.label.content { - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-text-fill: white; -} - -.dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); -} - -.dialog-pane:header *.header-panel *.label { - -fx-font-size: 18px; - -fx-font-style: italic; - -fx-fill: white; - -fx-text-fill: white; -} - .scroll-bar { -fx-background-color: derive(#1d1d1d, 20%); } @@ -307,6 +179,27 @@ -fx-padding: 8 1 8 1; } +.calendar_pane { + -fx-background-color: white; + -fx-border-style: dashed; + -fx-border-width: .5; + -fx-border-color: gray; +} + +.calendar_label { + -fx-text-fill: white; +} + +.elisa_dialog { + -fx-background-color: #00A5F0; + -fx-background-radius: 20, 20, 20, 20; +} + +.user_dialog { + -fx-background-color: white; + -fx-background-radius: 20, 20, 20, 20; +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; @@ -328,25 +221,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { - -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); -} - #resultDisplay .content { -fx-background-color: transparent, #383838, transparent, #383838; -fx-background-radius: 0; } - -#tags { - -fx-hgap: 7; - -fx-vgap: 3; -} - -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; - -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; -} diff --git a/src/main/resources/view/ElisaDialogBox.fxml b/src/main/resources/view/ElisaDialogBox.fxml new file mode 100644 index 00000000000..463fad2c095 --- /dev/null +++ b/src/main/resources/view/ElisaDialogBox.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ElisaReminderBox.fxml b/src/main/resources/view/ElisaReminderBox.fxml new file mode 100644 index 00000000000..25e458aab27 --- /dev/null +++ b/src/main/resources/view/ElisaReminderBox.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/EventListCard.fxml b/src/main/resources/view/EventListCard.fxml new file mode 100644 index 00000000000..b0cc52f52d0 --- /dev/null +++ b/src/main/resources/view/EventListCard.fxml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/EventListPanel.fxml b/src/main/resources/view/EventListPanel.fxml new file mode 100644 index 00000000000..bc1df705ab1 --- /dev/null +++ b/src/main/resources/view/EventListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..414e95396cd 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -3,11 +3,6 @@ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ } -.list-cell:empty { - /* Empty cells will not have alternating colours */ - -fx-background: #383838; -} - .tag-selector { -fx-border-width: 1; -fx-border-color: white; diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml deleted file mode 100644 index a16c28524ff..00000000000 --- a/src/main/resources/view/HelpWindow.fxml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/ItemListCard.fxml b/src/main/resources/view/ItemListCard.fxml new file mode 100644 index 00000000000..280d6b4b5eb --- /dev/null +++ b/src/main/resources/view/ItemListCard.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ItemListPanel.fxml b/src/main/resources/view/ItemListPanel.fxml new file mode 100644 index 00000000000..a1b4f8841d2 --- /dev/null +++ b/src/main/resources/view/ItemListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..61a90a20740 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,58 +3,126 @@ - - - - + + + + + - + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/OpenItem.fxml b/src/main/resources/view/OpenItem.fxml new file mode 100644 index 00000000000..5e00bb41e5a --- /dev/null +++ b/src/main/resources/view/OpenItem.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ReminderListCard.fxml b/src/main/resources/view/ReminderListCard.fxml new file mode 100644 index 00000000000..6d9cdb1d361 --- /dev/null +++ b/src/main/resources/view/ReminderListCard.fxml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ReminderListPanel.fxml b/src/main/resources/view/ReminderListPanel.fxml new file mode 100644 index 00000000000..8bb822f5d2a --- /dev/null +++ b/src/main/resources/view/ReminderListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56..42b6eee0c42 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -1,9 +1,30 @@ - - -