diff --git a/.gitignore b/.gitignore index 5e59b862ba4..92d241ecd42 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,9 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store + +# Poster Storage +src/main/resources/images/posters/ + +# Ui Poster to prevent bugs +docs/images/Ui.png diff --git a/README.adoc b/README.adoc index d34211c9341..96748b3f593 100644 --- a/README.adoc +++ b/README.adoc @@ -1,36 +1,57 @@ -= Address Book (Level 3) += EzWatchList 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]] +:imagesDir: /docs/images +https://travis-ci.org/AY1920S1-CS2103T-F13-4/main[image:https://travis-ci.org/AY1920S1-CS2103T-F13-4/main.svg?branch=master[Build Status]] +https://coveralls.io/github/AY1920S1-CS2103T-F13-4/main?branch=master[image:https://coveralls.io/repos/github/AY1920S1-CS2103T-F13-4/main/badge.svg?branch=master[Coverage Status]] -ifdef::env-github[] -image::docs/images/Ui.png[width="600"] -endif::[] +== Introduction -ifndef::env-github[] -image::images/Ui.png[width="600"] -endif::[] +++++ +

+

+Ui.png +

-* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. +++++ + +Tired of using multiple sources on the internet to keep track of all your movies and tv shows that you want to watch? EzWatchlist is a solution to your entertainment problems! + +EzWatchlist is a desktop application for cinepliles to manage their favourite movies and tv series with a clean and intuitive interface. +It was built for users who enjoy using a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). + +=== Features + +Spending too much time looking for shows? + +* Get recommendations for shows by using our https://ay1920s1-cs2103t-f13-4.github.io/main/UserGuide.html#movie-and-tv-show-recommendations[recommendations feature] + +Forgetting the shows you want to watch? + +* Keep track of shows with our inbuilt https://ay1920s1-cs2103t-f13-4.github.io/main/UserGuide.html#watchlist-page[watchlist.] + +Curious about your watching habits? + +* Check out our https://ay1920s1-cs2103t-f13-4.github.io/main/UserGuide.html#statistics-page[statistics page] + +More features + +* Integrated with an online database of shows, allowing you to search and get information about shows. +* Will work and function even without internet. +* No installation required. +* Auto-saves. + +Excited to use it? Head straight to our <> for a more detailed explanation of our application. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements - -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. -* Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] +* An Adressbook project this application was built upon created by https://se-education.org[SE-EDU initiative] +* Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5], https://www.themoviedb.org/documentation/api[TMDB api], https://github.com/holgerbrandl/themoviedbapi[Java Wrapper of TMDB] == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 93029ef8262..69c91452d39 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.ezwatchlist.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -23,6 +23,10 @@ targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { + url "https://jcenter.bintray.com" + } + jcenter() } checkstyle { @@ -57,6 +61,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-swing', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-swing', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-swing', 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' @@ -64,10 +71,14 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + + compile group: 'info.movito', name: 'themoviedbapi', version:'1.10' + compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.28' + compile group: 'ch.qos.logback', name:'logback-core', version: '1.0.9' } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'ezwatchlist.jar' destinationDir = file("${buildDir}/jar/") } @@ -133,9 +144,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-name': 'EzWatchlist', + 'site-githuburl': 'https://github.com/AY1920S1-CS2103T-F13-4/main', ] options['template_dirs'].each { diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..5971b6c86f8 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,51 @@ :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.}_ + -{empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +Ezwatchlist was developed by a group of students based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Caleb Goh Ee Gen +image::heze8.png[width="150", align="left"] +{empty} [https://github.com/heze8[github]] [<>] -Role: Project Advisor +Role: Team Lead + +Responsibilities: Online database API, Recommendations, Image Retrieval ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Wu Xia +image::tswuxia.png[width="150", align="left"] +{empty}[http://github.com/tswuxia[github]] [<>] -Role: Team Lead + +Role: Code Quality + Responsibilities: UI ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Chiang Jiajun Jared +image::jcjjjared.png[width="150", align="left"] +{empty}[http://github.com/jcjjjared[github]] [<>] -Role: Developer + -Responsibilities: Data +Role: Integration + +Responsibilities: Main Logic ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Wong Chuan Kai +image::wongchuankai.png[width="150", align="left"] +{empty}[http://github.com/wongchuankai[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Testing + +Responsibilities: Component Logic ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Michelle Yong Kai Wen +image::michelleykw.png[width="150", align="left"] +{empty}[http://github.com/michelleykw[github]] [<>] -Role: Developer + -Responsibilities: UI +Role: Documentation + +Responsibilities: Search and Tag Logic ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..f3d357fe933 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,6 @@ :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-F13-4/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 `ezwatchlist [at] gmail.com` diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc index 2aa5a6bc0c1..f2cac163aef 100644 --- a/docs/DevOps.adoc +++ b/docs/DevOps.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Dev Ops += EzWatchlist - 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-F13-4/main/tree/master == 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}/src/main/java/seedu/ezwatchlist/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, EzWatchlist 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..3a758bba1bc 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += EzWatchlist - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,14 +12,17 @@ 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-F13-4/main/tree/master +:icons: font -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team CS2103T-F13-4`      Since: `Sept 2019`      Licence: `MIT` == Setting up Refer to the guide <>. +{sp} + + == Design [[Design-Architecture]] @@ -34,7 +37,7 @@ The *_Architecture Diagram_* given above explains the high-level design of the A The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. Refer to the <> to learn how to create and edit diagrams. -`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has two classes called link:{repoURL}/src/main/java/seedu/EzWatchlist/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/EzWatchlist/MainApp.java[`MainApp`]. It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup method where necessary. @@ -44,14 +47,15 @@ The following class plays an important role at the architecture level: * `LogsCenter` : Used by many classes to write log messages to the App's log file. -The rest of the App consists of four components. +The rest of the App consists of five components. * <>: The UI of the App. * <>: The command executor. * <>: Holds the data of the App in-memory. * <>: Reads data from, and writes data to, the hard disk. +* <>: Access data from an online database about Movies and Tv Shows. -Each of the four components +Each of the five components * Defines its _API_ in an `interface` with the same name as the Component. * Exposes its functionality using a `{Component Name}Manager` class. @@ -71,15 +75,16 @@ image::ArchitectureSequenceDiagram.png[] The sections below give more details of each component. + [[Design-Ui]] === UI component .Structure of the UI Component image::UiClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/seedu/EzWatchlist/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`, `ShowListPanel`, `StatusBarFooter` 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,6 +93,7 @@ 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. + [[Design-Logic]] === Logic component @@ -96,11 +102,11 @@ The `UI` component, image::LogicClassDiagram.png[] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/seedu/EzWatchlist/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `WatchListParser` 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 `Model` (e.g. adding a show). . 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. @@ -111,23 +117,24 @@ 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. + [[Design-Model]] === Model component .Structure of the Model Component image::ModelClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/seedu/EzWatchlist/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the EzWatchlist data. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can store a `Actor` list in `Watch List`, which `Show` can reference. This would allow `Watch List` to only require one `Actor` object per unique `Actor`, instead of each `Show` needing their own `Actor` object. An example of how such a model may look like is given below. + + image:BetterModelClassDiagram.png[] @@ -137,110 +144,153 @@ image:BetterModelClassDiagram.png[] .Structure of the Storage Component image::StorageClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/seedu/EzWatchlist/storage/Storage.java[`Storage.java`] 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 Watch list data in json format and read it back. + +// tag::apicomponent[] +[[Design-API]] +=== API component + +[[fig-LogicClassDiagram]] +.Structure of the Api Component +image::ApiClass.png[width='600'] + +*API* : +link:{repoURL}/src/main/java/seedu/EzWatchlist/api/ApiManager.java[`ApiManager.java`] + +In Figure 9, we see the structure of the API component centered around `ApiManager`. Moreover: + +. `ApiManager` uses the `ApiUtil` class for static methods for data handling. +. The `ApiUtil` class creates an `ImageRetrieval` object for retrieving images over the network and a `RecommendationEngine` object to generate recommendations. +. `ApiManager` object encapsulated by the interface `ApiInterface` can be created at any point in the application to access the online database. +. If no network connection can be established, an `OnlineConnectionException` is thrown. +. `ApiManager` will not affect any of the internal logic and model in the application. + +// end::apicomponent[] [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.EzWatchlist.commons` package. + +{sp} + == Implementation This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +// tag::markaswatched[] +=== [Feature] Mark/Unmark as Watched Feature -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +The watch feature allows users to mark or unmark shows as watched. It also allows users to keep track of the latest episode +of a TV series that they have watched. -* `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. +==== Implementation +The mark/unmark as watched mechanism is facilitated by `WatchCommand` which can be found under the commands package. +It extends `Command` and uses the `WatchCommandParser` to process the command entered by the user. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Given below is an example usage scenario and how the mark/unmark as watched mechanism works at each step. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Step 1. The user launches the application, and executes `watch 1 s/2 e/3` command to update the latest watched episode of the first show in the list. -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 2. Entering the command calls `WatchListParser#parseCommand()`, which in turn returns a new `WatchCommandParser` and the `WatchCommandParser#parse()` command is called. -image::UndoRedoState0.png[] +Step 3. A new `WatchCommand` is created, with the index of the show being parsed as a field of the `WatchCommand`. A new `WatchShowDescriptor` is also created to relay the episode number and season number to the `WatchCommand` object. -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 4. The `WatchCommand#execute()` method is called, referencing the current `model`, and the show that is in the current `FilteredShowList` is referenced based off the current `model`. -image::UndoRedoState1.png[] +[NOTE] +If the `index` is out of bounds, a new `CommandException` is thrown. -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 5. A copy of the show is created through the use of `WatchCommand#createEditedShow()`, with the new total number of seasons and episodes updated if there are any changes. +A new `isWatched` value of the show is also determined based on the number of episodes that are watched. -image::UndoRedoState2.png[] +The following activity diagram below summarizes the calculation of the number of episodes watched: -[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`. +.WatchActivityDiagram showing how episodes are calculated +image::WatchActivityDiagram.png[] -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 6. The show in the current show list is updated to the newly created copy with the updated watched status and latest episode watched, and a `CommandResult` with the new watched status of the show is created. -image::UndoRedoState3.png[] +The following sequence diagram shows how the watch operation works: -[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. +.WatchSequenceDiagram showing flow of the watch command +image::WatchSequenceDiagram.png[] -The following sequence diagram shows how the undo operation works: +==== Design Considerations -image::UndoSequenceDiagram.png[] +===== Aspect: Creating a new WatchCommand instead of an altered EditCommand -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. +* **Alternative 1 (current choice):** Creating a new `WatchCommand` class for changing the 'watch' status of a show. +** Pros: Enables for greater cohesion since there is a specific command for editing the 'watch' status of a show. +** Cons: Requires longer code, and the code is also repetitive since its implementation is similar to that of the `EditCommand`. +* **Alternative 2:** Use the `WatchCommandParser` to create a new `EditCommand` object that edits the watch status of the show. +** Pros: Less code repetition and shorter code in general. +** Cons: This will mean that there is less cohesion of the code and greater dependencies since more classes depend on the `EditCommand` class. +// end::markaswatched[] -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. +// tag::statistics[] +=== Statistics Feature +==== Proposed Implementation -[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 statistics feature is facilitated by `Statistics` object. It extends `EzWatchlist` with a summary of the users' personal +preferences and footprint including the most watched genre, shows that you might have forgotten to watch, and the +recommendations generated for you. It is stored internally as an `Statistics` object containing a `ModelManager`. +Additionally, it implements the following operations: -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +* `Statistics#getFavouriteGenre()` -- Gives the genre that appears the most number of times in the list of watched shows. +* `Statistics#getForgotten()` -- Gives the 3 shows that were added the earliest but have not been watched. +* `Statistics#getRecommendation()` -- Gives 3 recommendations according to the watched shows. -image::UndoRedoState4.png[] +Given below is a usage scenario of a user checking his/her statistics page. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +Step 1. The user launches the application. The Statistics object will be initialized with ModelManager +containing the current watchlist and watched list. Behind the scenes, the 3 statistics will be calculated and the +statistics panel will be populated with the result. -image::UndoRedoState5.png[] +Step 2. The user clicks on the statistic button and sees the content. -The following activity diagram summarizes what happens when a user executes a new command: +This is the sequence diagram of `getFavouriteGenre()`. -image::CommitActivityDiagram.png[] +image::GetFavouriteSequenceDiagram.png[] -==== Design Considerations +This is the sequence diagram of `getForgotten()`. -===== Aspect: How undo & redo executes +image::GetForgottenSequenceDiagram.png[] -* **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. +This is the sequence diagram of `getRecommendation()`. + +image::GetRecommendationSequenceDiagram.png[] + +The following activity diagram summarizes the workflow of Statistics: -===== Aspect: Data structure to support the undo/redo commands +image::StatisticsActivityDiagram.png[] -* **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[] +==== Design Considerations + +===== Aspect: How Statistics stores the watchlist and watched list -// tag::dataencryption[] -=== [Proposed] Data Encryption +* **Alternative 1 (current choice):** Saves the whole ModelManager as a field. +** Pros: Easy to implement and make use of. +** Cons: More dependency on ModelManager. +* **Alternative 2:** Saves the watchlist and watched list as fields respectively. +** Pros: Less dependency on ModelManager. +** Cons: Less potential functionality and the lists might not be up-to-date. -_{Explain here how the data encryption feature will be implemented}_ +===== Aspect: When the results are calculated -// end::dataencryption[] +* **Alternative 1 (current choice):** Calculate when the user navigate to Statistics panel and the user has made changes +to the watchlist. +** Pros: Statistics results is more up to date. +** Cons: Appear less responsive as the API needs to retrieve information during calculation. +* **Alternative 2 :** Calculate when the application starts and update when there are changes. +** Pros: It appears more responsive in terms of navigating around the application. +** Cons: The statistics information will not be updated realtime if the user has changed the watchlist. +// end::statistics[] === Logging @@ -257,11 +307,397 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is * `INFO` : Information showing the noteworthy actions by the App * `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size +// tag::onlinedata[] +=== Online Data + +We are using https://www.themoviedb.org/documentation/api[The Movie Database (TMDB)] to retrieve information on movies and tv shows. + +==== Implementation + +All interactions with the third party library are facilitated by the interface `ApiInterface`. Methods in `ApiInterface` are the main +way the application retrieves any online information from the database. + +*Methods in ApiInterface* + +* `getMovieByName` : The method will return a list of movies in the database based on the name of the movie given +* `getTvShowByName` : The method will return a list of tv shows in the database based on the name of the tv show given +* `isConnected` : Checks if the application is connected online to the database +* `getUpcomingMovies` : The method will return a list of upcoming movies from the database. +* `getMovieByGenre` : The method will return a list of movies from the database based on a set of genres. +* `getMovieRecommendations` : The method will return a list of movie recommendations specified by the amount required based on the user's movies. +* `getTvShowRecommendations` : The method will return a list of tv shows recommendations specified by the amount required based on the user's tv shows. + +`ApiManager` is an implementation of `ApiInterface` and is dependent on a https://github.com/holgerbrandl/themoviedbapi[java wrapper] for the TMDB api implemented by Holger Brandl. +Allowing us to retrieve information in the java code. + +Given below is an example of how the application might want to retrieve movie information through `ApiInterface`. + +Step 1. First an instance of a class implementing `ApiInterface` has to be created for the methods to be called such as `ApiManager`. When `ApiManager` is instantiated the class's internal field +`Api Key` 's is used to create a *call object* to the TMDB database. All information retrieved must go through this class's api *call object*. + +Step 2. The method getMovieByName("Name Of Movie") is called and the *api call object* is passed to the corresponding method in the class `ApiUtil`. The corresponding method then searches the for the movie in the database with the name given. +A list of wrapped movies is given back, which is then read and used to create new instances of our application's `Show` model with information wrapped in classes such as +`Name`, `Description`, and `Actor`. + +Step 3. To retrieve an image from the database, an image url is downloaded to the computer. The class `ImageRetrieval` does this. An +`ImageRetrieval` instance is created by the method for each image, and is downloaded to a root folder determined by `ImageRetrieval#defaultDirectory()`. + +Step 4. A `poster` class is then created with the image path of the downloaded image. At any point, the `poster` object can be used to load images in the application. + +Step 5. With the list of movies returned, the application can then read the internal `Movie` object returned. The `Poster` object in the `Movie` object can be used to load the poster of the movie, and the other fields can be shown to the user as seen in the figure below. + +.The movie component shown to the user in `EzWatchlist`. +image::moviecard.png[width=900] + +[NOTE] +At any point the application might fail due to the application not being connected to the internet. If that occurs a +`OnlineConnectionException` is thrown for the application to handle. + +The relationship between all the classes, and their methods are shown in the following class diagram in the figure below: + +.Detailed Api Class Diagram +image::ApiClassDiagram.png[width=600] +// tag::apiimpl[] + +==== Design Considerations + +===== Why this implementation was chosen + +* **All API interactions would be in the API package and go through ApiInterface. ** +** Pros: +- The application wouldn't have to seek access to the database on their own. The interface should provide all the functionality needed. +- Follows the Single Responsibility Principle (SRP) that a module in the program should be encapsulated and have one repsponsibility. + +==== Aspect: Image retrieval implementation +Images are retrieved through the `ImageRetrieval` class. Which downloads the image into the computer, +wrapping a `Poster` class with the local path of the image, for the application to access images. + +===== Design Considerations +* ** Alternative 1 (current choice):** Download the image into the computer then access it locally on the computer. +** Pros: +*** Easier to keep track of images. +*** All images are retrieved the same way making it easier to implement showing the images. +** Cons: +*** All images shown are currently downloaded without being deleted, hence the amount of images downloaded can get unwieldy quickly, increasing +amount of memory used. +* **Alternative 2:** Parse the online url to the application for them to access the image online when needed. +** Pros: +*** No need for the images to be downloaded allows less memory to be used by the application. +** Cons: +*** Everytime the image is viewed, the application has to retrieve it from online making it more intensive on the network. +*** If the internet connection fails, the image can no longer be viewed. +//end::ImageRetrieval[] + +==== Aspect: Recommendations +Recommendations are generated through the `RecommendationEngine` class in the API package. Currently, +recommendations are retrieved through the `ApiInterface` implemented in the API package. + +===== Implementation +The figure below is a _sequence diagram_ of how the recommendations are generated when called from the `ApiManager`. + +.Sequence Diagram of Movie Recommendations retrieval. +image::movieRecommendationSD.png[width=500] + +Step 1. First an instance of `RecommendationEngine` is created by passing in the list of movies the user has and the *api call object* generated by the instance of the `ApiManager` object. + +Step 2. The method `getMovieRecommendations(noOfRecommendations)` is called in the `RecommendationEngine` object and it will: + +* Check if the list is valid to generate recommendations. +* Parse the list to get the online entries in the database, and their recommendations from the database. +* For each recommendation, store it in a _HashMap_ and if there are duplicates increase the value. This counts the amount of occurrences each recommendation occurs. +* Filter the entries to remove all entries that the user already has. + +Step 3. The recommendations are then sorted based on the amount of occurrences in appears in the _HashMap_. + +Step 4. The recommendations are then returned in a list in which the length depends on the amount of recommendations requested. + +Step 5. With the list of movies returned, the application can then display the movie recommendations back to the user. + +[NOTE] +If no recommendations can be generated, a `NoRecommendationsExceptions` is thrown. Moreover, an +`OnlineConnectionException` is still thrown when not connected to the internet. +// end::apiimpl[] + +// end::onlinedata[] [[Implementation-Configuration]] === Configuration Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). +// tag::search[] +[[Implementation]] +=== [Feature] Search Feature +The `Search` feature allows users to search for shows from the *online database*, the *internal database*, their *watchlist* or +*watched-list*. +It allows users to search for shows based on either "*name*", "*genre*" or "*actors*", or a combination of them. + +Users can choose to search from the online database or their watchlist and watched-list, and also filter their search +based on the show type. + +The following _activity diagram_ summarises the workflow of the `Search` feature: + +.Activity Diagram for the `Search` Feature +image::SearchActivityDiagram.png[width="450"] + +{sp} + + +==== Implementation +The search mechanism is facilitated by `SearchCommand` which can be found under the commands package. +It extends `Command` and uses the `SearchCommandParser` to process the command entered by the user. + +Given below is an example usage scenario and how the search mechanism behaves at each step. + +Step 1. The user launches the application, goes to the *Search page* and executes `search n/Avengers o/no` +command to search for shows named "Avengers" from the watchlist and watched-list. + +Step 2. Entering the command calls `LogicManager#execute()`, which in turns calls the `WatchListParser#parseCommand()`. + +Step 3. `WatchListParser#parseCommand()` returns a new `SearchCommandParser` and the `SearchCommandParser#parse()` command is called. + +[NOTE] +If the user enters a wrong command, such as incorrect prefixes or keywords, a new `ParseException` is thrown. + +Step 4. A new `SearchCommand` is created, with the hash map containing the contents to be searched as a field of +`SearchCommand`. + +Step 5. The `SearchCommand#execute()` method is called, referencing the current model. + +[NOTE] +If the user is not connected online, a new `OnlineConnectionException` is caught and search would be done using the +*internal database*, *watchlist* and *watched-list* instead of the *online database*. + +Step 6. The `SearchCommand#searchByName()` method is called, referencing the current model. + +Step 7. The `SearchCommand#addShowFromWatchListIfSameNameAs()` method is called, referencing the current model and name +of the show to be searched from the list of shows. + +Step 8. The `Model#getShowFromWatchlistIfHasName()` method is called, referencing the name of the show to be searched. +A list of shows with the name "Avengers" is retrieved. + +Step 9. The `SearchCommand#addShowToSearchResult()` method is called, referencing the list of the shows found in Step 7. +Shows are filtered based on the possible filters and added the the `SearchCommand#searchResult` + +Step 10. A new `CommandResult` is created, referencing the search message to be shown to user. This `CommandResult` is +returned to the `LogicManager`. + +{sp} + +The following _sequence diagram_ summarizes how the search operation works based on the example above: + +.Sequence Diagram for the `Search` Operation +image::SearchSequenceDiagram.png[] + +{sp} + + +==== Design Considerations + +===== Aspect: How `SearchCommand` reference the information to be searched for +* Alternative 1 (current choice): Takes reference to the hash map from `SearchCommandParser`, consisting of all the +possible methods to search for shows +** Pros: +*** Easy to implement +*** Can be easily made use of by retrieving the list of what the user want to search from the hash map +** Cons: +*** Command may be longer and user would be required to be familiarize with the prefix, such as `n/` when searching by name +*** Certain list might be redundant as the user might not have requested to search by certain fields +*** Higher run time in checking through all the lists + +* Alternative 2: Takes reference to a string from `SearchCommandParser` and by *name*, *genre*, and *actor* using that string +** Pros: +*** Easy to parse the information from `SearchCommandParser` to `SearchCommand` +** Cons: +*** Does not allow the user to have the freedom to choose what they would like to search by +*** May return irrelevant results back to the user as the user did not specify what they would like to search based on +*** Higher run time as there would be a need to search based on all 3 methods + +===== Aspect: How `SearchCommand` is executed +* Current choice: Search by name, followed by genre then actor, when the user chooses to search with any combinations of the 3 methods +** Pros: +*** Easy to implement and make use of. +*** Shows all the shows from the combination of the search results +** Cons: +*** The user has to input either a name, genre or actor in order for the search to work +*** Logic is repetitive when searching based on name, actor and genre +*** Does not allow the user to search for shows that has a combination of the names, genres and actors + +e.g. `search n/Avengers g/Comedy` would show a search result with shows that either have the name "Avengers" or the genre "Comedy", +instead of shows that have both the name "Avengers" and the genre "Comedy" + +// end::search[] + +{sp} + + +// tag::add[] +=== [Feature] Add feature +==== Implementation +The `AddCommand` extends `Command` and uses `AddCommandParser` to process the command entered by the user. + +*Scenario 1:* Adding show in the WatchList. + +*Scenario 2.* Adding show found from online search. + +Given below is an example usage of scenario 1 and how the add mechanism behaves at each step. + +Step 1. The user launches the application and executes `add n/Joker...` command to add a show in the WatchList with the name "Joker". + +Step 2. Entering the command calls AddCommandParser#parse(). + +Step 3. A new `AddCommand` is created, with the show to be added in AddCommand. + +Step 4. The `AddCommand#execute()` method is called, referencing the current model and add the show given by user to the `filteredShowList` found in model. + + +The following activity diagram summarises the workflow of Add: + +image::AddActivitySequenceDiagram.png[width="300"] + +_Figure 3: Activity Diagram of `AddCommand`_ + +In Figure 3, the user first launches the app. After the user input a add command, the program runs and add the show input by user into WatchList. + +==== Design Considerations + +===== Aspect: How `AddCommand` executes +** Current choice: Create a show object and add it to a `filteredShowList` found in ModelManager. +*** Pros: Easy to implement and make use of. +*** Cons: May have performance issues in terms of memory usage. + + +Given below is an example usage of scenario 2 and how the add mechanism behaves at each step. + +Step 1. The user uses the search(Online) command and executes `add INDEX` command to add a show from search result page of INDEX in the WatchList. + +Step 2. Entering the command calls AddCommandParser#parse(). + +Step 3. A new `AddCommand` is created, with the show to be added in AddCommand. + +Step 4. The `AddCommand#execute()` method is called, referencing the current model and add the show given by user to the `searchList` of INDEX found in model. + +The following sequence diagram shows how the `add` operation works: + +image::AddSequenceDiagram.png[width="500"] + +The following activity diagram summarises the workflow of Add: + +image::AddActivitySequenceDiagram.png[width="300"] + + +_Figure 4: Activity Diagram of `AddCommand`_ + +In Figure 4, User input search(online) command. User then input `add INDEX` command. Show of INDEX found in search result page is added to WatchList. + +==== Design Considerations + +===== Aspect: How `AddCommand` executes + +** Current choice: Retrieve the show object found in `searchList` of INDEX from ModelManager and add it to `filteredShowList`. +** Pros: Enables for greater cohesion since there is a specific command for adding information of a show in watchlist. +** Cons: Requires longer code, and the code is also repetitive since its implementation is similar to that of the add and edit command +** Cons: May have performance issues in terms of memory usage. + +// end::add[] + +// tag::add2[] +=== [Feature] Add feature (Extension) +==== Implementation + +This `add` feature is an extension to the `add` feature found in previous feature. +It is used after user has searched for a show using `search` feature and the user wants to add a certain show +into his watchlist. + +Given below is an example usage of `add` feature (Extension) and how the add mechanism behaves at each step. + +Step 1. The user uses the search(Online) command and executes `add INDEX` command to add a show from search result page of INDEX in the WatchList. + +Step 2. Entering the command calls AddCommandParser#parse(). + +Step 3. A new `AddCommand` is created, with the show to be added in AddCommand. + +Step 4. The `AddCommand#execute()` method is called, referencing the current model and add the show given by user to the `searchList` of INDEX found in model. + + +The following sequence diagram shows how the `add` extension operation works: + +image::AddSequenceDiagram2.png[width="500"] + +The following activity diagram summarises the workflow of Add extension: + +image::AddActivitySequenceDiagram2.png[width="350"] + + +_Figure 4: Activity Diagram of `AddCommand`_ + +In Figure 4, User input search(online) command. User then input `add INDEX` command. Show of INDEX found in search result page is added to WatchList. + +==== Design Considerations + +===== Aspect: How `AddCommand` executes + +** Current choice: Retrieve the show object found in `searchList` of INDEX from ModelManager and add it to `filteredShowList`. +** Pros: Enables for greater cohesion since there is a specific command for adding information of a show in watchlist. +** Cons: Requires longer code, and the code is also repetitive since its implementation is similar to that of the add and edit command +** Cons: May have performance issues in terms of memory usage. + +// end::add2[] + + +// tag::sync[] +=== [Feature] Synchronise user's show data + +The synchronise feature allows user to sync a show found in watchlist with online searched show data. It modifies all of the +parameters/information is user selected show with online searched show data. + +User may have added their show with their own information. However, user might not know some of the parameters such as actors. +Thus, user can use the search online command `search n/` to look up information regarding that show. + +Then, Synchronise command `sync` can be used to update information/modify on that show. + +==== Implementation + +The Synchronise feature is facilitated by `SyncCommand` object which can be found under the commands package. +It extends `Command` and uses the `SyncCommandParser` to process the command entered by the user. + +Given below is an example usage scenario and how the Synchronise command work as Sync mechanism works at each step. + +Pre-Condition: User has already added a certain show into watchlist manually. That show must have at least `name` and `type` parameters. +Example of Pre-Condition: User has added Titanic movie into watchlist. + +Step 1. The user launches the application, go to Search page and execute `search n/titanic`. + +Step 2. The user execute `sync 1` command to synchronise index 1 of result page with a show in watchlist with same name (case-insensitive). + +Step 3. Entering the command calls `SyncCommandParser#parseCommand()`, which in turn returns a new `SyncCommandParser` and the `SyncCommandParser#parse()` command is called. + +Step 4. A new `SyncCommand` is created, with the index of the show being parsed as a field of the `SyncCommand`. + +Step 5. The `SyncCommand#execute()` method is called, referencing the current `model`, and the show that is in the current `FilteredShowList` is referenced based off the current `model`. + +[NOTE] +If the `index` is out of bounds, a new `CommandException` is thrown. + +Step 6. A list of shows found in search page and watchlist are retrieved from `model`. The show according to the Index of the `searchpagelist` are retrieved as well. +Then, the list of show in watchlist will be checked through to match the name of the index show. + +Step 7. If a show in watchlist matched with the name of the index show, `model.setShow` will be called to replace the show found in watchlist with index show. +`CommandResult` will be return which contains information regarding the feedback result. Else, `CommandException` is thrown to notify user no similar show name is found in watchlist as index show. + +The following sequence diagram shows how the sync operation works: + +image::SyncSequenceDiagram.png[width="500"] + + +The following activity diagram summarises the workflow of Sync: + +image::SyncActivityDiagram.png[width="500"] + + +==== Design Considerations + +===== Aspect: Creating a new Synchronise instead of an altered EditCommand and AddCommand + +* **Alternative 1 (current choice):** Creating a new Synchronise class for replace information of a certain show found in search page with one in watchlist. +** Pros: Enables for greater cohesion since there is a specific command for replacing/modifying information of a show in watchlist. +** Cons: Requires longer code, and the code is also repetitive since its implementation is similar to that of the add and edit command +* **Alternative 2:** Use the SyncCommandParser to create a new EditCommand object that edits the information of a certain show found in search page with one in watchlist. +** Pros: Less code repetition and shorter code in general. +** Cons: This will mean that there is less cohesion of the code and perhaps greater dependencies since more classes depend on the EditCommand class. +// end::sync[] + == Documentation Refer to the guide <>. @@ -279,13 +715,17 @@ 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 -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* Users who are tidy and organised +* Forgetful person who has a need to keep track of what movies, tv series they would like to watch +* Tech-savvy users who prefer desktop apps over other types +* User can type fast +* User prefers typing over mouse input +* User is reasonably comfortable using CLI apps +* Movie/TV series lover -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*Value proposition*: + +EzWatchlist provides a unique, clean and simple way of organizing and keeping track of your watchlist. All in one solution to your problem. [appendix] == User Stories @@ -295,17 +735,67 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un [width="59%",cols="22%,<23%,<25%,<30%",options="header",] |======================================================================= |Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +|`* * *` |forgetful person | keep track of the shows to watch|I won’t forget about them. + +|`* * *` |very neat and organised movie lover |can organise the movies I’ve watched in categories/groups | watch them again. -|`* * *` |user |add a new person | +|`* * *` |As an organised person| sort the movies and tv shows into genres | easily find a show from a genre that I want to watch. -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |As a movie lover |mark movies in the watchlist as “watched” | keep track of what I have watched -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *`| As a fickle person | delete a movie from the watchlist if I don’t feel like watching it anymore| my watchlist will not be cluttered with movies that I have no interest in watching anymore. + +|`* * *` |Someone who loves keeping records | keep track of how many movies or tv series I have watched | + +|`* * *` |tech-savvy |do any task as quickly as possible| experience is pleasant and quick. + +|`* * *` | App user | categorize and search based on distinct things | everything is neat and pleasing. + +|`* * *` | App user | track where I am specifically in the series | I can remember where I am for that show. + +|`* *`|movie lover who likes to think about the message conveyed in the movie |I can note down some of my thoughts for every movie after watching | + +|`* *` | a movie critic| can track my thoughts and criticism of movie easily | it is organised. + +|`* *` | a movie/drama lover who also likes music | can note down the name of the soundtrack in the movie in a place specially for that. | + +|`* *`| a movie lover | rate a movie that I have watched | I can know what movies I liked to watch + +|`* *`| a movie lover | can know about all the movies | I would be able to watch them in the cinemas when they are out. + +|`* *`| a movie lover | look up for shows to watch from the recommendations |I will not have to manually search online. + +|`* *`| a movie lover | I can search for movies that I have added to the watchlist| I can easily find the movie that I want to watch. + +|`* *`| looking for new movies| search for some recommendations| I can watch it when I am free. + +|`* *`| a movie lover | search for movies that I have added to the watchlist| easily find the movie that I want to watch. + +|`* *`| a movie lover | can keep track of the number of times I have watched a movie |I can find out which movie I liked watching the best. + +|`* *` | a movie lover |check the ratings of each movie I have watched |I can recommend them to my friends. + +|`* *`|a tv show addict | I can put my favourite shows in | I can enjoy watching it next time. + +|`* *`| unorganised person | I can rank the movies that I want to watch into different priorities | I can easily know what are the movies that I desperately want to watch. + +|`* *` |unorganised person | can sort movies that I want to watch into the date they were added |I can clear the movies that have been lingering at the back of my mind the longest. + +|`* *` | does reviews | keep track of movies and write reviews for the show I have watched |I can have a positive criticism. + +|`* *` | someone who forget to watch tv series/movies | I can have an alarm that alert me to watch videos on time.| + +|`* *` |As someone who has a deadline to watch movies | I can plan and add deadlines to watch movies.| + +|`* *`|As a lover of multiple mediums| I can combine all these different mediums in the same platform | I can keep track of everything in the same place. + +|`* *`| As a statistics lover| I can view the statistics of the shows I watched | I can gain insight of my viewing habits. + +|`* *`| forgetful person| I can add shows I’ve watched to see a virtual library of things I’ve completed| I can log it like a book in a bookcase. + +|`* *`| As an app user| I can see the date I’ve inputted the specific object| I can remember when I completed. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident -|`*` |user with many persons in the address book |sort persons by name |locate a person easily |======================================================================= _{More to be added}_ @@ -313,70 +803,194 @@ _{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 the `EzWatchlist` and the *Actor* is the `user`, unless specified otherwise) [discrete] -=== Use case: Delete person +=== Use case: Add Movie *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 navigates to the main page +2. User enters 'Add' command +3. User enters the movie name +4. EzWatchlist will display a list of movies found +5. User enters the index given by the list +6. EzWatchlist will add the movie to their watchlist + Use case ends. *Extensions* [none] -* 2a. The list is empty. +* 3a. No movie is found +[none] +** 3a1. User enter movie information himself. +** 3a2. User saves the movie + Use case ends. -* 3a. The given index is invalid. +[none] +* 5a. The given index is invalid. +[none] +** 5a1. System shows an error message. ++ +Use case resumes at step 4. + +[discrete] +=== Use case: Mark as watched +*MSS* + +1. User navigates to the main page +2. User enters 'Watched' command giving the index of the item +3. EzWatchlist will mark that item as watched + +Use case ends. + +*Extensions* +[none] +* 2a. The given index is invalid. [none] -** 3a1. AddressBook shows an error message. +** 2a1. System shows an error message. +image::CommitActivityDiagram.png[] + -Use case resumes at step 2. +Use case ends. + +[discrete] +=== Use case: Search locally +*MSS* + +1. User navigates to the main page +2. User enters 'search' command giving the name of the item +3. EzWatchlist will bring the user to a search page with items found in a list +4. User enters View command of the index of the item. +5. EzWatchlist will bring that item into details page ++ +Use case ends. + +*Extensions* +[none] +* 2a. The given name is not found. +[none] +** 2a1. System shows an error message. ++ +Use case ends. + +[none] +* 4a. The given index is not valid. +[none] +** 4a1. System shows an error message. ++ +Use case resumes at step 3. + + +[discrete] +=== Use case: Edit item +*MSS* + +1. User navigates to the main page. +2. User enters 'edit' command giving the name of the item. +3. EzWatchlist will bring the user to a details page of the item entered. +4. User edits the details of the item. +5. User saves the edits. ++ +Use case ends. + +*Extensions* +[none] +* 2a. The given name is not found. +[none] +** 2a1. System shows an error message. ++ +Use case ends. + + +[discrete] +=== Use case: Delete item +*MSS* + +1. User navigates to the page containing the item to be deleted. +2. User enters 'delete' command giving the name of the item. +3. EzWatchlist asks for confirmation from the user. +4. User confirms. +5. EzWatchlist deletes the item. ++ +Use case ends. + +*Extensions* +[none] +* 2a. The given name is not found. +[none] +** 2a1. System shows an error message. ++ +Use case ends. + +[none] +* 3a. User cancels the deletion. +[none] +** Use case ends. + + +[discrete] +=== Use case: Statistics +*MSS* + +1. User navigates to the statistics page +2. EzWatchlist displays the information. ++ +Use case ends. -_{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 movies in the watchlist 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. +. The user interface should be intuitive enough for users who are not IT-savvy. +. Compliance with data, security, copyright laws. +. The application will be offline-friendly with online data being the only functionality missing. -_{More to be added}_ +// tag::glossary[] [appendix] == Glossary +[[details-page]] Details page:: +The page that shows the details of shows, which the user may then add it to the watchlist if interested. -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +[[main-page]] Main Page:: +The default main page with the WatchList tab that contains a list of shows that the user wishes to watch (same as _Watchlist Page_) -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[movies]] Movie:: +A cinema film -[appendix] -== Product Survey +[[search-page]] Search page:: +The page where the user searches for shows to be added or to view their information + +[[show-index]] Show index:: +The number reference for the show on the page in EzWatchlist + +[[shows]] Shows:: +Movies or TV series + +[[statistics-page]] Statistics page:: +The page that shows the statistics of user, such as total number of movies / TV series watched -*Product Name* +[[tv-series]] TV Series:: +A television program which consists of possibly several episodes and seasons that are broadcast on regular intervals -Author: ... +[[view-command]] View command `view [index]`:: +This command brings the user to the details page of the show with the specified show index -Pros: +[[watched-list]] Watched-list:: +The list of shows that the user has watched. -* ... -* ... +[[watched-page]] Watched Page:: +The page where the user can access to view the list of shows indicated as watched -Cons: +[[watchlist]] Watchlist:: +The list of shows that the user wishes to watch in the future. -* ... -* ... +// end::glossary[] [appendix] == Instructions for Manual Testing @@ -400,26 +1014,54 @@ 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 ... }_ +=== Trying out the Application + +. Adding shows + +.. Type `add n/Harry Potter and the Chamber of Secrets t/movie` +.. These commands should add the movie to your list. + +. Searching for shows + +.. Type `search n/Harry Potter and the Chamber of Secrets` +.. This should bring you to the search page and display the movie. + +. Syncing the show + +.. Type `sync 1` +.. This should sync the show into the watchlist. + +. Adding shows from search result + +.. Type `search n/stranger things` +.. Type `add 3`. +.. This should add the TV series, Stranger Things to the watchlist. -=== Deleting a person +. Marking a movie as watched -. Deleting a person while all persons are listed +.. Type `watch 1`. This should mark the show as watched and transfer it to your watched list. +.. Navigate to the watched show by typing "2" and pressing enter. +.. Alternatively, you may click on the "watched" tab itself. +.. The `watch` command should only be executable on the watchlist and watched tabs. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. +. Marking a TV show's seasons and episodes -_{ more test cases ... }_ +.. Navigate to the watchlist tab by typing "1" and pressing enter or clicking the tab itself. +.. Type `watch 1 s/2 e/4` +.. This should update the last watched episode and season of the "Stranger Things". +.. Try the command with other season and episode numbers. +.. The `watch` command should only be executable on the watchlist and watched tabs. -=== Saving data +. Advanced search features -. Dealing with missing/corrupted data files +.. Type `search n/Stranger Things o/yes` +.. Type `search g/Horror n/Annabelle` +.. Try other search fields following the user guide's usage. +.. Also try searching for shows while being disconnected from the internet. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +. Statistics feature -_{ more test cases ... }_ +.. Navigate to the statistics page by typing "4" then pressing enter or clicking on the statistics tab. +.. This should bring you to the statistics tab of EzWatchlist. +.. Here you can view some shows that have not been watched, your favourite genres and recommendations. +.. Try accessing this page while being connected and disconnected to the internet. diff --git a/docs/DeveloperGuide.pdf b/docs/DeveloperGuide.pdf new file mode 100644 index 00000000000..6fd94b8cc59 Binary files /dev/null and b/docs/DeveloperGuide.pdf differ diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..8ef595d8688 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += EzWatchlist - Documentation :site-section: DeveloperGuide :toc: :toc-title: diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..165fdf5609f 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += EzWatchlist - Setting Up :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-F13-4/main/tree/master == Prerequisites @@ -37,7 +37,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu == Verifying the setup -. Run the `seedu.address.Main` and try a few commands +. Run the `seedu.ezwatchlist.Main` and try a few commands . <> to ensure they all pass. == Configurations to do before writing code diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..d73393a1a73 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += EzWatchlist - 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.ezwatchlist.commons.StringUtilTest` . _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.ezwatchlist.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.ezwatchlist.logic.LogicManagerTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..e3dde74be3e 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,6 +1,7 @@ -= AddressBook Level 3 - User Guide += EzWatchlist - User Guide :site-section: UserGuide :toc: +:toclevels: 3 :toc-title: :toc-placement: preamble :sectnums: @@ -12,166 +13,636 @@ 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-F13-4/main.git +:icons: font -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team CS2103T-F13-4` Since: `Sept 2019` Licence: `MIT` +// tag::intro[] == Introduction +Tired of using multiple sources on the internet to keep track of all your movies and tv shows that you want to watch? EzWatchlist is a solution to your entertainment problems! -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! +`EzWatchList` is an application is for cinephiles who *prefer to use a desktop for managing movies and tv shows*. More importantly, `EzWatchList` 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, `EzWatchList` works faster than traditional GUI apps. +. Keep track of movies and tv shows that you plan to watch or have watched. +. Access quick information about your favourite shows through our online database. +. Discover your personal movie habits, with our statistics page. +. Can't find any good shows to watch? We will give you recommendations on what to watch based on your `EzWatchlist` usage. + +==== +Already interested? Jump to <> to quickly get started. Enjoy! +==== + +=== What else does EzWatchlist offer you? + +* Integration with an online database of shows, allowing you to search and get information about shows. + +* Functions and works without internet. + +* No installations required. + +* Auto-saves all data. + +* Dual-purpose interface, command line interface for those who prefer typing and graphical for those who prefer using a mouse. + +=== How do I use EzWatchlist? + +EzWatchlist was built with ease in mind hence our interface reflects this design philosophy. + +.EzWatchlist's graphical interface +image::Ui.png[width="900"] + +==== Navigation + +EzWatchlist is split into four different pages: + +. <>, where shows you want to watch are added. +. <>, where shows you have watched are located. +. <>, where you search for shows both online and offline. +. <>, where information about your viewing habits are shown. + +This pages are represented by the <> shown visibly in Figure 1. Navigate between pages by using keyboard shortcuts kbd:[1], kbd:[2], kbd:[3], kbd:[4] respectively or +through a button press on the graphical interface. + +==== Interaction +.EzWatchlist's command line interface. +image::commandline.png[width="900"] + + +EzWatchlist uses <> entered through the command line interface in figure 2 to interact with the application. +Typing a command into the interface and entering it (by pressing kbd:[Enter] or clicking `_Go!_`) is the main way of interaction in the application. +The following is an example of adding a movie into your watch list: +// end::intro[] + +===== Steps to quickly add a movie + +* **`search`** `n/ip man` : Searched for movie named `ip man` in the offline and online database which will bring you to the search page automatically. + +.Search page for "ip man". +image::searchIpMan.png[width='500'] + +* **`add`** `2` : Adds "Ip Man" (2008) shown in entry 2 in Figure 3 into your watchlist. + +.Watchlist page after adding the entry. +image::watchlistIpMan.png[width='500'] + + +And that's it. Our <> will guide you to set up our application. If you like to learn more head straight into our <> for a run down on our major features, or +go to our <> to learn more on our commands. Otherwise, if you're a developer and are interested to +understand how each of our features were implemented, our <> breaks down each of our major components. + + +What are you waiting for? Start using `EzWatchlist` now! == 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. +. Download the latest `ezwatchlist.jar` link:https://github.com/AY1920S1-CS2103T-F13-4/main/releases[here]. +. Copy the file to the folder you want to use as the home folder for your watchlist. . Double-click the file to start the app. The GUI should appear in a few seconds. -+ -image::Ui.png[width="790"] -+ -. Type the command in the command box and press kbd:[Enter] to execute it. + +. Interact with `EzWatchlist` with <> 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: -* *`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 +* **`add`**`n/Titanic t/movie` : adds the `movie` named `Titanic` to the Watchlist. +* **`delete`**`3` : deletes the 3rd movie shown in either watch or watched page. * *`exit`* : exits the app -. Refer to <> for details of each command. +. Refer to <> for details of each command. + -[[Features]] == Features +// tag::UI[] +=== User-Interface + +The UI consists of three parts: sidebar, command bar and various pages that could appear in the main panel. +The following sections introduce these UI parts in details. + +==== Sidebar + +EzWatchlist gives users the ability to navigate easily through the **Watchlist, Watchedlist, Search page and Statistics pages** through the use of the side bar at the left of the main page of the graphical user interface. Moreover, keyboard shortcuts kbd:[1], kbd:[2], kbd:[3], kbd:[4] bring you to each page respectively. +==== +Note: If user has selected the command box, type kbd:[1], kbd:[2], kbd:[3], kbd:[4] and enter to bring you to each page respectively. +==== + +==== Command bar + +Users input commands in the command bar and any feedback will be displayed below the text area of command bar. + +==== Watchlist Page + +The watchlist page is the default page that is opened upon start-up of `EzWatchlist`. +It shows the user’s unwatched movies and television series. +The user can navigate to this list by clicking the “Watchlist” tab on the side bar or by using the keyboard short-cut `1`. + +**Characteristics of the watchlist page:** + +* Movie/TV show poster +* Name of show +* Genres +* Actors +* Description of the show +* Running Time +* Last watched episode (For TV series that have been updated using the `watch` command) +* Checkbox to indicate if the show has been watched + +.Main page of the UI: watchlist page +image::Ui.png[width="790"] + + +==== Watched List Page + +The user has the option to view the list of shows that have been marked as watched in the watched list. The user can navigate to this list by clicking the “Watched” tab on the side bar or by using the keyboard short-cut `2`. + +==== Search Page + +The user can navigate to the search page to find a specific movie or television by searching for its name. The user can navigate to this list by clicking the “Search” tab on the side bar or by using the keyboard short-cut `3`. +[NOTE] +The search function may require some time to load the data from the API. + +.Main page of the UI: search page +image::search_page.png[width="790"] + + +==== Statistics Page + +The user can navigate to the statistics page to find a summary of his watching habits, +preferences and history according to what is present in his lists. +Examples include: + +* Movies added but might have forgotten to watch +* Up to 3 most favourite genres +* Recommendations + +The user can navigate to this list by clicking the “Statistics” tab on the side bar or by using the keyboard short-cut `4`. +[NOTE] +The statistics function may require some time to load the data from the API the first time you start the application, +or after you have made changes to your watchlist. + + + +.Main page of the UI: statistics page +image::statistics_page.png[width="790"] + + +==== Details Page (Coming in v2.0) + +Upon clicking a movie or tv show title, a pop-up window will emerge, with a detailed view of the movie or tv show. This page provides the user with more information about the show, like the year, cast and ratings. The user also has the ability to edit the details here. + +// end::UI[] + +// tag::advancedfeatures[] +=== Advanced Features + +==== Parsing of Movie and Tv Show Information +In EzWatchlist, an online database is accessed so that all the information about your favourite Movies and +Television Shows is updated, informative, and concise. What this means for you is that: + +* Posters of your EzWatchlist shows, +can be displayed in EzWatchlist's sleek dark interface. + +* Moreover, your offline shows can be synced with this database allowing the application to fill in +the minor details for you. + +* Unsure of what actors played in your favourite movie? EzWatchlist will find that information for you. + +**** +You don't need to do anything other than be connected to the internet. It is that easy! +**** + +==== Movie and Tv Show Recommendations +EzWatchlist has the ability to recommend Movies and TvShows to you based on +your EzWatchlist usage. EzWatchlist will create your own personalized recommendations for you. + +===== How to get your recommendations +1. First, make sure that you have added at least one (offline or online) into `EzWatchlist` so that we can use that to generate recommendations for you. + +2. Then head to the <> by clicking kbd:[4] or pressing the `Statistics.` button on the +in the interface. + +3. Now your recommendations as seen in Figure 5 will be shown prominently on the bottom of the Statistics Page, with movie recommendations on the left, and Tv Series recommendations on the right. + +.Recommendations shown in the Statistics Page. +image::recommendations.png[width="900] + +**** +Excited? Enter your statistics page to get started on your recommendations. You might +just find your next favourite! +**** + +==== Auto-Correct (Coming in v2.0) +EzWatchlist aims to provide a smooth experience for our typing users. All typing in EzWatchlist will be automatically +corrected for you when typing in your commands. + +**** +Say goodbye to typing errors! +**** + +// end::advancedfeatures[] +[[UserCommands]] +=== User Commands ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{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. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/SHOW_NAME t/SHOW_TYPE`, `SHOW_NAME` and `SHOW_TYPE` are parameters which can be used as `add n/The Angry Birds Movie 2 t/movie`. +* Items in square brackets are optional e.g `n/SHOW_NAME t/SHOW_TYPE [s/DESCRIPTION]` can be used as `n/The Angry Birds Movie t/movie 2 d/Oscar-worthy` or as `n/The Angry Birds Movie t/movie`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[a/ACTOR]...` can be used as `{nbsp}` (i.e. 0 times), `a/Leonardo Di Carpio`, `a/Leonardo Di Carpio a/Christian Bale` etc. +* Parameters can be in any order e.g. if the command specifies `n/SHOW_NAME t/SHOW_TYPE`, `t/SHOW_TYPE n/SHOW_NAME` is also acceptable. ==== -=== Viewing help : `help` +==== View help : `help` Format: `help` -=== Adding a person: `add` +// tag::addfirst[] +==== Add a show: `add` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +User can add a show they are interested to watch in the watchlist by using + +`add` command. [TIP] -A person can have any number of tags (including 0) +*`add` can only work in **Watchlist page** and **Watched page**.* + +Format: `add n/SHOW_NAME t/SHOW_TYPE [d/DATE_OF_RELEASE] [w/WATCHED] [r/RUNNING_TIME] [s/DESCRIPTION] [a/ACTOR_NAME]...` + +* Only `SHOW_NAME`, `SHOW_TYPE` are compulsory fields. Rest is optional. +* `SHOW_TYPE` can only be 'movie' or 'tv' +* `WATCHED` can only be 'true' or 'false' +* `SHOW_NAME`, `DESCRIPTION` and `ACTOR_NAME` can be any words +* `DATE_OF_RELEASE` must be in the format dd/MM/yyyy +* `RUNNING_TIME` can be any number above 0 +* `ACTOR_NAME` can be more than one. + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `add n/Titanic t/movie` +* `add n/Friends t/tv` +* `add n/John Wick t/movie d/24/10/014 w/false r/101 s/An ex-hit-man comes out of retirement to track down the gangsters that killed his dog and took everything from him. a/Keanu Reeves` +* `add n/Joker t/movie d/3/10/2019 w/false r/122 s/In Gotham City, mentally-troubled comedian Arthur Fleck is disregarded and mistreated by society. He then embarks on a downward spiral of revolution and bloody crime. This path brings him face-to-face with his alter-ego: "The Joker". a/Joaquin Phoenix` + +// end::addfirst[] + +// tag::addsecond[] +==== Adding a show from search result page: `add` + +This is an extension to the `add` feature. +After user has searched from the show, user can add a show found in the search result page into their watchlist. + +Format: `add INDEX` + +INDEX is a positive integer and is limited to the number of shows found in search result page. -=== Listing all persons : `list` +[TIP] +This add command can only be used if user is currently at `search page`, and has already searched for show using the search online command. + +Example Usage: + +Pre-condition: User has already searched for a show using search online command. + +Step 1. User click (or use keyboard `3` key) on search page. User then input `add 1` on command box. + +image::add2.png[width="500"] + +Step 2. User click (or press keyboard `1` key) on watch list page. User will see `Joker` +movie added to watch list. -Shows a list of all persons in the address book. + -Format: `list` +image::add3.png[width="500"] -=== Editing a person : `edit` +// end::addsecond[] -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +==== Clear the WatchList: `clear` + +Clear the WatchList + +Format: `clear` + +// tag::watch[] +==== Mark/Unmark as watched : `watch` + +To mark an unwatched show in the watchlist as watched, use the command format listed below. + +Format: `watch INDEX [e/EPISODE_NUMBER] [s/SEASON_NUMBER]` + +[TIP] +Before marking TV show's seasons and episodes, you may want to use the <> +command to ensure that the TV show's season and episode details are up to date. + +*Example Usage:* + +1. You want to mark "The Office" in the watchlist page as watched. Navigate to the watchlist by clicking on the watchlist tab or hitting the keyboard shortcut `1`. + +.Viewing an unmarked show +image::WatchTheOffice.png[width="790"] + +[start=2] +2. Enter `watch 1` into the command box in the watchlist tab. + +.Entering the watch command +image::Watch.png[width="790"] + +[start=3] +3. You may now view the show under the watched tab by clicking the watched tab or hitting the keyboard shortcut kbd:[2]. + +.Viewing the newly marked show +image::WatchedTheOffice.png[width="790"] + +Alternatively, you may click on the watched checkbox to toggle between whether a show is watched as indicated by the red arrow in the image below. + +.Marking a show using the checkbox +image::TheOfficeWatchCommand.png[width="790"] **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* The `index` refers to the index number shown in the displayed watchlist. The index *must be a positive integer* 1, 2, 3, ... +* Any number of the optional fields may be provided. +* Having only the index of the show will mark/unmark the show as watched. +* Having the index and the episode number of the show will update the cumulative number of episodes of the show that are watched. +* Having the index and the season number of the show will update the cumulative number of seasons of the show that are watched. +* Having the index, season number and the episode number of the show will update the last watched episode to be the indicated episode of the indicated season of the show. **** +[TIP] +Using the `watch` command on an already watched show will un-mark the show as watched. +[TIP] +`watch` would only work in **Watchlist page** and **Watched page**. 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. +* `watch 1` + +Marks/un-marks the first show of the list as watched. +* `watch 2 e/20` + +Marks the first 20 episodes of the second show of the list as watched. +* `watch 2 s/5` + +Marks all episodes of the first 5 seasons of the second show as watched. +* `watch 3 s/5 e/2` + +Marks all episodes up to and including the second episode of the fifth season of the third show in the list as watched. -=== Locating persons by name: `find` +// end::watch[] +==== Edit a show's details : `edit` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Edits an existing show in the list + +Format: `edit INDEX [n/SHOW_NAME] [d/DATE_OF_RELEASE] [w/WATCHED] [r/RUNNING_TIME] [s/DESCRIPTION] [a/ACTOR]...` **** -* 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` +* Edits the show at the specified `INDEX`. The index refers to the index number shown in the displayed watchlist. 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 actors, the existing actors of the show will be removed i.e adding of actors is not cumulative. +* You can remove all the show's actors by typing `a/` without specifying any actors after it. **** +[TIP] +`edit` would only work in **Watchlist page** and **Watched page**. + Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `edit 1 n/Joker a/Joaquin Phoenix` + +Edits the name and actor name of the 1st show in the list to be `Joker` and `Joaquin Phoenix` respectively. +* `edit 2 a/` + +Clears all existing actors of the 2nd show in the list. + +//tag::search[] + +==== Search for a show: `search` + +Searches for shows whose names contain any of the given keywords from the online database, unless specified to be from +the watchlist or watched list. + + +Format: + +* by name: + +`search n/SHOW_NAME... [g/GENRE]... [a/ACTOR_NAME]... [o/FROM_ONLINE] [t/SHOW_TYPE] [w/HAS_WATCHED]` + +* by genre: + +`search g/GENRE... [n/SHOW_NAME]... [a/ACTOR_NAME]... [o/FROM_ONLINE] [t/SHOW_TYPE] [w/HAS_WATCHED]` + +* by actor (from watchlist only): + +`search a/ACTOR_NAME... [n/SHOW_NAME]... [g/GENRE]... [o/FROM_ONLINE] [t/SHOW_TYPE] [w/HAS_WATCHED]` + +** `SHOW_NAME`, `GENRE`, `ACTOR_NAME` can be any words, as long as it does not contain `/`. +** `FROM_ONLINE` and `HAS_WATCHED` can only be `yes`, `true`, `no` or `false`. +** `SHOW_TYPE` can only be `movie` or `tv`. + +==== +[TIP] +Special commands to take note of: + +`o/no`: to search from watchlist or watched list + +`o/no w/no`: to search from watchlist only + +`o/no w/yes`: to search from watched list only +==== +==== +[NOTE] +When searching based on genre online, only movies will be searched. + +{sp} + +For `[o/FROM_ONLINE] [t/SHOW_TYPE] [w/HAS_WATCHED]`, if multiple entries are entered, only the last would be considered. e.g. "search n/Avengers t/movie t/tv" will be interpreted as "search n/Avengers t/tv". + +{sp} + +Space between prefix and slash is not acceptable. e.g. "n /Avengers" will throw an error. + +Space after prefix is acceptable. e.g. "n/ Avengers" will be interpreted as "n/Avengers". +==== + +==== +[NOTE] +The search is case insensitive. e.g "avengers" will match "Avengers". + +The order of the keywords matter. e.g. "Chris Evans" will not match "Evans Chris". + +Not only full words will be matched. e.g. "Joke" will also match with "Joker". +==== +{sp} + +*Example Usage:* + +You may want to search for movies named "Avengers" and also movies with an actor named "Tom". +As shown below, assume that your watchlist only has a movie "Avengers: Endgame" that you have watched. + +.Current *Watched Page* with a movie watched "Avengers: Endgame" +image::watchedPageWithAvengerEndgame.png[width="790"] +{sp} + +[start=1] +1. Navigate to the *Search page* by one of the following ways: + +* clicking on the *search* tab +* hitting the keyboard shortcut kbd:[3] +* typing `3` in the command box and pressing kbd:[enter] +* typing `search` in the command box and pressing kbd:[enter] as shown in the figure below + +.Entering of the `search` command +image::searchCommand.png[width="790"] +{sp} + +After you have entered the command, you will be led to the *Search page* as shown below. + +.Graphical Interface of the *Search Page* +image::emptySearchPage.png[width="790"] + +[TIP] +You may skip Step 1 as you will be automatically be navigated to the *Search Page* when you key in any valid `search` +command, such as the one in Step 2 below. + +{sp} + +[start=2] +1. Enters `search n/Avengers a/Tom t/movie` into the command box in the *Search Page* as shown below. + +.Entering of the `search` command to search for shows +image::searchAvengerUserInput.png[width="790"] + +{sp} + +Press kbd:[enter] after entering the information shown above and wait for the information to load. + +.Loading Screen of the *Search page* +image::searchPageLoadingScreen.png[width="790"] + +The loading page as shown above will appear while EzWatchlist searches for your shows. + +[TIP] +You can go to the other pages in the mean time. Do allow some time for the search to load. + +{sp} + +[start=3] +1. Search page shows the list of shows based on `search n/Avengers a/Tom t/movie`. + +{sp} + +*Scenario A: You are offline* + +Only movies from the watchlist or offline database with the movie name "Avengers" or actor name "Tom" will be shown, +as seen in the figure below. + +.*Search Page* showing the search results when offline +image::searchPageSearchedForAvengersWhenOffline.png[width="790"] + +{sp} + +*Scenario B: You are online* + +Only movies from the online database with the movie name "Avengers" or actor name "Tom" will be shown, as seen in the +figure below. + +.*Search Page* showing the search results when online +image::searchPageSearchedForAvengersWhenOnline.png[width="790"] +{sp} + +Examples: + +* By name: +** `search n/Joker o/no` + +Returns shows with the name "Joker" within the watchlist +** `search n/Avengers g/Science Fiction t/movie n/Spiderman` + +(If online) Returns movies from the online database with the name "Avenger" or "Spiderman" and movies with the genre "Science Fiction" + +(If offline) Returns movies from the internal database, watchlist and watched-list with the name "Avenger" or "Spiderman" and movies with the genre "Science Fiction" +* By genre: +** `search g/Action t/movie` + +(If online) Returns movies from the online database with the genre "Action" + +(If offline) Returns movies from the internal database, watchlist and watched-list with the genre "Action" +* By actor: (from watchlist only) +** `search a/Tom o/no w/no` + +Returns shows within the watchlist with actor named "Tom" + +// end::search[] + +// tag::sync[] +==== Synchronise user\'s show data: `sync` + +If user has lack of information about a certain show in their watch list, User can use `sync` command. +Synchronise, `sync`, command will transfer all the information about a certain show (for example: show A) found in +search result page with a show (for example: show A') that has the same name as Show A found in watch list. + +==== +Note: + +1. The show in the watch list must have at least a name and type. +2. Names are not-case sensitive. +3. `Sync` will *WRITE OVER* all the information of show with same name found in watchlist. +==== + +Format: `sync INDEX` + +INDEX is a positive integer and is limited to the number of shows found in search result page. + +*Example Usage* + +Scenario 1: User has already input 'Titanic' show into watchlist manually. + +image::Sync1.png[width="350"] + +Step 1. User searches for `Titanic` show in search page. + +Step 2. `Titanic` result page will be displayed. User input `sync 2` to synchronise movie at index 2 of the list with a movie +of same name found in watchlist. + +image::Sync5.png[width="500"] + +Step 3. Go to watchlist. New information of `Titanic` in watchlist will be displayed. + +image::Sync6.png[width="500"] + +Scenario 2. User has not input Titanic show into watchlist manually. + +image::Sync21.png[width="350"] + +Step 1. Similar to scenario 1, user searches for `Titanic` show in search page. + +image::Sync4.png[width="500"] + +Step 2. `Titanic` result page will be displayed. If user were to sync any index, error message will be displayed +because there is no show of similar name found in watch list. + +image::Sync22.png[width="500"] + +Step 3. User can choose to use `add 1` command to add show of index 1 found in search result page. + +image::Sync23.png[width="500"] + +// end::sync[] // tag::delete[] -=== Deleting a person : `delete` +==== Deleting a show : `delete` -Deletes the specified person from the address book. + +Deletes the specified show from the watchlist. + Format: `delete INDEX` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Deletes the show at the specified `INDEX`. +* The index refers to the index number shown in the displayed watchlist. * The index *must be a positive integer* 1, 2, 3, ... **** +[NOTE] +`delete` would only work in **Watchlist page** and **Watched page**. + Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + +* `delete 2` + +Deletes the 2nd show in the watchlist. +* `search Angry` + `delete 1` + -Deletes the 1st person in the results of the `find` command. +Deletes the 1st show in the results of the `search` command. // end::delete[] -=== Clearing all entries : `clear` - -Clears all entries from the address book. + -Format: `clear` - -=== Exiting the program : `exit` +==== Exiting the program : `exit` Exits the program. + Format: `exit` -=== Saving the data +==== Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +EzWatchlist's data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` - -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] - == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous EzWatchlist folder. == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` +* *Add* : `add n/SHOW_NAME t/SHOW_TYPE [d/DATE_OF_RELEASE] [w/WATCHED] [r/RUNNING_TIME] [s/DESCRIPTION] [a/ACTOR_NAME]` + +e.g. `add n/John Wick t/movie d/24 OCTOBER 2014 w/false r/101 s/An ex-hit-man comes out of retirement to track down the gangsters that killed his dog and took everything from him. a/Keanu Reeves` +* *Add from search result page* : `add INDEX` + +e.g. `add 1` +* *Sync* : `sync INDEX` + +e.g. `sync 2` * *Clear* : `clear` +* *Watch* : `watch INDEX [e/EPISODE_NUMBER] [s/SEASON_NUMBER]` * *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` +* *Edit* : `edit INDEX [n/SHOW_NAME] [d/DATE_OF_RELEASE] [w/WATCHED] [r/RUNNING_TIME] [s/DESCRIPTION] [a/ACTOR]...` + +e.g. `edit 2 n/John Wick a/Johnny Depp` +* *Search* +** *by name* : `search n/SHOW_NAME... [g/GENRE]... [a/ACTOR_NAME]... [o/FROM_ONLINE] [t/TYPE] [w/IS_WATCH]` + +e.g. `search n/Joker o/no` searches for shows with the name 'Joker' within the watchlist +** *by genre* : `search g/GENRE... [n/SHOW_NAME]... [a/ACTOR_NAME]... [o/FROM_ONLINE] [t/TYPE] [w/IS_WATCH]` + +e.g. `search g/Action t/movie` searches for movies from the online database with the genre 'Action' +** *by actor from watchlist* : `search a/ACTOR_NAME... [n/SHOW_NAME]... [g/GENRE]... [o/FROM_ONLINE] [t/TYPE] [w/IS_WATCH]` + +e.g. `search a/Tom` searches from shows within the watchlist with actor named 'Tom' + +* *Exit* : `exit` * *Help* : `help` diff --git a/docs/UserGuide.pdf b/docs/UserGuide.pdf new file mode 100644 index 00000000000..9461528edc5 Binary files /dev/null and b/docs/UserGuide.pdf differ diff --git a/docs/UsingAppVeyor.adoc b/docs/UsingAppVeyor.adoc index 12a7a89ac68..8adeeafe2d1 100644 --- a/docs/UsingAppVeyor.adoc +++ b/docs/UsingAppVeyor.adoc @@ -6,12 +6,6 @@ ifdef::env-github[] :note-caption: :information_source: endif::[] -[NOTE] -==== -This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. -==== - https://www.appveyor.com/[AppVeyor] is a _Continuous Integration_ platform for GitHub projects. It runs its builds on Windows virtual machines. AppVeyor can run the project's tests automatically whenever new code is pushed to the repo. This ensures that existing functionality and features have not been broken on Windows by the changes. diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index a12ab09cc9c..5e2f02bd279 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -8,12 +8,6 @@ ifdef::env-github[] :note-caption: :information_source: endif::[] -[NOTE] -==== -This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. -==== - == Configuring Checkstyle-IDEA . Install the Checkstyle-IDEA plugin by going to `File` > `Settings` (Windows/Linux), or `IntelliJ IDEA` > `Preferences...` (macOS). + diff --git a/docs/diagram.uml b/docs/diagram.uml new file mode 100644 index 00000000000..a5e9969b0f9 --- /dev/null +++ b/docs/diagram.uml @@ -0,0 +1,18 @@ + + + JAVA + + + seedu.ezwatchlist.api.model.ApiMain + + + + + + seedu.ezwatchlist.api.model.ApiMain + + + All + private + + diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..b9618cc84f5 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,7 +3,7 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":WatchListParser" as WatchListParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR @@ -16,17 +16,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> WatchListParser : parseCommand("delete 1") +activate WatchListParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +WatchListParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> WatchListParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +WatchListParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -36,14 +36,14 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : d deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> WatchListParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> WatchListParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +WatchListParser --> LogicManager : d +deactivate WatchListParser LogicManager -> DeleteCommand : execute() activate DeleteCommand diff --git a/docs/diagrams/GetFavouriteSequenceDiagram.puml b/docs/diagrams/GetFavouriteSequenceDiagram.puml new file mode 100644 index 00000000000..39a1af93c47 --- /dev/null +++ b/docs/diagrams/GetFavouriteSequenceDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml + +box Statistics LOGIC_COLOR_T1 +participant ":Statistics" as Statistics LOGIC_COLOR +end box + + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager LOGIC_COLOR +end box + +[-> Statistics : getFavouriteGenre() +activate Statistics + +Statistics -> ModelManager: getWatchedShowList() +activate ModelManager + +Statistics <-- ModelManager: watchedlist +deactivate ModelManager + +[<--Statistics: favouriteGenres +deactivate Statistics + +@enduml diff --git a/docs/diagrams/GetForgottenSequenceDiagram.puml b/docs/diagrams/GetForgottenSequenceDiagram.puml new file mode 100644 index 00000000000..a50f1031481 --- /dev/null +++ b/docs/diagrams/GetForgottenSequenceDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml + +box Statistics LOGIC_COLOR_T1 +participant ":Statistics" as Statistics LOGIC_COLOR +end box + + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager LOGIC_COLOR +end box + +[-> Statistics : getForgotten() +activate Statistics + +Statistics -> ModelManager: getWatchlist() +activate ModelManager + +Statistics <-- ModelManager: watchlist +deactivate ModelManager + +[<--Statistics: forgotten +deactivate Statistics + +@enduml diff --git a/docs/diagrams/GetRecommendationSequenceDiagram.puml b/docs/diagrams/GetRecommendationSequenceDiagram.puml new file mode 100644 index 00000000000..c0972fa7ee0 --- /dev/null +++ b/docs/diagrams/GetRecommendationSequenceDiagram.puml @@ -0,0 +1,25 @@ +@startuml +!include style.puml + +box Statistics LOGIC_COLOR_T1 +participant ":Statistics" as Statistics LOGIC_COLOR +end box + + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager LOGIC_COLOR +end box + +[-> Statistics : getRecommendation() +activate Statistics + +Statistics -> ModelManager: getWatchedShowList() +activate ModelManager + +Statistics <-- ModelManager: watchlist +deactivate ModelManager + +[<--Statistics: recommendations +deactivate Statistics + +@enduml diff --git a/docs/diagrams/SearchActivityDiagram.puml b/docs/diagrams/SearchActivityDiagram.puml new file mode 100644 index 00000000000..4d52d23b342 --- /dev/null +++ b/docs/diagrams/SearchActivityDiagram.puml @@ -0,0 +1,51 @@ +@startuml +skinparam backgroundColor white + +skinparam activity { + StartColor RoyalBlue + EndColor RoyalBlue + BackgroundColor RoyalBlue + arrowColor Blue + activityDiamondBackgroundColor Blue + activityBarColor blue + BorderColor blue + ControlBackgroundColor white + FontColor Snow + FontName Calibri + FontSize 16 +} + +skinparam activityDiamond { + BackgroundColor Snow + BorderColor blue + + FontSize 24 +} + +start + +:User launches EzWatchlist; +:User goes to the **Search Page**; +:User executes Search command; + +while () is ([Incorrect command format]) + : EzWatchlist prompts user about the correct command format; +endwhile ([Correct command format]) + +: **Loading page** shown; + +if () then ([Online]) + :EzWatchlist searches from + the online database; +else ([Offline]) + : EzWatchlist searches from + the offline database, user's + watchlist and watched-list; +endif + + +:Search result shown on the **Search Page**; + +stop + +@enduml diff --git a/docs/diagrams/SearchSequenceDiagram.puml b/docs/diagrams/SearchSequenceDiagram.puml new file mode 100644 index 00000000000..7f9a04bd1b8 --- /dev/null +++ b/docs/diagrams/SearchSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":WatchListParser" as WatchListParser LOGIC_COLOR +participant ":SearchCommandParser" as SearchCommandParser LOGIC_COLOR +participant "s:SearchCommand" as SearchCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute\n("search n/Avengers o/no",\n mainWindow, "search-list") +activate LogicManager + +LogicManager -> WatchListParser : parseCommand\n("search n/Avengers o/no",\n "search-list") +activate WatchListParser + +create SearchCommandParser +WatchListParser -> SearchCommandParser +activate SearchCommandParser + +SearchCommandParser --> WatchListParser +deactivate SearchCommandParser + +WatchListParser -> SearchCommandParser : parse\n("n/Avengers o/no",\n "search-list") +activate SearchCommandParser + +create SearchCommand +SearchCommandParser -> SearchCommand +activate SearchCommand + +SearchCommand --> SearchCommandParser : s +deactivate SearchCommand + +SearchCommandParser --> WatchListParser : s +deactivate SearchCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +SearchCommandParser -[hidden]-> WatchListParser +destroy SearchCommandParser + +WatchListParser --> LogicManager : s +deactivate WatchListParser + +LogicManager -> SearchCommand : execute(model) +activate SearchCommand + + +SearchCommand -> SearchCommand : searchByName(model) +activate SearchCommand + +SearchCommand -> Model : getShowFromWatchlistIfHasName\n("Avengers", model) +activate Model +return + +deactivate SearchCommand + +deactivate Model + +create CommandResult +SearchCommand -> CommandResult +activate CommandResult + +CommandResult --> SearchCommand +deactivate CommandResult + +SearchCommand --> LogicManager : result +deactivate SearchCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StatisticsActivityDiagram.puml b/docs/diagrams/StatisticsActivityDiagram.puml new file mode 100644 index 00000000000..48f1448837f --- /dev/null +++ b/docs/diagrams/StatisticsActivityDiagram.puml @@ -0,0 +1,13 @@ +@startuml +start +:User launches app; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +:Statistics calculates +the 3 results and +populate the panel; + +stop +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..e157adc2f22 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -6,19 +6,26 @@ skinparam classBackgroundColor STORAGE_COLOR Interface Storage <> Interface UserPrefsStorage <> -Interface AddressBookStorage <> +Interface WatchListStorage <> Class StorageManager Class JsonUserPrefsStorage -Class JsonAddressBookStorage +Class JsonWatchListStorage StorageManager .left.|> Storage StorageManager o-right-> UserPrefsStorage -StorageManager o--> AddressBookStorage +StorageManager o--> WatchListStorage JsonUserPrefsStorage .left.|> UserPrefsStorage -JsonAddressBookStorage .left.|> AddressBookStorage -JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage -JsonSerializableAddressBookStorage .right.> JsonSerializablePerson -JsonSerializablePerson .right.> JsonAdaptedTag +JsonWatchListStorage .left.|> WatchListStorage +JsonWatchListStorage .down.> JsonSerializableWatchList +JsonSerializableWatchList .right.> JsonAdaptedShows +JsonAdaptedShows .right.> JsonAdaptedMovie +JsonAdaptedShows .down.> JsonAdaptedTvShow +JsonAdaptedMovie .down.> JsonAdaptedGenre +JsonAdaptedTvShow .down.> JsonAdaptedGenre +JsonAdaptedMovie .down.> JsonAdaptedActor +JsonAdaptedTvShow .down.> JsonAdaptedActor +JsonAdaptedTvShow .down.> JsonAdaptedTvSeason +JsonAdaptedTvSeason .down.> JsonAdaptedEpisode @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..1c7438173c2 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,12 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class ShowListPanel +class WatchedPanel +class SearchPanel +class StatisticsPanel +class LoadingPanel +Class ShowCard Class StatusBarFooter Class CommandBox } @@ -33,25 +37,33 @@ UiManager -down-> MainWindow MainWindow --> HelpWindow MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel +MainWindow *-down-> ShowListPanel +MainWindow *-down-> WatchedPanel +MainWindow *-down-> SearchPanel +MainWindow *-down-> StatisticsPanel +MainWindow *-down-> LoadingPanel MainWindow *-down-> StatusBarFooter -PersonListPanel -down-> PersonCard +ShowListPanel -down-> ShowCard +WatchedPanel -down-> ShowCard +SearchPanel -down-> ShowCard +StatisticsPanel -down-> ShowCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +ShowListPanel --|> UiPart +ShowCard --|> UiPart StatusBarFooter --|> UiPart +LoadingPanel -down-|> UiPart HelpWindow -down-|> UiPart -PersonCard ..> Model +ShowCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +ShowListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/WatchActivityDiagram.puml b/docs/diagrams/WatchActivityDiagram.puml new file mode 100644 index 00000000000..dbfd965b73e --- /dev/null +++ b/docs/diagrams/WatchActivityDiagram.puml @@ -0,0 +1,54 @@ +@startuml +skinparam backgroundColor white +skinparam activity { + StartColor RoyalBlue + EndColor RoyalBlue + BackgroundColor RoyalBlue + arrowColor Blue + activityDiamondBackgroundColor Blue + activityBarColor blue + BorderColor blue + ControlBackgroundColor white + FontColor Snow + FontName Calibri + FontSize 16 +} + +skinparam activityDiamond { + BackgroundColor Snow + BorderColor blue + FontSize 24 +} + +start +:User executes Watch command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([Fields specified]) +if () then ( [Seasons and +episodes specified]) + :Set last watched episode + to episode number + of season number; +else ([else]) + if () then ([Seasons specified]) + : Set last watched + episode to last episode + of season number; + else ([Episodes specified]) + : Set last watched + episode to specified + episode number; + endif +endif +else ([else]) + :Toggle the watched + status of the show; +endif +:Show information is updated; + +:Appropriate result is shown to the user; +stop +@enduml diff --git a/docs/diagrams/WatchSequenceDiagram.puml b/docs/diagrams/WatchSequenceDiagram.puml new file mode 100644 index 00000000000..b3899a9aa41 --- /dev/null +++ b/docs/diagrams/WatchSequenceDiagram.puml @@ -0,0 +1,80 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":WatchListParser" as WatchListParser LOGIC_COLOR +participant ":WatchCommandParser" as WatchCommandParser LOGIC_COLOR +participant "w:WatchCommand" as WatchCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("watch 1") +activate LogicManager + +LogicManager -> WatchListParser : parseCommand("watch 1") +activate WatchListParser + +create WatchCommandParser +WatchListParser -> WatchCommandParser +activate WatchCommandParser + +WatchCommandParser --> WatchListParser +deactivate WatchCommandParser + +WatchListParser -> WatchCommandParser : parse("1") +activate WatchCommandParser + +create WatchCommand +WatchCommandParser -> WatchCommand +activate WatchCommand + +WatchCommand --> WatchCommandParser : w +deactivate WatchCommand + +WatchCommandParser --> WatchListParser : w +deactivate WatchCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +WatchCommandParser -[hidden]-> WatchListParser +destroy WatchCommandParser + +WatchListParser --> LogicManager : w +deactivate WatchListParser + +LogicManager -> WatchCommand : execute() +activate WatchCommand + +WatchCommand -> Model : getFilteredShowList() +activate Model + +Model --> WatchCommand +deactivate Model + +WatchCommand -> WatchCommand : createEditedShow(showToEdit) +activate WatchCommand +WatchCommand --> WatchCommand +deactivate WatchCommand + +WatchCommand -> Model : setShow(showToEdit, EditedShow) +activate Model + +Model --> WatchCommand +deactivate Model + +create CommandResult +WatchCommand -> CommandResult +activate CommandResult + +CommandResult --> WatchCommand +deactivate CommandResult + +WatchCommand --> LogicManager : result +deactivate WatchCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/plantuml/AddActivitySequenceDiagram.puml b/docs/diagrams/plantuml/AddActivitySequenceDiagram.puml new file mode 100644 index 00000000000..5c55143d0a8 --- /dev/null +++ b/docs/diagrams/plantuml/AddActivitySequenceDiagram.puml @@ -0,0 +1,37 @@ +@startuml +skinparam backgroundColor white +skinparam activity { + StartColor RoyalBlue + EndColor RoyalBlue + BackgroundColor RoyalBlue + arrowColor Blue + activityDiamondBackgroundColor Blue + activityBarColor blue + BorderColor blue + ControlBackgroundColor white + FontColor Snow + FontName Calibri + FontSize 16 +} + +skinparam activityDiamond { + BackgroundColor Snow + BorderColor blue + FontSize 24 +} +start +:User executes Add command + "add n/Joker t/movie"; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([No existing show found in watchlist]) + : 'Joker' show of type 'movie' is + added to watch list; + stop +else([Existing show found in watchlist]) + : Error result message will be + displayed to the user; +stop +@enduml diff --git a/docs/diagrams/plantuml/AddActivitySequenceDiagram2.puml b/docs/diagrams/plantuml/AddActivitySequenceDiagram2.puml new file mode 100644 index 00000000000..e02dc46ba0c --- /dev/null +++ b/docs/diagrams/plantuml/AddActivitySequenceDiagram2.puml @@ -0,0 +1,37 @@ +@startuml +skinparam backgroundColor white +skinparam activity { + StartColor RoyalBlue + EndColor RoyalBlue + BackgroundColor RoyalBlue + arrowColor Blue + activityDiamondBackgroundColor Blue + activityBarColor blue + BorderColor blue + ControlBackgroundColor white + FontColor Snow + FontName Calibri + FontSize 16 +} + +skinparam activityDiamond { + BackgroundColor Snow + BorderColor blue + FontSize 24 +} +start +:User executes Add command (Extension) + Add INDEX; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([INDEX IS VALID]) + : Show of INDEX found in search + result page is added to watchlist; + stop +else([INDEX IS INVALID]) + : Error result message will be displayed + to the user; +stop +@enduml diff --git a/docs/diagrams/plantuml/AddSequenceDiagram.puml b/docs/diagrams/plantuml/AddSequenceDiagram.puml new file mode 100644 index 00000000000..410aab4e412 --- /dev/null +++ b/docs/diagrams/plantuml/AddSequenceDiagram.puml @@ -0,0 +1,53 @@ +@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 +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":VersionedAddressBook" as VersionedAddressBook 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 -> Model : undoAddressBook() +activate Model + +Model -> VersionedAddressBook : undo() +activate VersionedAddressBook + +VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) +VersionedAddressBook --> Model : +deactivate VersionedAddressBook + +Model --> UndoCommand +deactivate Model + +UndoCommand --> LogicManager : result +deactivate UndoCommand +UndoCommand -[hidden]-> LogicManager : result +destroy UndoCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/plantuml/SyncActivityDiagram.puml b/docs/diagrams/plantuml/SyncActivityDiagram.puml new file mode 100644 index 00000000000..ebe1e9b2714 --- /dev/null +++ b/docs/diagrams/plantuml/SyncActivityDiagram.puml @@ -0,0 +1,47 @@ +@startuml +skinparam backgroundColor white +skinparam activity { + StartColor RoyalBlue + EndColor RoyalBlue + BackgroundColor RoyalBlue + arrowColor Blue + activityDiamondBackgroundColor Blue + activityBarColor blue + BorderColor blue + ControlBackgroundColor white + FontColor Snow + FontName Calibri + FontSize 16 +} + +skinparam activityDiamond { + BackgroundColor Snow + BorderColor blue + FontSize 24 +} + +start +:User has searched for show in search page; +:User executes Sync command + "Sync INDEX"; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([INDEX IS VALID]) +if () then ([Show of same name found in watchlist]) + : Show of INDEX found in search + result page is synchronised with + the same show in watchlist.; + stop +else ([ELSE]) + : Error result message displayed: + "Show cannot be found in watchlist + to be sync-ed"; + stop + endif +else([INDEX IS INVALID]) + : Error result message will be displayed + to the user; +stop +@enduml diff --git a/docs/images/105-1054061_check-mark-comments-check-icon.png b/docs/images/105-1054061_check-mark-comments-check-icon.png new file mode 100644 index 00000000000..cbbd7194575 Binary files /dev/null and b/docs/images/105-1054061_check-mark-comments-check-icon.png differ diff --git a/docs/images/117912.png b/docs/images/117912.png new file mode 100644 index 00000000000..c329db256d5 Binary files /dev/null and b/docs/images/117912.png differ diff --git a/docs/images/55369.png b/docs/images/55369.png new file mode 100644 index 00000000000..ff649a6e026 Binary files /dev/null and b/docs/images/55369.png differ diff --git a/docs/images/61-512.png b/docs/images/61-512.png new file mode 100644 index 00000000000..57ac51592cb Binary files /dev/null and b/docs/images/61-512.png differ diff --git a/docs/images/AddActivityDiagram2.png b/docs/images/AddActivityDiagram2.png new file mode 100644 index 00000000000..3b3e643541b Binary files /dev/null and b/docs/images/AddActivityDiagram2.png differ diff --git a/docs/images/AddActivitySequenceDiagram.png b/docs/images/AddActivitySequenceDiagram.png new file mode 100644 index 00000000000..c31635a7c25 Binary files /dev/null and b/docs/images/AddActivitySequenceDiagram.png differ diff --git a/docs/images/AddActivitySequenceDiagram2.png b/docs/images/AddActivitySequenceDiagram2.png new file mode 100644 index 00000000000..32a0f6fbed5 Binary files /dev/null and b/docs/images/AddActivitySequenceDiagram2.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 00000000000..4618f843b27 Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/AddSequenceDiagram2.png b/docs/images/AddSequenceDiagram2.png new file mode 100644 index 00000000000..de3a8c153b5 Binary files /dev/null and b/docs/images/AddSequenceDiagram2.png differ diff --git a/docs/images/ApiClass.png b/docs/images/ApiClass.png new file mode 100644 index 00000000000..0ba3f2dc9a8 Binary files /dev/null and b/docs/images/ApiClass.png differ diff --git a/docs/images/ApiClassDiagram.png b/docs/images/ApiClassDiagram.png new file mode 100644 index 00000000000..588eef6f16c Binary files /dev/null and b/docs/images/ApiClassDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..115d085ceb4 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index bc7ed18ae29..ab6fa6f16e3 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CK_PPP.png b/docs/images/CK_PPP.png new file mode 100644 index 00000000000..366d44308b6 Binary files /dev/null and b/docs/images/CK_PPP.png differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png index 4de4fa4bf2b..800464a0a0d 100644 Binary files a/docs/images/CommitActivityDiagram.png and b/docs/images/CommitActivityDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..908c41a53ce 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/GetFavouriteSequenceDiagram.png b/docs/images/GetFavouriteSequenceDiagram.png new file mode 100644 index 00000000000..d4ac71e4886 Binary files /dev/null and b/docs/images/GetFavouriteSequenceDiagram.png differ diff --git a/docs/images/GetForgottenSequenceDiagram.png b/docs/images/GetForgottenSequenceDiagram.png new file mode 100644 index 00000000000..212b40000ef Binary files /dev/null and b/docs/images/GetForgottenSequenceDiagram.png differ diff --git a/docs/images/GetRecommendationSequenceDiagram.png b/docs/images/GetRecommendationSequenceDiagram.png new file mode 100644 index 00000000000..0b5d89eefb8 Binary files /dev/null and b/docs/images/GetRecommendationSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..58fe159c7ac 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..5db713cd74a 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/SeEduLogo.png b/docs/images/SeEduLogo.png deleted file mode 100644 index 31ad50b6f88..00000000000 Binary files a/docs/images/SeEduLogo.png and /dev/null differ diff --git a/docs/images/SearchActivityDiagram.png b/docs/images/SearchActivityDiagram.png new file mode 100644 index 00000000000..4fe939b0bb9 Binary files /dev/null and b/docs/images/SearchActivityDiagram.png differ diff --git a/docs/images/SearchSequenceDiagram.png b/docs/images/SearchSequenceDiagram.png new file mode 100644 index 00000000000..1204ab10de2 Binary files /dev/null and b/docs/images/SearchSequenceDiagram.png differ diff --git a/docs/images/StatisticsActivityDiagram.png b/docs/images/StatisticsActivityDiagram.png new file mode 100644 index 00000000000..5cea7c8ddc9 Binary files /dev/null and b/docs/images/StatisticsActivityDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..14aad71517c 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Sync1.png b/docs/images/Sync1.png new file mode 100644 index 00000000000..66750df2004 Binary files /dev/null and b/docs/images/Sync1.png differ diff --git a/docs/images/Sync2.png b/docs/images/Sync2.png new file mode 100644 index 00000000000..fe138ac46fa Binary files /dev/null and b/docs/images/Sync2.png differ diff --git a/docs/images/Sync21.png b/docs/images/Sync21.png new file mode 100644 index 00000000000..8620c4c32ce Binary files /dev/null and b/docs/images/Sync21.png differ diff --git a/docs/images/Sync22.png b/docs/images/Sync22.png new file mode 100644 index 00000000000..3a762ff847c Binary files /dev/null and b/docs/images/Sync22.png differ diff --git a/docs/images/Sync23.png b/docs/images/Sync23.png new file mode 100644 index 00000000000..3478f00397a Binary files /dev/null and b/docs/images/Sync23.png differ diff --git a/docs/images/Sync3.png b/docs/images/Sync3.png new file mode 100644 index 00000000000..296f4e5ffae Binary files /dev/null and b/docs/images/Sync3.png differ diff --git a/docs/images/Sync4.png b/docs/images/Sync4.png new file mode 100644 index 00000000000..227fcf7b36c Binary files /dev/null and b/docs/images/Sync4.png differ diff --git a/docs/images/Sync5.png b/docs/images/Sync5.png new file mode 100644 index 00000000000..6461cdaef3f Binary files /dev/null and b/docs/images/Sync5.png differ diff --git a/docs/images/Sync6.png b/docs/images/Sync6.png new file mode 100644 index 00000000000..378558bdb8d Binary files /dev/null and b/docs/images/Sync6.png differ diff --git a/docs/images/SyncActivityDiagram.png b/docs/images/SyncActivityDiagram.png new file mode 100644 index 00000000000..c5b83448bd3 Binary files /dev/null and b/docs/images/SyncActivityDiagram.png differ diff --git a/docs/images/SyncSequenceDiagram.png b/docs/images/SyncSequenceDiagram.png new file mode 100644 index 00000000000..8f9e78bdfe8 Binary files /dev/null and b/docs/images/SyncSequenceDiagram.png differ diff --git a/docs/images/TheOfficeWatchCommand.png b/docs/images/TheOfficeWatchCommand.png new file mode 100644 index 00000000000..ad910d76aac Binary files /dev/null and b/docs/images/TheOfficeWatchCommand.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..f32d5c7c779 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..c6cfadd9eea 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png index 8f7538cd884..1fec2a09a7f 100644 Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png index df9908d0948..2e286616fe1 100644 Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 36519c1015b..a4ef14d12df 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 19959d01712..6303c9363b1 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png index 4c623e4f2c5..c085a9347af 100644 Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png index 84ad2afa6bd..3fd8a453629 100644 Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png index 6addcd3a8d9..30e89b766fb 100644 Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ diff --git a/docs/images/Watch.png b/docs/images/Watch.png new file mode 100644 index 00000000000..0811e921be0 Binary files /dev/null and b/docs/images/Watch.png differ diff --git a/docs/images/WatchActivityDiagram.png b/docs/images/WatchActivityDiagram.png new file mode 100644 index 00000000000..7922c5552c0 Binary files /dev/null and b/docs/images/WatchActivityDiagram.png differ diff --git a/docs/images/WatchSequenceDiagram.png b/docs/images/WatchSequenceDiagram.png new file mode 100644 index 00000000000..868a20ac222 Binary files /dev/null and b/docs/images/WatchSequenceDiagram.png differ diff --git a/docs/images/WatchTheOffice.png b/docs/images/WatchTheOffice.png new file mode 100644 index 00000000000..f487af89b08 Binary files /dev/null and b/docs/images/WatchTheOffice.png differ diff --git a/docs/images/WatchedTheOffice.png b/docs/images/WatchedTheOffice.png new file mode 100644 index 00000000000..4b72a55d032 Binary files /dev/null and b/docs/images/WatchedTheOffice.png differ diff --git a/docs/images/activityDiagram.png b/docs/images/activityDiagram.png new file mode 100644 index 00000000000..24954e5c4ec Binary files /dev/null and b/docs/images/activityDiagram.png differ diff --git a/docs/images/add1.png b/docs/images/add1.png new file mode 100644 index 00000000000..082de5baf9b Binary files /dev/null and b/docs/images/add1.png differ diff --git a/docs/images/add2.png b/docs/images/add2.png new file mode 100644 index 00000000000..48a30be8354 Binary files /dev/null and b/docs/images/add2.png differ diff --git a/docs/images/add3.png b/docs/images/add3.png new file mode 100644 index 00000000000..a02c3a92151 Binary files /dev/null and b/docs/images/add3.png differ diff --git a/docs/images/add_entry_page.png b/docs/images/add_entry_page.png new file mode 100644 index 00000000000..eb310583dfa Binary files /dev/null and b/docs/images/add_entry_page.png differ diff --git a/docs/images/applogo.png b/docs/images/applogo.png new file mode 100644 index 00000000000..d18b70719ec Binary files /dev/null and b/docs/images/applogo.png differ diff --git a/docs/images/commandline.png b/docs/images/commandline.png new file mode 100644 index 00000000000..2bb9beb1a89 Binary files /dev/null and b/docs/images/commandline.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/details_page.png b/docs/images/details_page.png new file mode 100644 index 00000000000..9cf2614a663 Binary files /dev/null and b/docs/images/details_page.png differ diff --git a/docs/images/emptySearchPage.png b/docs/images/emptySearchPage.png new file mode 100644 index 00000000000..bd7efe5a203 Binary files /dev/null and b/docs/images/emptySearchPage.png differ diff --git a/docs/images/getFavouriteGenre.png b/docs/images/getFavouriteGenre.png new file mode 100644 index 00000000000..3cb5bda4003 Binary files /dev/null and b/docs/images/getFavouriteGenre.png differ diff --git a/docs/images/getForgotten.png b/docs/images/getForgotten.png new file mode 100644 index 00000000000..5b281aedb8f Binary files /dev/null and b/docs/images/getForgotten.png differ diff --git a/docs/images/getMovieRecommendationsSequenceDiagram.png b/docs/images/getMovieRecommendationsSequenceDiagram.png new file mode 100644 index 00000000000..9f9d54994ae Binary files /dev/null and b/docs/images/getMovieRecommendationsSequenceDiagram.png differ diff --git a/docs/images/getRecommendation.png b/docs/images/getRecommendation.png new file mode 100644 index 00000000000..b4ff188908b Binary files /dev/null and b/docs/images/getRecommendation.png differ diff --git a/docs/images/heze8.png b/docs/images/heze8.png new file mode 100644 index 00000000000..93fe05e6109 Binary files /dev/null and b/docs/images/heze8.png differ diff --git a/docs/images/jcjjjared.png b/docs/images/jcjjjared.png new file mode 100644 index 00000000000..b2ebd8cc525 Binary files /dev/null and b/docs/images/jcjjjared.png differ diff --git a/docs/images/keyboard.png b/docs/images/keyboard.png new file mode 100644 index 00000000000..9ef4301af31 Binary files /dev/null and b/docs/images/keyboard.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/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/michelleykw.png b/docs/images/michelleykw.png new file mode 100644 index 00000000000..820fb469256 Binary files /dev/null and b/docs/images/michelleykw.png differ diff --git a/docs/images/movieRecommendationSD.png b/docs/images/movieRecommendationSD.png new file mode 100644 index 00000000000..41a2fe8105b Binary files /dev/null and b/docs/images/movieRecommendationSD.png differ diff --git a/docs/images/moviecard.png b/docs/images/moviecard.png new file mode 100644 index 00000000000..a51b0ae5d24 Binary files /dev/null and b/docs/images/moviecard.png differ diff --git a/docs/images/recommendations.png b/docs/images/recommendations.png new file mode 100644 index 00000000000..313d57cfd47 Binary files /dev/null and b/docs/images/recommendations.png differ diff --git a/docs/images/searchAvengerUserInput.png b/docs/images/searchAvengerUserInput.png new file mode 100644 index 00000000000..838a2f38ec6 Binary files /dev/null and b/docs/images/searchAvengerUserInput.png differ diff --git a/docs/images/searchCommand.png b/docs/images/searchCommand.png new file mode 100644 index 00000000000..ce7bf638783 Binary files /dev/null and b/docs/images/searchCommand.png differ diff --git a/docs/images/searchIpMan.png b/docs/images/searchIpMan.png new file mode 100644 index 00000000000..5d41af2f82a Binary files /dev/null and b/docs/images/searchIpMan.png differ diff --git a/docs/images/searchPageLoadingScreen.png b/docs/images/searchPageLoadingScreen.png new file mode 100644 index 00000000000..1f5add4cf6d Binary files /dev/null and b/docs/images/searchPageLoadingScreen.png differ diff --git a/docs/images/searchPageSearchedForAvengersWhenOffline.png b/docs/images/searchPageSearchedForAvengersWhenOffline.png new file mode 100644 index 00000000000..050cb736cc0 Binary files /dev/null and b/docs/images/searchPageSearchedForAvengersWhenOffline.png differ diff --git a/docs/images/searchPageSearchedForAvengersWhenOnline.png b/docs/images/searchPageSearchedForAvengersWhenOnline.png new file mode 100644 index 00000000000..0fb19a7be57 Binary files /dev/null and b/docs/images/searchPageSearchedForAvengersWhenOnline.png differ diff --git a/docs/images/search_page.png b/docs/images/search_page.png new file mode 100644 index 00000000000..79cee9194d5 Binary files /dev/null and b/docs/images/search_page.png differ diff --git a/docs/images/statisticsButton.png b/docs/images/statisticsButton.png new file mode 100644 index 00000000000..1c68675ef7f Binary files /dev/null and b/docs/images/statisticsButton.png differ diff --git a/docs/images/statistics_page.png b/docs/images/statistics_page.png new file mode 100644 index 00000000000..fa893da3a29 Binary files /dev/null and b/docs/images/statistics_page.png differ diff --git a/docs/images/tswuxia.png b/docs/images/tswuxia.png new file mode 100644 index 00000000000..114b14a4ddd Binary files /dev/null and b/docs/images/tswuxia.png differ diff --git a/docs/images/watchedPageWithAvengerEndgame.png b/docs/images/watchedPageWithAvengerEndgame.png new file mode 100644 index 00000000000..b4cbfab7499 Binary files /dev/null and b/docs/images/watchedPageWithAvengerEndgame.png differ diff --git a/docs/images/watchlistIpMan.png b/docs/images/watchlistIpMan.png new file mode 100644 index 00000000000..82c3821fa8b Binary files /dev/null and b/docs/images/watchlistIpMan.png differ diff --git a/docs/images/watchlist_page.png b/docs/images/watchlist_page.png new file mode 100644 index 00000000000..21bfea8efc2 Binary files /dev/null and b/docs/images/watchlist_page.png differ diff --git a/docs/images/wongchuankai.png b/docs/images/wongchuankai.png new file mode 100644 index 00000000000..b831dc02cd9 Binary files /dev/null and b/docs/images/wongchuankai.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/heze8.adoc b/docs/team/heze8.adoc new file mode 100644 index 00000000000..19c35739b4b --- /dev/null +++ b/docs/team/heze8.adoc @@ -0,0 +1,179 @@ += Caleb Goh - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: `EzWatchlist` + +== 1. Introduction +The following Project Portfolio documents my contributions to the software project, `EzWatchlist`. +Which was a project my team of software engineering students and I decided to pursue for our Software Engineering project. + +Our team were initially tasked with enhancing a basic command line interface (CLI) desktop application that functioned as an Adressbook. +What this means is that the application focused on typing as the main mode of use, and had an existing rudimentary base of code already functioning. + +Ultimately, we made the decision to modify it into a management application for Movies and Tv Shows named `EzWatchlist`. + +=== 1.1 Overview + +image::Ui.png[width=600] +Figure 1. The graphical interface for `EzWatchlist`. + +`EzWatchlist` is an application that saves movies and television shows the user adds into a watchlist. +The user interacts with `EzWatchlist` through typing commands or the graphical interface seen in Figure 1. +This enhanced application boasts the following features: + +. Keeps track of the movies and television shows that they planed to watch. +. Keeps track of the movies and television shows that they have watched. +. Allows them to update the episodes they are at each television show. +. Gets information about their watching habits. +. Gets recommendations for movies and television shows based on their personal taste. + + +[NOTE] +The following symbols, abbreviations and formatting used in this document: + +|=== +| *API - Application Programming Interface* | An interface as a intermediary to use other programs or software. +| *`Highlighted words`* | Our application related components such as commands, classes, methods, e.t.c. +| image:keyboard.png[] | Keyboard characters on the desktop. +| https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests[#number] | Pull request links based on their serial number. +|=== + +The following sections illustrate these enhancements in more detail, as well as the relevant documentation +I have added to the user and developer guides in relation to these enhancements. + +== 2. Summary of contributions +This section shows a summary of my role in the project, my technical ability to code, document, and other beneficial contributions to the +team project. + +=== Role + +My main technical role was to design and write the codes for the integration with the application with an online database of movies and tv shows. +This was highly integral due to the nature and vision of the application where information about movies and tv shows +should be readily available to the user. + +Moreover, I took the supervising role of team lead in this project. +I made sure that the team was well coordinated and that we were all clear on the overall vision of the application at every point in the development. +Ensuring clear communication throughout the team, so that we all knew what to do. And that issues that arrived were handled smoothly and cleanly. + +=== *Major enhancement*: +Added the ability for the application to integrate with an *online database of movies and tv shows*. + +*What it does*: + +. Allows the user to see images in the application. +. Retrieve information about movies and tv shows. +. Search for movies and tv shows from a large up-to-date database. +. Access reviews, ratings, recommendations, collections of movies and tv shows, genre information, and much more. + +*Justification*: + +This feature improves the product significantly because a user should be encouraged to use the application by providing +appealing images, comprehensive information and up-to-date data. + +*Highlights*: + +This feature required a robust knowledge on how to set up third party libraries in a Java application. +Json conversion (Online data retrieval) is also another key element when considering the API used. + +Moreover, I had to integrate this external data with our own internal systems. Meaning my teammates had to use my implementations easily. Hence, data handling and conversion had to be designed in an understandable manner. + +*Credits*: + +We are using The Movie Database (TMDB) API to retrieve information on movies and tv shows. As well as +using a java wrapper for the TMDB api implemented by Holger Brandl. + + +=== *Minor enhancement*: + +Added ability to *store and retrieve images* in the application. + +** Justification: This feature improves the visual interface, and provides movies and tv shows a more potent identity compared to just words. +** Highlights: A lot of consideration had to +be placed in the retrieval and presentation of images. I had to integrate the image portrayal with the `JavaFX`-based interface, +consider how to store images and retrieve them when needed. + +=== *Minor enhancement*: + +Added ability to get *recommendations* based on the user's personal movies and tv shows. + +** Justification: This feature improves the product because a user can gain the added benefit of getting more suggestions for Movies and Tv Shows, which +makes the application more robust. Moreover, it improves the value of the application encouraging usage of the application. +** Highlights: This design for this enhancement had considerable thought into the efficiency of this feature due to the large amount of processing required from parsing the user's data. There is also the subjective manner of knowing what to recommend from a large list. +** Credits: The Movie Database (TMDB) API + + +*Code contributed*: + +All of my contributions to the application can be found in the following link: [https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=heze8&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[My Code Contribution]] + + +*Other contributions*: + +Project management: + +*** I am the Project lead, thus my job consisted of making sure the team coordinated, the overall vision of the application was reached, and handling all the issues that arose in the development. + +*** Managed releases `v1.1` - `v1.4` (3 releases) on GitHub + +*** Managed and assigned the issues, milestones and project board on Github. + +Enhancements to existing features: + +*** Updated the application's model to integrate seamlessly with the external library's data. This meant that each internal model class had to be error-friendly due to the unpredictable manner of the online database. +*** Added more internal classes such as `Poster` and `Genre` to be used in the application. (Commits https://github.com/AY1920S1-CS2103T-F13-4/main/commit/f9e7b69e36d8404e79107bc94d04670321d71aa3[Poster] and https://github.com/AY1920S1-CS2103T-F13-4/main/commit/e07c06d1fe2420db5163956c63d92cbf9cfbad5c[Genre]) +*** Wrote additional tests to increase coverage. +*** Helped to integrate the API code with the rest of the application. + +Documentation: + +*** Did cosmetic tweaks to our website. https://github.com/AY1920S1-CS2103T-F13-4/main/pull/224[Pull request #224] + +Community: + +*** Reviewed Pull Request : https://github.com/AY1920S1-CS2103T-F13-4/main/pull/107[#107], https://github.com/AY1920S1-CS2103T-F13-4/main/pull/30[#30], https://github.com/AY1920S1-CS2103T-F13-4/main/pull/73[#73], https://github.com/AY1920S1-CS2103T-F13-4/main/pull/51[#51], https://github.com/AY1920S1-CS2103T-F13-4/main/pull/78[#78] +*** Reported bugs and suggestions for other teams in the class: https://github.com/AY1920S1-CS2103T-T11-3/main/issues[T11-3 team], https://github.com/nus-cs2103-AY1920S1/addressbook-level3/pull/20[F12-4 team] + +Tools: + +*** Integrated a third party library (TMDB) to the project (https://www.themoviedb.org/documentation/api[TMDB Api]) +*** Integrated a new Github plugin (Java wrapper) to the team repo. +*** Added a successfully merged pull request to the Java Wrapper we are using in our application to fix their issue of not supporting recommendations. (https://github.com/holgerbrandl/themoviedbapi/pull/111[Pull request merged]) + + +== 3. Contributions to the User Guide +Our `EzWatchlist` user guide consists of instructions to the user on how to use our application. +It displays my ability to document in an engaging and readable format. + +I wrote the introduction to ease the user into the main features of the application, breaking it down to navigation and interaction with our program. + +|=== +|_The following is an excerpt from our `EzWatchlist` User Guide, showing additions that I have +made for the Introduction. The full introduction can be found https://ay1920s1-cs2103t-f13-4.github.io/main/UserGuide.html#introduction[here]:_ + +|=== +include::../UserGuide.adoc[tag=intro] + +___ + +|=== +|_Moreover I wrote the Advanced Features portion of the user guide, available https://ay1920s1-cs2103t-f13-4.github.io/main/UserGuide.html#advanced-features[here]._ +|=== + +== Contributions to the Developer Guide +This sections showcases my contribution to the `EzWatchlist` Developer Guide, which serve to document the implementation of our features to technical users. It demonstrates my capability to use diagrams +and illustrate technical details. + +|=== +|_The following are sections for the Api Component I wrote to introduce the design architecture in the application._ +|=== + +include::../DeveloperGuide.adoc[tag=apicomponent] + +|=== +|_Moreover, I also contributed the description of my implementation of the Api Model classes, Image Retrieval, and generation of Recommendations. The following is only an excerpt of my design considerations. The full portion can be found https://ay1920s1-cs2103t-f13-4.github.io/main/DeveloperGuide.html#online-data[here]_ +|=== + +include::../DeveloperGuide.adoc[tag=apiimpl] + diff --git a/docs/team/heze8.pdf b/docs/team/heze8.pdf new file mode 100644 index 00000000000..97ab3143c33 Binary files /dev/null and b/docs/team/heze8.pdf differ diff --git a/docs/team/jcjjjared.adoc b/docs/team/jcjjjared.adoc new file mode 100644 index 00000000000..332f5e4e019 --- /dev/null +++ b/docs/team/jcjjjared.adoc @@ -0,0 +1,126 @@ += Jared Chiang: Project Portfolio Page for EzWatchlist +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:icons: font + +This document serves to show the various contributions that I have made in the development of link:https://ay1920s1-cs2103t-f13-4.github.io/main/index.html[EzWatchlist] +in a concise and understandable manner. + +== More About The Project +EzWatchlist is a desktop application that allows forgetful users to keep track of movies and tv series that they want to watch or have watched. +It was developed by a group of 5 students from the National University of Singapore taking the Software Engineering module including +myself under the premise that we had to either optimize or morph an existing link:https://nus-cs2103-ay1920s1.github.io/addressbook-level3/index.html[AddressBook] application. We chose the latter. Another +constraint was ensuring that the application has a command-line interface, which means that all commands should be executable through typing. +The picture below shows the various components of EzWatchlist. + +.Graphical User Interface of EzWacthlist +image::Ui.png[width="790"] + +=== Main Features +* Adding, editing and deleting shows +* Keeping track of shows and episodes that are watched +* Online search and syncing of shows +* Statistics about user's watchlist + +=== Role +I was tasked with ensuring integration of the features of EzWatchlist. This means that I had to ensure that the commands +work with one another well and perform their expected functions in the respective pages. This was a challenge +since our application uses 4 different tabs, each with their unique functions and list of shows, so the implementation +of the commands would vary from tab to tab. + +=== Interpreting Symbols +The table below shows a summary of the symbols used and their respective meanings. + +[width="59%",cols="^22%,^50%",options="header",align="centre"] +|=================================== +|Symbol |Meaning + +|`Command` `Component`|Commands and Components of the Project + +|icon:lightbulb-o[role="icon-tip", size="2x"]|Tips for the user + +|icon:info-circle[role="icon-note", size="2x"] |Additional information +|=================================== + +== Summary of Contributions + +This section provides an overview of the contributions that I have made to the development of EzWatchlist. It also +showcases some of my coding and documentation abilities, and the challenges I faced during the development of EzWatchlist. + +=== Major Contributions + +==== Implemented the `watch` feature + +*What it does* + +The `watch` feature allows users to update their watch list if a show has been watched. +It also helps users to keep track of where they are in a TV series in terms +of seasons and episodes. + +*Justification* + +The `watch` feature enables users to keep track of shows that they have and have not watched so that they can plan +out what they want to watch in the future. + +*Highlights* + +This enhancement resulted in a need to change the storage structure of the shows to include the episodes and tv seasons. +Its implementation was a challenge since it required structural change of the show objects, splitting them into movies and tv shows. +The addition of the clickable checkbox to mark or unmark shows also increased the difficulty of the implementation. + +=== Minor Contributions + +* Separated the the watchlist into different sections (not watched and watched). +* Changed the storage structure of the shows to save them as either movies or tv shows. This feature was +implemented with the intention of possibly retrieving season and episode details future versions. + +=== Code Contributions +View my code contributions link:https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=jcjjjared&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[here]. + +=== Other Contributions + +[NOTE] +*#Numbers* represent pull requests to the mentioned contributions. +These links show the code that has been contributed to the team project + +*Project Management* + +* Provided the team with information about the tasks that need to be completed +* Assigned tasks link:https://github.com/AY1920S1-CS2103T-F13-4/main/issues/25[1], link:https://github.com/AY1920S1-CS2103T-F13-4/main/issues/42[2] + +*Enhancements to Features:* + +* Improved `add` command by reducing amount of information needed to be entered link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/138[#138] +* Changed storage structure to save shows as movies or tv shows link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/117[#117] +* Writing of various tests for EzWatchlist to reduce the possibility of bugs when using EzWatchlist link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/132[#231], link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/237[#237] + +*Documentation:* + +* Improved the structure of the user guide link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/15[#15], link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/214[#214] +* Updated the developer guide link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/241[#241] + +*Community:* + +* Reviewed team pull requests link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/77[#74], link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/81[#81], https://github.com/AY1920S1-CS2103T-F13-4/main/pull/84[#84] +* Reported bugs and suggestions for other teams in the class (examples: link:https://github.com/jcjjjared/ped/issues/1[1], link:https://github.com/jcjjjared/ped/issues/2[2], link:https://github.com/jcjjjared/ped/issues/4[3]) + +*Tools:* + +* Set up Travis Continuous Integration + +== Contributions to User Guide + +This section serves to showcase my contributions to the User Guide, as well as my ability to write +documentation for users in a concise and understandable manner. It also shows my ability to use asciidoc and +markup language for formatting. + +include::../UserGuide.adoc[tag=watch] + +== Contributions to Developer Guide + +This sections showcases my contribution to the EzWatchlist Developer Guide. It exhibits my ability to use diagrams +and technical terms to inform other developers of how the features were implemented and the possibility +for enhancements. + +include::../DeveloperGuide.adoc[tag=markaswatched] diff --git a/docs/team/jcjjjared.pdf b/docs/team/jcjjjared.pdf new file mode 100644 index 00000000000..df71ecae8a6 Binary files /dev/null and b/docs/team/jcjjjared.pdf differ diff --git a/docs/team/michelleykw.adoc b/docs/team/michelleykw.adoc new file mode 100644 index 00000000000..8ddbd16b34b --- /dev/null +++ b/docs/team/michelleykw.adoc @@ -0,0 +1,94 @@ += Michelle Yong Kai Wen: Project Portfolio for EzWatchList +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:icons: font + +:xrefstyle: full +:experimental: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +endif::[] + +== 1. Introduction + +The purpose of this portfolio is to document my roles and contributions to the project, in terms of the code, the User +Guide and the Developer Guide. + +In a team of 5 software engineering students, we were tasked to enhance an existing desktop application, +link:https://nus-cs2103-ay1920s1.github.io/addressbook-level3/index.html[AddressBook], for our software +engineering project. We chose to morph it into a watchlist for movie and television series called +link:https://ay1920s1-cs2103t-f13-4.github.io/main/index.html[EzWatchlist]. + +=== 1.1. About EzWatchlist + +EzWatchList is a Command Line Interface desktop application, which means that it is designed for those who types fast +and prefer to execute commands through typing. It helps users, especially those who are forgetful, to keep track of the +shows they had watched or wish to watch, reducing the hassle of having to search for shows frequently. + +.Graphical User Interface of EzWatchlist +image::Ui.png[width="500"] + +==== 1.2.1. Main Features +* Add, edit and delete shows +* Search online and offline +* Statistics + +=== 1.3. Interpreting Symbols +The table below shows a summary of the symbols used and their respective meanings +[width="80%",cols="^22%,^78%",options="header",align="centre"] +|=================================== +|Symbol |Meaning +|`Commands`|Commands input to the command line of EzWatchList +|icon:lightbulb-o[role="icon-tip", size="2x"]|Tips for the user +|icon:info-circle[role="icon-note", size="2x"] |Additional information +|kbd:[3] |Keyboard input to EzWatchList +|=================================== + +=== 1.4. Role + +My role was to implement the `search` feature and ensure proper documentation of the code. I had to implement the +`search` feature such that users would be able to search when online and offline. The +code had to be refactored and the `search` command had to be modified to allow users to search for different +categories such as searching by show name, actor or genre. + +== 2. Summary of Contributions +This section shows a summary of my coding, documentation, and other helpful contributions to the team project. + + +=== 2.1. Major Contributions +*Enhancement added: Implemented the `Search` command* + + +* *What it does*: The `search` feature allows users to search for shows, from the online database, offline database, their watchlist or watched-list. +Users can search for shows based on the show name, genres or actors and filter based on the type and where the show is found at. +* *Justification*:The `search` feature enables users to find new shows online easily or check if they had watched a certain show. +* *Highlights*: This enhancement requires a change on how `SearchCommand` process the search method and additional integration with the API. +* *Code contribution*: View my code contributions link:https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=michelleykw&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[here]. + +==== 2.2. Other contributions: +* *Project Management*: Assigned issues +link:https://github.com/AY1920S1-CS2103T-F13-4/main/issues/39[#39] +link:https://github.com/AY1920S1-CS2103T-F13-4/main/issues/59[#59] +link:https://github.com/AY1920S1-CS2103T-F13-4/main/issues/171[#171] +* *Enhancements to existing features*: Implemented an internal storage +link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/246[#246] +* *Documentation*: Updated the User Guide and Developer Guide +link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/118[#118] +* *Community*: Reviewed team pull requests +link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/30[#30] + +== 3. Contributions to the User Guide +This section showcase my contributions to the EzWatchlist User Guide and my ability to write documentations to teach +users on how use EzWatchlist. + +include::../UserGuide.adoc[tag=search] + +{sp} + +{sp} + +{sp} + + +== 4. Contributions to the Developer Guide +This section showcase my contributions to the EzWatchlist Developer Guide and my ability to use diagrams and technical +terms to inform other developers on the features and implementation of EzWatchlist. + +include::../DeveloperGuide.adoc[tag=search] diff --git a/docs/team/tswuxia.adoc b/docs/team/tswuxia.adoc new file mode 100644 index 00000000000..0c04468c271 --- /dev/null +++ b/docs/team/tswuxia.adoc @@ -0,0 +1,80 @@ += Wu Xia - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: `EzWatchlist` + +--- + +This document is to document my contributions to the desktop application `EzWatchlist`. + +== Overview + +EzWatchlist is a desktop application where the user can systematically record down what movie/TV series he/she wants to +watch or has watched. It is developed by a group of 5 NUS Computer Science students for the software engineering course +based on the Addressbook level 3 codebase provided. One specification is that the user prefers command line interface +(CLI), hence the CLI in the application. However, we also implemented a graphical user interface (GUI) created with +JavaFX. The project is written in Java. + +== Summary of contributions + +=== *Major enhancement*: + +Changed the *GUI* to better suit EzWatchlist + +* *What it does:* +** Structures the features into 4 tabs and links them together with the main logic which allows the user to easily +navigate around the tabs and execute commands. +** Ensures that each visible component is displayed with an appropriate size, appearance, space and position. +** Enhances user experience through user-friendly layout and placeholder text and loading signs to keep the user informed. +** Enhances user experience through a creative theme. + +* *Justification:* +** This feature improves the product significantly because a user would like to use an application that is user friendly +and has an appealing appearance. + +* *Highlights:* +** This enhancement requires deep understanding of both the implementation of the application itself and also how to use +javafx flexibly in order to effectively communicate between the user and the application and present the message in a +desirable manner. It also requires repetitive adjustments and testing of the appearance of the elements to produce a +final result. + + +=== *Minor enhancement*: + +Added a `Statistics` feature that reflects the user's personal preferences. + +=== *Code contributed*: +Code contributed can be found here: [https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=tswuxia&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=tswuxia&tabRepo=AY1920S1-CS2103T-F13-4%2Fmain%5Bmaster%5D[My Code Contribution]] + +=== *Other contributions*: + +** Enhancements to existing features: +*** Updated the GUI color scheme (Pull request https://github.com/AY1920S1-CS2103T-F13-4/main/pull/123[#123]) +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/tswuxia/ped/issues/7[1], +https://github.com/tswuxia/ped/issues/6[2], https://github.com/tswuxia/ped/issues/5[3], https://github.com/tswuxia/ped/issues/4[4], +https://github.com/tswuxia/ped/issues/3[5], https://github.com/tswuxia/ped/issues/2[6], https://github.com/tswuxia/ped/issues/1[7]) + +== Contributions to the User Guide +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write user documentation targeting normal +application users in a clear and understandable manner._ +|=== + +include::../UserGuide.adoc[tag=UI] + +== 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=statistics] + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/tswuxia.pdf b/docs/team/tswuxia.pdf new file mode 100644 index 00000000000..8eeae5223c7 Binary files /dev/null and b/docs/team/tswuxia.pdf differ diff --git a/docs/team/wongchuankai.adoc b/docs/team/wongchuankai.adoc new file mode 100644 index 00000000000..e27e2b8634d --- /dev/null +++ b/docs/team/wongchuankai.adoc @@ -0,0 +1,200 @@ += Wong Chuan Kai - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:icons: font + +== PROJECT: `EzWatchList` + + +== 1. Introduction + +The purpose of the Project Portfolio is to document and showcase my contributions to the software project, link:https://ay1920s1-cs2103t-f13-4.github.io/main/index.html[EzWatchList] + +Our team was initially tasked with enhancing a basic command line interface(CLI) desktop application +for our Software Engineering project. We are also allowed to morph it to other application that uses command line interface. +Thus, we chose to morph it into a movie records management system called EzWatchList. This enhanced +version provides a unique, clean and simple way of organizing and keeping track of movie or TV show watch list. + +=== 1.1 Overview + +image::CK_PPP.png[width="500"] +_Figure 1. The graphical interface for Ezwatchlist._ + +`EzWatchList` helps users to keep track and organise movie or TV show in a watch list. User can simply +interact with the application by inputting commands into the interface. + +Main features of EzWatchList: + +- Keep track of shows that users plan to watch +- Allows users to edit and mark down shows that they have watched +- Allows users to search for show online using `search` online function or search within user's watch list. +- Gives users statistics about their watching habits and recommend shows to them. + +The following sections illustrate these enhancements in more detail, as well as the relevant documentation +I have added to the user and developer guides in relation to these enhancements. + +=== Interpreting Symbols +The table below shows a summary of the symbols used and their respective meanings. + +[width="59%",cols="^22%,^50%",options="header",align="centre"] +|=================================== +|Symbol |Meaning + +|`Command` `Component`|Commands and Components of the Project + +|icon:lightbulb-o[role="icon-tip", size="2x"]|Tips for the user + +|icon:info-circle[role="icon-note", size="2x"] |Additional information +|=================================== + +== 2. Summary of contributions +|=== +|_This section shows a summary of my challenges, coding, documentation, and other helpful contributions to the team project._ +|=== + +My role was to design and implement the `Add` and `Sync` features. These commands are essential to the application as they are the +building block of the application. Before I can design these features, I have to understand +the application requirement first such as functional and non-function requirements. I also have to know who my target audience +is and study the use-cases properly to plan out and design these features. + +=== 2.1 Major enhancement: + +==== *Added `add` and synchronise, `sync`, commands.* + +*1. Modifying and extension of `add` command* + +*What it does*: + +There are two separate functions for `Add` command. +First, it allows the user to add movies or TV shows into the watch list. +User has to input several information of the show such as name, description, and +name of actors. + +Beside this core function, the add command also allows user to add a movie found in search +result page after user has used the `search` online feature. Search page displays the search result of the movies the user +is interested to watch. The `search` feature is implemented by my team mate, Michelle. + +*Justification*: + +The purpose of this application is to allow user to track movie list. Thus, adding shows into watch list is a +fundamental and core function that runs the application. If the user wants to know more about a certain movie, he can +simply use the `search` function to search for it. The `search` function is able to gather information about movies online +and user can choose to ‘add’ a certain movie from the search list into his own watch list. + +*Highlights*: + +The addition of `add` command requires some understanding of both the implementation of the application and how to retrieve +the data from 3rd party API(TMDB). + +*Credits*: + +Since we are retrieving information on shows online, we have decided to use The Movie Database (TMDB) api. + + +*2. Synchronise, Sync, command* + +*What it does*: + +User may not have updated information about the show they want to add. They can use the `search` function to check the +details of the show they are interested to watch. Then, they are able to use the `sync` to synchronise a show found in +search page to their show which has already been added in watch list. + +*Justification*: + +User will definitely not know most of the information about a show. Hence, by implementing this `sync` function, users +will then be able to get updated information about the show. + +*Highlights*: + +This enhancement requires some understanding of both the implementation of the application and how to retrieve +the data from 3rd party API(TMDB). + + +*Credits*: + +Since we are retrieving information on shows online, we decided to use The Movie Database (TMDB) api. + +=== *2.2 Minor enhancement:* + +==== Added shortcut keys to move about pages easily. + +*What it does*: + +Users are able to press a single shortcut key button to move about the panels such as ‘Watchlist’, ‘Watched’, ‘Search’ +and ‘Statistics’ without moving and clicking mouse. + +*Justification*: + +Our target user is one who prefers to type fast and complete tasks quickly. One of the non-functional requirement is to +reduce the usage of the mouse as this is mainly a command line application. Adding shortcut keys will reduce the +usage of the mouse. + +*Highlights*: + +This addition make it a lot easier for user to navigate through the different pages. + +//==== Added multi threading when `search` function is used. + +*Code contributed*: + +My contributions to EzWatchList can be found in the following link:[https://nus-cs2103-ay1920s1.github.io/tp-dashboard/#search=wongchuankai&sort=groupTitle&sortWithin=title&since=2019-09-06&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[My Code Contribution]] +[https://github.com[Test code]] + +=== *2.3 Other contributions:* + +*Project management*: + +*** I am in charged of testing, so my responsibility is to plan, understand and write test codes for this project. + +*** My job is to understand the component logic as well so I am analyse the problems faced better. + +*Enhancements to existing features* : + +*** I have added multi-threading when the user uses `search` command. Since, it might take some time to load the data, +multi-threading allows the application to process these data in the background. Users will then be able to do other task +in the application like using `add` command as well. + +*** I have added shortcut keys so users are able to navigate the pages easily. + +*** Since I am in charged of the unit testing, I have added test cases to improve test coverage. + +*Documentation*: + +* Improved the structure of the user guide link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/258[#258], link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/279[#279] +* Updated the developer guide link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/11[#11], link:https://github.com/AY1920S1-CS2103T-F13-4/main/pull/258[#258] + + +*Community*: + +* Reported bugs and suggestions for other teams in the class (examples: link:https://github.com/wongchuankai/ped/blob/master/files/fceb38f2-23e3-4f5e-a7a7-42afce9258a8.png[1], link:https://github.com/wongchuankai/ped/blob/master/files/f0a21388-54e5-49b2-9cfd-efb47c514667.png[2], link:https://github.com/wongchuankai/ped/blob/master/files/bf312b96-02eb-47e5-a170-7946d1f7f100.png[3]) + +*Tools*: + +*** Integrated a third party library (TMDB) to the project (https://www.themoviedb.org/documentation/api[TMDB Api]) + + +== 3. Contributions to the User Guide +|=== +|_This section is an excerpt from our EzWatchList User Guide, showing additions that I have made for the sync +features. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=sync] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide, showing additions that I have made for the add and sync +features. They showcase my ability to write technical documentation +and the technical depth of my software engineering skills to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=add2] +include::../DeveloperGuide.adoc[tag=sync] + + + +--- + diff --git a/docs/team/wongchuankai.pdf b/docs/team/wongchuankai.pdf new file mode 100644 index 00000000000..981edcce3fd Binary files /dev/null and b/docs/team/wongchuankai.pdf differ diff --git a/docs/templates/_header.html.slim b/docs/templates/_header.html.slim index 3c2d5aed43c..8d840758c99 100644 --- a/docs/templates/_header.html.slim +++ b/docs/templates/_header.html.slim @@ -1,26 +1,4 @@ / NOTE: You must restart the gradle daemon after modifying any template file for the changes to take effect. -- if !(attr? 'no-site-header') && (attr? 'site-seedu') - #seedu-header - nav.navbar.navbar-lg.navbar-light.bg-lighter - .container - a.navbar-brand href='https://se-edu.github.io/' - img src=(site_url 'images/SeEduLogo.png') alt='SE-EDU' - ul.navbar-nav - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level1' AB-1 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level2' AB-2 - li.nav-item - a.nav-link.active href=(site_url 'index.html') AB-3 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level4' AB-4 - li.nav-item - a.nav-link href='https://se-edu.github.io/collate' Collate - li.nav-item - a.nav-link href='https://se-edu.github.io/se-book' Book - li.nav-item - a.nav-link href='https://se-edu.github.io/learningresources' Resources - - if !(attr? 'no-site-header') #site-header nav.navbar.navbar-light.bg-light 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/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/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/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/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} 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/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/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 0684b088868..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,74 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.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.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/ezwatchlist/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/ezwatchlist/AppParameters.java index ab552c398f3..6d363fe116a 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/ezwatchlist/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.ezwatchlist; 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.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.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/ezwatchlist/Main.java similarity index 96% rename from src/main/java/seedu/address/Main.java rename to src/main/java/seedu/ezwatchlist/Main.java index 052a5068631..5aad393a1d2 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/ezwatchlist/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.ezwatchlist; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/ezwatchlist/MainApp.java similarity index 56% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/ezwatchlist/MainApp.java index e5cfb161b73..c2b49b30655 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/ezwatchlist/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.ezwatchlist; import java.io.IOException; import java.nio.file.Path; @@ -7,36 +7,40 @@ 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.ezwatchlist.commons.core.Config; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.core.Version; +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.commons.util.ConfigUtil; +import seedu.ezwatchlist.commons.util.StringUtil; +import seedu.ezwatchlist.logic.Logic; +import seedu.ezwatchlist.logic.LogicManager; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.ModelManager; +import seedu.ezwatchlist.model.ReadOnlyUserPrefs; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.UserPrefs; +import seedu.ezwatchlist.model.WatchList; +import seedu.ezwatchlist.model.util.DataBaseUtil; +import seedu.ezwatchlist.model.util.SampleDataUtil; +import seedu.ezwatchlist.statistics.Statistics; +import seedu.ezwatchlist.storage.DatabaseStorage; +import seedu.ezwatchlist.storage.JsonDatabaseStorage; +import seedu.ezwatchlist.storage.JsonUserPrefsStorage; +import seedu.ezwatchlist.storage.JsonWatchListStorage; +import seedu.ezwatchlist.storage.Storage; +import seedu.ezwatchlist.storage.StorageManager; +import seedu.ezwatchlist.storage.UserPrefsStorage; +import seedu.ezwatchlist.storage.WatchListStorage; +import seedu.ezwatchlist.ui.Ui; +import seedu.ezwatchlist.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, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -45,10 +49,11 @@ public class MainApp extends Application { protected Storage storage; protected Model model; protected Config config; + protected Statistics statistics; @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing WatchList ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,41 +61,60 @@ 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); + WatchListStorage watchListStorage = new JsonWatchListStorage(userPrefs.getWatchListFilePath()); + DatabaseStorage databaseStorage = new JsonDatabaseStorage(userPrefs.getDatabaseFilePath()); + storage = new StorageManager(watchListStorage, databaseStorage, userPrefsStorage); initLogging(config); model = initModelManager(storage, userPrefs); + statistics = new Statistics(model); + logic = new LogicManager(model, storage); - ui = new UiManager(logic); + ui = new UiManager(logic, statistics); } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s watchlist and {@code userPrefs}.
+ * The data from the sample watchlist will be used instead if {@code storage}'s watchlist is not found, + * or an empty watchlist will be used instead if errors occur when reading {@code storage}'s watchlist. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional watchListOptional; + Optional databaseOptional; + ReadOnlyWatchList initialData; + ReadOnlyWatchList database; + try { + watchListOptional = storage.readWatchList(); + if (!watchListOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample WatchList"); + } + initialData = watchListOptional.orElseGet(SampleDataUtil::getSampleWatchList); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty WatchList"); + initialData = new WatchList(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty WatchList"); + initialData = new WatchList(); + } + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + databaseOptional = storage.readDatabase(); + if (!databaseOptional.isPresent()) { + logger.info("Database not found. Will be starting with a new database"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + database = databaseOptional.orElseGet(DataBaseUtil::getShowDatabaseList); } 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 database"); + database = new WatchList(); } 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 database"); + database = new WatchList(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialData, database, userPrefs); } private void initLogging(Config config) { @@ -151,7 +175,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty WatchList"); initializedPrefs = new UserPrefs(); } @@ -167,13 +191,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting WatchList " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping WatchList ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/ezwatchlist/api/exceptions/NoRecommendationsException.java b/src/main/java/seedu/ezwatchlist/api/exceptions/NoRecommendationsException.java new file mode 100644 index 00000000000..8daef7ce8a7 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/exceptions/NoRecommendationsException.java @@ -0,0 +1,17 @@ +package seedu.ezwatchlist.api.exceptions; + +/** + * Represents an error when RecommendationEngine fails to return a recommendation. + */ +public class NoRecommendationsException extends Exception { + private String message; + + public NoRecommendationsException(String message) { + super(message); + this.message = message; + } + + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/seedu/ezwatchlist/api/exceptions/OnlineConnectionException.java b/src/main/java/seedu/ezwatchlist/api/exceptions/OnlineConnectionException.java new file mode 100644 index 00000000000..a4f459c4c6b --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/exceptions/OnlineConnectionException.java @@ -0,0 +1,18 @@ +package seedu.ezwatchlist.api.exceptions; + +/** + * Represents an error when application fails to connect online to the API + */ +public class OnlineConnectionException extends Exception { + private String message; + + public OnlineConnectionException(String message) { + super(message); + this.message = message; + } + + public String getMessage() { + return this.message; + } +} + diff --git a/src/main/java/seedu/ezwatchlist/api/model/ApiInterface.java b/src/main/java/seedu/ezwatchlist/api/model/ApiInterface.java new file mode 100644 index 00000000000..3454e81d6c0 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/model/ApiInterface.java @@ -0,0 +1,88 @@ +package seedu.ezwatchlist.api.model; + +import java.util.List; +import java.util.Set; + +import seedu.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Interface to retrieve information from online API. + * Methods used here will return the information for Movies and Tv Shows. + */ +public interface ApiInterface { + + /** + * Returns a list of Movies from the API search method. + * + * @param name the name of the Movie that the user wants to search. + * @exception OnlineConnectionException when the user is not connected to the internet. + */ + List getMovieByName(String name) throws OnlineConnectionException; + + /** + * Returns a list of Tv Shows from the API search method. + * + * @param name the name of the Tv Show that the user wants to search. + * @exception OnlineConnectionException when the user is not connected to the internet. + */ + List getTvShowByName(String name) throws OnlineConnectionException; + + /** + * Retrieves a list of upcoming Movies in the API. + * + * @return a list of Movies that are upcoming from the API. + * @throws OnlineConnectionException when not connected to the internet. + */ + List getUpcomingMovies() throws OnlineConnectionException; + + /** + * Retrieves a list of recommended Movies based on the list of Movies the user has. + * @param userMovies list of Movies that belongs to the user. + * @param noOfRecommendations Number of recommendations returned. The method will attempt to reach that number, + * it will act as an upper limit to the amount of Movies returned. + * @return list of Movies that are recommended to the user. + * @throws OnlineConnectionException when not connected to the internet. + * @throws NoRecommendationsException when no recommendations can be generated. + */ + List getMovieRecommendations(List userMovies, int noOfRecommendations) + throws OnlineConnectionException, NoRecommendationsException; + + /** + * Retrieves a list of recommended Tv Shows based on the list of Tv Shows the user has. + * @param userTvShows list of Tv Shows that belongs to the user. + * @param noOfRecommendations Number of recommendations returned. The method will attempt to reach that number, + * it will act as an upper limit to the amount of Tv Shows returned. + * @return list of Tv Shows that are recommended to the user. + * @throws OnlineConnectionException when not connected to the internet. + * @throws NoRecommendationsException when no recommendations can be generated. + */ + List getTvShowRecommendations(List userTvShows, int noOfRecommendations) + throws OnlineConnectionException, NoRecommendationsException; + + /** + * Returns a list of Tv Shows from the API search method. + * + * @param genreSet the set of genres that the user wants to search. + * @throws OnlineConnectionException when not connected to the internet. + */ + //List getTvShowByGenre(Set genreSet) throws OnlineConnectionException; + + /** + * Returns a list of movies from the API search method. + * + * @param genreSet the set of genres that the user wants to search. + * @throws OnlineConnectionException when not connected to the internet. + */ + List getMovieByGenre(Set genreSet) throws OnlineConnectionException; + + /** + * Checks if the api is connected to the internet. + */ + static boolean isConnected() { + return false; + } +} diff --git a/src/main/java/seedu/ezwatchlist/api/model/ApiManager.java b/src/main/java/seedu/ezwatchlist/api/model/ApiManager.java new file mode 100644 index 00000000000..e27123709a2 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/model/ApiManager.java @@ -0,0 +1,220 @@ +package seedu.ezwatchlist.api.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import info.movito.themoviedbapi.TmdbApi; +import info.movito.themoviedbapi.TvResultsPage; +import info.movito.themoviedbapi.model.core.MovieResultsPage; +import info.movito.themoviedbapi.tools.MovieDbException; +import seedu.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.api.util.ApiUtil; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Main class for the API to connect to the server + */ +public class ApiManager extends ApiUtil implements ApiInterface { + //API key is to connect with the TMDB server. + private static final String API_KEY = "44ed1d7975d7c699743229199b1fc26e"; + private static final String CONNECTION_ERROR_MESSAGE = "Looks like you're not connected to the internet"; + private TmdbApi apiCall; + + /** + * Constructor for ApiMain object used to interact with the API. + * + * @throws OnlineConnectionException when not connected to the internet. + */ + public ApiManager() throws OnlineConnectionException { + try { + apiCall = new TmdbApi(API_KEY); + } catch (MovieDbException e) { + //when not connected to the internet + notConnected(); + } + } + + /** + * Checks if the API is connected to the internet. + * + * @return true if connected to the API. + */ + public static boolean isConnected() { + try { + new TmdbApi(API_KEY); + } catch (MovieDbException e) { + return false; + } + return true; + } + + /** + * Helper function to call when not connected to the API. + * + * @throws OnlineConnectionException when the method is called with an error message. + */ + public static void notConnected() throws OnlineConnectionException { + throw new OnlineConnectionException(CONNECTION_ERROR_MESSAGE); + } + + /** + * Retrieves a list of upcoming movies in the API. + * + * @return a list of movies that are upcoming from the API. + * @throws OnlineConnectionException when not connected to the internet. + */ + public List getUpcomingMovies() throws OnlineConnectionException { + ArrayList movies = new ArrayList<>(); + try { + MovieResultsPage upcoming = apiCall.getMovies().getUpcoming(null, null, null); + ApiUtil.extractMovies(movies, upcoming, apiCall); + + return movies; + } catch (MovieDbException e) { + notConnected(); + return movies; + } + } + + /** + * Retrieves the movies from the API by the string given. + * + * @param name the name of the movie that the user wants to search. + * @return a list of movies that are returned from the API search call. + * @throws OnlineConnectionException when not connected to the internet. + */ + public List getMovieByName(String name) throws OnlineConnectionException { + ArrayList movies = new ArrayList<>(); + try { + MovieResultsPage page = apiCall.getSearch().searchMovie(name, + null, null, false, 1); + + + ApiUtil.extractMovies(movies, page, apiCall); + + return movies; + } catch (MovieDbException e) { + notConnected(); + return movies; + } + } + + /** + * Retrieves a list of recommended Movies based on the list of Movies the user has. + * @param userMovies list of Movies that belongs to the user. + * @param noOfRecommendations Number of recommendations returned. The method will attempt to reach that number, + * it will act as an upper limit to the amount of Movies returned. + * @return list of Movies that are recommended to the user. + * @throws OnlineConnectionException when not connected to the internet. + * @throws NoRecommendationsException when no recommendations can be generated. + */ + public List getMovieRecommendations(List userMovies, int noOfRecommendations) + throws OnlineConnectionException, NoRecommendationsException { + try { + RecommendationEngine recommendation = new RecommendationEngine(userMovies, null, apiCall); + return recommendation.getMovieRecommendations(noOfRecommendations); + } catch (MovieDbException e) { + notConnected(); + return null; + } + } + + /** + * Retrieves a list of recommended Tv Shows based on the list of Tv Shows the user has. + * @param userTvShows list of Tv Shows that belongs to the user. + * @param noOfRecommendations Number of recommendations returned. The method will attempt to reach that number, + * it will act as an upper limit to the amount of Tv Shows returned. + * @return list of Tv Shows that are recommended to the user. + * @throws OnlineConnectionException when not connected to the internet. + * @throws NoRecommendationsException when no recommendations can be generated. + */ + public List getTvShowRecommendations(List userTvShows, int noOfRecommendations) + throws OnlineConnectionException, NoRecommendationsException { + try { + RecommendationEngine recommendation = new RecommendationEngine(null, userTvShows, apiCall); + return recommendation.getTvShowRecommendations(noOfRecommendations); + } catch (MovieDbException e) { + notConnected(); + return null; + } + } + + /** + * Retrieves the tv shows from the API by the string given. + * + * @param name the name of the tv show that the user wants to search. + * @return a list of tv shows that are returned from the API search call. + * @throws OnlineConnectionException when not connected to the internet. + */ + public List getTvShowByName(String name) throws OnlineConnectionException { + ArrayList tvShows = new ArrayList<>(); + + try { + TvResultsPage page = apiCall.getSearch().searchTv(name, null, 1); + + ApiUtil.extractTvShows(tvShows, page, apiCall); + return tvShows; + } catch (MovieDbException e) { + notConnected(); + return tvShows; + } + } + + /*public List getTvShowByGenre(Set genreSet) throws OnlineConnectionException { + ArrayList tvShows = new ArrayList<>(); + try{ + List genreList = apiCall.getGenre().getGenreList(null); + for (Genre genreSearched : genreSet) { + for (info.movito.themoviedbapi.model.Genre genreApi : genreList) { + if (genreApi.getName().toLowerCase().contains(genreSearched.getGenreName().toLowerCase())) { + int genreID = genreApi.getId(); + apiCall.getGenre().getGenreMovies() + Discover discover = new Discover(); + discover.includeAdult(false).withGenres(genreID); + MovieResultsPage tvPage = apiCall.getDiscover().getDiscover(discover); + ApiUtil.extractTvShows(tvShows, tvPage, apiCall); + } + } + } + return tvShows; + } catch (MovieDbException e) { + notConnected(); + return tvShows; + } + } + + */ + + /** + * Returns a list of movies from the API search method. + * + * @param genreSet the set of genres that the user wants to search. + * @throws OnlineConnectionException when not connected to the internet. + */ + public List getMovieByGenre(Set genreSet) throws OnlineConnectionException { + ArrayList movies = new ArrayList<>(); + try { + + List genreList = apiCall.getGenre().getGenreList(null); + for (Genre genreSearched : genreSet) { + for (info.movito.themoviedbapi.model.Genre genreApi : genreList) { + if (genreApi.getName().toLowerCase().contains(genreSearched.getGenreName().toLowerCase())) { + int genreId = genreApi.getId(); + MovieResultsPage moviePage = apiCall.getGenre().getGenreMovies(genreId, null, 1, + true); + ApiUtil.extractMovies(movies, moviePage, apiCall); + } + } + } + + return movies; + } catch (MovieDbException e) { + notConnected(); + return movies; + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/api/model/ImageRetrieval.java b/src/main/java/seedu/ezwatchlist/api/model/ImageRetrieval.java new file mode 100644 index 00000000000..b9fe88ad162 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/model/ImageRetrieval.java @@ -0,0 +1,128 @@ +package seedu.ezwatchlist.api.model; + +import static java.util.Objects.isNull; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Logger; + +import info.movito.themoviedbapi.TmdbApi; +import info.movito.themoviedbapi.model.config.TmdbConfiguration; +import info.movito.themoviedbapi.tools.MovieDbException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.LogsCenter; + +/** + * A class to retrieve images from the internet to store in the user's cache. + */ +public class ImageRetrieval { + private static final Logger logger = LogsCenter.getLogger(ImageRetrieval.class); + private static final String DEFAULT_FILE_SIZE = "w300"; + private static final String ROOT = defaultDirectory(); + public static final String IMAGE_CACHE_LOCATION = ROOT + File.separator + + "Ezwatchlist" + File.separator + "posters"; + private String imageUrl; + private String formattedFileName; + + /** + * Creates an instance of a image retrieval used to download images online + * + * @param tmdbApi the tmdbApi object used to get the url + * @param filePath the url online to the image + * @param fileName the name of the show + * @throws OnlineConnectionException when not connected to the internet + */ + public ImageRetrieval(TmdbApi tmdbApi, String filePath, String fileName) + throws OnlineConnectionException, IllegalArgumentException { + try { + if (!isNull(filePath)) { + TmdbConfiguration configuration = tmdbApi.getConfiguration(); + imageUrl = configuration.getBaseUrl() + DEFAULT_FILE_SIZE + filePath; + String filterString = fileName.replaceAll("[^A-Za-z0-9\\[\\]]", "_"); + formattedFileName = filterString + filePath.hashCode(); + } else { + throw new IllegalArgumentException("Missing filepath"); + } + } catch (MovieDbException e) { + throw new OnlineConnectionException("Internet Connection failed at Image Retrieval"); + } + } + + /** + * Retrieves the default directory of the platform + * + * @return the string path to the root folder + */ + //adapted from + //https://stackoverflow.com/questions/6561172/find-directory-for-application-data-on-linux-and-macintosh + static String defaultDirectory() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return System.getenv("APPDATA"); + } else if (os.contains("mac")) { + return System.getProperty("user.home") + "/Library/Application Support"; + } else if (os.contains("nux")) { + return System.getProperty("user.home"); + } else { + return System.getProperty("user.dir"); + } + } + + /** + * Retrieves the image online by downloading it into the save folder + * + * @return the string path of the file + * @throws OnlineConnectionException when not connected to the internet + */ + public String retrieveImage() throws OnlineConnectionException { + try { + downloadImage(); + } finally { + return formattedFileName + ".png"; + } + } + + /** + * Returns the online url from this object instance. + * + * @return the online url + */ + public String getImageUrl() { + return imageUrl; + } + + /** + * Downloads the image from the online url. Is a helper method. + * @throws OnlineConnectionException when not connected online. + */ + private void downloadImage() throws OnlineConnectionException { + try (InputStream in = new URL(imageUrl).openStream()) { + File parent = new File(IMAGE_CACHE_LOCATION); + + if (!parent.exists()) { + parent.mkdirs(); + } + + Path filepath = Paths.get(IMAGE_CACHE_LOCATION + File.separator + + formattedFileName + ".png"); + + if (filepath.toFile().exists()) { + throw new FileAlreadyExistsException("Duplicate image"); + } + + Files.copy(in, filepath); + + } catch (FileAlreadyExistsException f) { + logger.info(f.getMessage()); + } catch (IOException e) { + logger.info(e.getMessage()); + throw new OnlineConnectionException("No internet connection at downloading image"); + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/api/model/RecommendationEngine.java b/src/main/java/seedu/ezwatchlist/api/model/RecommendationEngine.java new file mode 100644 index 00000000000..595d5062775 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/model/RecommendationEngine.java @@ -0,0 +1,258 @@ +package seedu.ezwatchlist.api.model; + +import static java.util.Map.Entry.comparingByValue; +import static java.util.Objects.isNull; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import javax.annotation.Nullable; + +import info.movito.themoviedbapi.TmdbApi; +import info.movito.themoviedbapi.TmdbMovies; +import info.movito.themoviedbapi.TmdbTV; +import info.movito.themoviedbapi.TvResultsPage; +import info.movito.themoviedbapi.model.MovieDb; +import info.movito.themoviedbapi.model.core.MovieResultsPage; +import info.movito.themoviedbapi.model.core.ResultsPage; +import info.movito.themoviedbapi.model.tv.TvSeries; +import info.movito.themoviedbapi.tools.MovieDbException; + +import seedu.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.api.util.ApiUtil; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Class used to generate recommendations based on list that the user has. The recommendations are sorted based on the + * likelihood that the recommendations are more suited to the user, with the best recommendation being the first entry + * in the list. + */ +public class RecommendationEngine extends ApiUtil { + private final List userMovies; + private final List userTvShows; + private List userMoviesId; + private List userTvShowsId; + private final TmdbApi tmdbApi; + private HashMap movieRecommendationOccurrences; + private HashMap tvRecommendationOccurrences; + private List movieRecommendations; + private List tvShowRecommendations; + + /** + * Creates an instance of RecommendationEngine used to generate recommendations for the user. + * @param movies @nullable the list of Movies the user has. + * @param tvShows @nullable the list of Tv Shows the user has. + * @param tmdbApi the Api call to retrieve online information. + */ + public RecommendationEngine(@Nullable List movies, @Nullable List tvShows, TmdbApi tmdbApi) + throws IllegalArgumentException { + if (isNull(tmdbApi)) { + throw new IllegalArgumentException(); + } + + userMovies = movies; + userTvShows = tvShows; + userMoviesId = new LinkedList<>(); + userTvShowsId = new LinkedList<>(); + this.tmdbApi = tmdbApi; + movieRecommendationOccurrences = new HashMap<>(); + tvRecommendationOccurrences = new HashMap<>(); + movieRecommendations = new LinkedList<>(); + tvShowRecommendations = new LinkedList<>(); + } + + /** + * Generates Movie recommendations, returning a sorted list of recommendations. + * @param noOfRecommendations the upper limit of recommendations returned. + * @return Sorted list of recommendations, with the best recommendation in the first entry. + * @throws NoRecommendationsException thrown when unable to generate recommendations. + * @throws OnlineConnectionException thrown when not connected to the internet. + */ + public List getMovieRecommendations(int noOfRecommendations) + throws NoRecommendationsException, OnlineConnectionException { + if (movieRecommendations.size() != noOfRecommendations) { + validForRecommendations(isNull(userMovies), "Movies from the user is null, unable to"); + validForRecommendations(userMovies.isEmpty(), "No movies from the user to"); + + parseUserMovies(); + filterRecommendations(true); + sortMovieRecommendations(noOfRecommendations); + + validForRecommendations(movieRecommendationOccurrences.isEmpty(), "Unable to"); + } + + return movieRecommendations; + } + + /** + * Filters the recommendations by removing all of the user's movies and tv shows if present in the list. + * This is to prevent recommending something the user already has. + * @param isMovie to filter the movie list or tv show list. + */ + private void filterRecommendations(boolean isMovie) { + List idList = isMovie ? userMoviesId : userTvShowsId; + HashMap recommendationOccurrences = isMovie ? movieRecommendationOccurrences + : tvRecommendationOccurrences; + + for (Integer id : idList) { + recommendationOccurrences.remove(id); + } + } + + /** + * Helper method to check if recommendations are valid. + * @param check the boolean check. + * @param prefix the prefix for the error message. + * @throws NoRecommendationsException thrown if invalid. + */ + private void validForRecommendations(boolean check, String prefix) throws NoRecommendationsException { + if (check) { + throw new NoRecommendationsException(prefix + + " generate recommendations"); + } + } + + /** + * Generates TV Shows recommendations, returning a sorted list of recommendations. + * @param noOfRecommendations the upper limit of recommendations returned. + * @return Sorted list of recommendations, with the best recommendation in the first entry. + * @throws NoRecommendationsException thrown when unable to generate recommendations. + * @throws OnlineConnectionException thrown when not connected to the internet. + */ + public List getTvShowRecommendations(int noOfRecommendations) + throws NoRecommendationsException, OnlineConnectionException { + if (tvShowRecommendations.size() != noOfRecommendations) { + validForRecommendations(isNull(userTvShows), "TvShows from the user is null, unable to"); + validForRecommendations(userTvShows.isEmpty(), "No TvShows from the user to"); + + parseUserTvShows(); + filterRecommendations(false); + sortTvShowRecommendations(noOfRecommendations); + + validForRecommendations(tvRecommendationOccurrences.isEmpty(), "Unable to"); + } + + return tvShowRecommendations; + } + + /** + * Sorts and adds the recommendations to the list of recommendations. + * @param noOfRecommendations the upper limit of recommendations. + */ + private void sortMovieRecommendations(int noOfRecommendations) { + movieRecommendationOccurrences.entrySet().stream() + .sorted(comparingByValue()) + .limit(noOfRecommendations) + .forEachOrdered(entry -> movieRecommendations.add(ApiUtil.getMovie(tmdbApi, entry.getKey()))); + } + + /** + * Sorts and adds the recommendations to the list of recommendations. + * @param noOfRecommendations the upper limit of recommendations. + */ + private void sortTvShowRecommendations(int noOfRecommendations) { + tvRecommendationOccurrences.entrySet().stream() + .sorted(comparingByValue()) + .limit(noOfRecommendations) + .forEachOrdered(entry -> tvShowRecommendations.add(ApiUtil.getTvShow(tmdbApi, entry.getKey()))); + } + + /** + * Parses the user's Tv Shows to get it's respective entry in the online database. A recommendation is then + * retrieved and added to the map of recommendations based on the number of occurrences. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void parseUserTvShows() throws OnlineConnectionException { + try { + for (TvShow tvShow : userTvShows) { + String tvShowName = tvShow.getName().showName; + + if (isInvalidName(tvShowName)) { + continue; + } + + TvResultsPage tvDbs = tmdbApi.getSearch().searchTv(tvShowName, null, 1); + List tvResults = tvDbs.getResults(); + + if (tvResults.isEmpty()) { + continue; + } + + int tvId = tvResults.get(0).getId(); //retrieves the first Tv Show that matches the name. + userTvShowsId.add(tvId); //adds the tv show id to a list so that a final filter can take place + + TvSeries series = tmdbApi.getTvSeries().getSeries(tvId, null, TmdbTV.TvMethod.recommendations); + ResultsPage recommendations = series.getRecommendations(); + List results = recommendations.getResults(); + results.forEach((tvSeries) -> addToRecommendations(tvSeries.getId(), false)); + } + } catch (MovieDbException e) { + ApiManager.notConnected(); + } + } + + /** + * Parses the user's Movies to get it's respective entry in the online database. A recommendation is then + * retrieved and added to the map of recommendations based on the number of occurrences. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void parseUserMovies() throws OnlineConnectionException { + try { + for (Movie m : userMovies) { + String movieName = m.getName().showName; + + if (isInvalidName(movieName)) { + continue; + } + + MovieResultsPage movieDbs = tmdbApi.getSearch() + .searchMovie(movieName, null, null, true, 1); + List movieDbsResults = movieDbs.getResults(); + + if (movieDbsResults.isEmpty()) { + continue; + } + + int movieId = movieDbsResults.get(0).getId(); //retrieves the first Movie that matches the name. + userMoviesId.add(movieId); //adds the movie id to a list so that a final filter can take place + + MovieDb movieDb = tmdbApi.getMovies().getMovie(movieId, null, TmdbMovies.MovieMethod.recommendations); + List similarMovies = movieDb.getRecommendations(); + if (!isNull(similarMovies)) { + similarMovies.forEach((movie) -> addToRecommendations(movie.getId(), true)); + } + } + } catch (MovieDbException e) { + ApiManager.notConnected(); + } + } + + /** + * Checks if the name is invalid. + * @param showName the Name to be checked. + * @return true if invalid. + */ + private boolean isInvalidName(String showName) { + return showName.equals(Name.DEFAULT_NAME); + } + + /** + * Adds an entry to the respective map. Increasing the occurrences if there is a duplicate. + * @param id the id of the Show. + * @param isMovie if the id belongs to a Movie. + */ + private void addToRecommendations(int id, boolean isMovie) { + HashMap showOccurrences = + isMovie ? movieRecommendationOccurrences : tvRecommendationOccurrences; + if (showOccurrences.containsKey(id)) { + Integer numberOfOccurrences = showOccurrences.remove(id); + showOccurrences.put(id, numberOfOccurrences + 1); + } else { + showOccurrences.put(id, 1); + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/api/util/ApiUtil.java b/src/main/java/seedu/ezwatchlist/api/util/ApiUtil.java new file mode 100644 index 00000000000..ef05f9ade0f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/api/util/ApiUtil.java @@ -0,0 +1,333 @@ +package seedu.ezwatchlist.api.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import info.movito.themoviedbapi.TmdbApi; +import info.movito.themoviedbapi.TmdbMovies; +import info.movito.themoviedbapi.TmdbTV; +import info.movito.themoviedbapi.TmdbTvSeasons; +import info.movito.themoviedbapi.TvResultsPage; +import info.movito.themoviedbapi.model.Credits; +import info.movito.themoviedbapi.model.Genre; +import info.movito.themoviedbapi.model.MovieDb; +import info.movito.themoviedbapi.model.core.MovieResultsPage; +import info.movito.themoviedbapi.model.people.PersonCast; +import info.movito.themoviedbapi.model.tv.TvEpisode; +import info.movito.themoviedbapi.model.tv.TvSeason; +import info.movito.themoviedbapi.model.tv.TvSeries; + +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.api.model.ImageRetrieval; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Episode; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Contains utility methods for extracting information from movies and tv shows. + */ +public class ApiUtil { + + /** + * Passes the movies from the movies page into the movies list with the new Movie model used in the application. + * + * @param movies list of movies to be added into. + * @param page results from the API database. + * @param apiCall API call to retrieve more information. + * @throws OnlineConnectionException when not connected to the internet. + */ + public static void extractMovies(ArrayList movies, MovieResultsPage page, + TmdbApi apiCall) throws OnlineConnectionException { + for (MovieDb m : page.getResults()) { + Movie toAdd = extractMovie(apiCall, m); + movies.add(toAdd); + } + } + + /** + * Morphs the MovieDb model into Movie model. + * @param apiCall API call to retrieve more information. + * @param movieDb model used by the API. + * @return Movie used by the application. + * @throws OnlineConnectionException when not connected to the internet/ + */ + public static Movie extractMovie(TmdbApi apiCall, MovieDb movieDb) throws OnlineConnectionException { + Name movieName = new Name(movieDb.getTitle()); + final int movieId = movieDb.getId(); //movie id to retrieve instance + TmdbMovies apiMovie = apiCall.getMovies(); + //gets the instance of the movie in the database + MovieDb movie = apiMovie.getMovie(movieId, null, TmdbMovies.MovieMethod.credits); + + //movie fields + RunningTime runtime = new RunningTime(movie.getRuntime()); + Description overview = new Description(movie.getOverview()); + Date releaseDate = new Date(movie.getReleaseDate()); + IsWatched isWatched = new IsWatched("false"); + + //actors + Set actors = getActors(movie.getCast()); + + Movie toAdd = new Movie(movieName, overview, + isWatched, releaseDate, runtime, actors); + + //retrieve image + addImage(movieDb.getPosterPath(), movieName, toAdd, apiCall); + + //genres + setGenres(movie.getGenres(), toAdd); + return toAdd; + } + + /** + * Uses the ImageRetrieval class to set an image to the show. + * + * @param posterPath path of the image online + * @param name name of the show + * @param toAdd the show to be modified + * @param apiCall the API call used + * @throws OnlineConnectionException when not connected to the internet. + */ + private static void addImage(String posterPath, Name name, Show toAdd, TmdbApi apiCall) + throws OnlineConnectionException { + try { + ImageRetrieval instance = new ImageRetrieval(apiCall, posterPath, name.showName); + String imagePath = instance.retrieveImage(); + toAdd.setPoster(new Poster(imagePath)); + } catch (IllegalArgumentException e) { + toAdd.setPoster(new Poster()); + } + } + + /** + * Passes the Tv Shows from the Tv Show page into the tvShow list with the new TvShow model used in the application. + * + * @param tvShows list of tvShows to be added into. + * @param page results from the API database. + * @param apiCall API call to retrieve more information. + * @throws OnlineConnectionException when not connected to the internet. + */ + public static void extractTvShows(ArrayList tvShows, TvResultsPage page, + TmdbApi apiCall) throws OnlineConnectionException { + for (TvSeries tv : page.getResults()) { + TvShow tvShowToAdd = extractTvShow(apiCall, tv); + + tvShows.add(tvShowToAdd); + } + } + + /** + * Morphs the TvSeries model into TvShow model. + * @param apiCall API call to retrieve more information. + * @param tv model used by the API. + * @return TvShow model used by the application. + * @throws OnlineConnectionException when not connected to the internet. + */ + public static TvShow extractTvShow(TmdbApi apiCall, TvSeries tv) throws OnlineConnectionException { + final int tvId = tv.getId(); + TmdbTV apiCallTvSeries = apiCall.getTvSeries(); + TvSeries series = apiCallTvSeries.getSeries(tvId, null); + TmdbTvSeasons tvSeasons = apiCall.getTvSeasons(); + final int numberOfSeasons = series.getNumberOfSeasons(); + + //seasons + ArrayList seasonsList = new ArrayList<>(); + extractSeasons(tvId, tvSeasons, numberOfSeasons, seasonsList); + + //runtime + List episodeRuntime = series.getEpisodeRuntime(); + RunningTime runTime = new RunningTime(episodeRuntime.isEmpty() ? 0 : getAverageRuntime(episodeRuntime)); + + //tv show fields. + Name tvName = new Name(tv.getName()); //name + Date date = new Date(series.getFirstAirDate()); //date of release + int totalNumOfEpisodes = getTotalNumOfEpisodes(seasonsList); + Description description = new Description(tv.getOverview()); //description + IsWatched isWatched = new IsWatched("false"); + + //actors + Credits credits = apiCallTvSeries.getCredits(tvId, null); + Set actors = getActors(credits.getCast()); + + TvShow tvShowToAdd = new TvShow(tvName, description, + isWatched, date, runTime, + actors, 0, totalNumOfEpisodes, seasonsList); + + //image + addImage(tv.getPosterPath(), tvName, tvShowToAdd, apiCall); + + //genres + setGenres(series.getGenres(), tvShowToAdd); + return tvShowToAdd; + } + + /** + * Extracts the seasons into the season list. + * + * @param tvId id of the TV Show in the database. + * @param tvSeasons the api TvSeason database. + * @param numberOfSeasons number of seasons of the show. + * @param seasonsList list to be added into. + */ + private static void extractSeasons(int tvId, TmdbTvSeasons tvSeasons, int numberOfSeasons, + ArrayList seasonsList) { + for (int seasonNo = 1; seasonNo <= numberOfSeasons; seasonNo++) { + TvSeason tvSeason = tvSeasons.getSeason(tvId, seasonNo, + null); + + seedu.ezwatchlist.model.show.TvSeason season = extractEpisodes(tvSeason); + + seasonsList.add(season); + } + } + + /** + * Extracts the episodes of each season. + * + * @param tvSeason the data from the API of the season. + * @return a season list with the episodes added in. + */ + private static seedu.ezwatchlist.model.show.TvSeason extractEpisodes(TvSeason tvSeason) { + List episodes = tvSeason.getEpisodes(); + ArrayList episodeList = new ArrayList<>(); + + for (TvEpisode episode : episodes) { + episodeList.add(new Episode( + episode.getName(), episode.getEpisodeNumber())); + } + + return new seedu.ezwatchlist.model.show.TvSeason(tvSeason.getSeasonNumber(), episodes.size(), + episodeList); + } + + /** + * Sets the genres taken from the API. + * + * @param genres the genre list to be added into + * @param tvShowToAdd the show to be modified. + */ + private static void setGenres(List genres, Show tvShowToAdd) { + ArrayList genreList = new ArrayList<>(); + genres.forEach(genre -> genreList.add(new seedu.ezwatchlist.model.show.Genre(genre.getName()))); + Set genreSet = new HashSet<>(genreList); + tvShowToAdd.addGenres(genreSet); + } + + /** + * Calculates the average runtime. + * + * @param episodesRuntime list of runtimes + * @return the average of the runtime. + */ + private static int getAverageRuntime(List episodesRuntime) { + float totalRuntime = 0; + int noOfEpisodes = episodesRuntime.size(); + + for (int i = 0; i < noOfEpisodes; i++) { + int individualRuntime = episodesRuntime.get(i); + totalRuntime += individualRuntime; + } + + int averageRunTime = Math.round(totalRuntime / noOfEpisodes); + return averageRunTime; + } + + /** + * Retrieves the actors from the online database. + * + * @param cast the list of cast taken online. + * @return a set of actors. + */ + private static Set getActors(List cast) { + Set actors = new HashSet<>(); + for (PersonCast personCast : cast) { + Actor actor = new Actor(personCast.getName()); + actors.add(actor); + } + return actors; + } + + /** + * Returns the total number of episodes + * + * @param tvSeasons the online tv season database + * @return total number of episodes + */ + private static int getTotalNumOfEpisodes(List tvSeasons) { + int totalEpisodes = 0; + for (seedu.ezwatchlist.model.show.TvSeason season : tvSeasons) { + totalEpisodes += season.getEpisodes().size(); + } + return totalEpisodes; + } + + /** + * Filters the list of Shows to a list of Movies. + * @param shows the list of Shows. + * @return a list of movies from the Shows. + */ + public static List filterToMovieFromShow(List shows) { + List movies = new LinkedList<>(); + for (Show show: shows) { + if (show.getType().equals("Movie")) { + movies.add((Movie) show); + } + } + return movies; + } + + /** + * Filters the list of Shows to a list of Tv Shows. + * @param shows the list of Shows. + * @return a list of Tv Shows from the Shows. + */ + public static List filterToTvShowsFromShow(List shows) { + List tvShows = new LinkedList<>(); + for (Show show: shows) { + if (show.getType().equals("Tv Show")) { + tvShows.add((TvShow) show); + } + } + return tvShows; + } + + /** + * Retrieves a Tv Show from it's ID + * @param tmdbApi + * @param tvId the ID of the Tv Show + * @return TvShow + */ + public static TvShow getTvShow(TmdbApi tmdbApi, Integer tvId) { + try { + TvSeries tvSeries = tmdbApi.getTvSeries().getSeries(tvId, null, TmdbTV.TvMethod.values()); + return extractTvShow(tmdbApi, tvSeries); + } catch (OnlineConnectionException e) { + return null; + } + } + + /** + * Retrieves a Movie from it's ID. + * @param tmdbApi + * @param movieId the ID of the Movie. + * @return Movie + */ + public static Movie getMovie(TmdbApi tmdbApi, Integer movieId) { + try { + MovieDb movie = tmdbApi.getMovies().getMovie(movieId, null, TmdbMovies.MovieMethod.values()); + return extractMovie(tmdbApi, movie); + } catch (OnlineConnectionException e) { + return null; + } + } +} diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/ezwatchlist/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/ezwatchlist/commons/core/Config.java index 91145745521..d7b5e691dd3 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/ezwatchlist/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.ezwatchlist.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/ezwatchlist/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/ezwatchlist/commons/core/GuiSettings.java index 5ace559ad15..adac4c223b7 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/ezwatchlist/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.ezwatchlist.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/ezwatchlist/commons/core/LogsCenter.java similarity index 95% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/ezwatchlist/commons/core/LogsCenter.java index 431e7185e76..59e8eb50dd5 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/ezwatchlist/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.ezwatchlist.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 = "watchlist.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; @@ -109,4 +109,8 @@ private static ConsoleHandler createConsoleHandler() { consoleHandler.setLevel(currentLogLevel); return consoleHandler; } + + public static Level getCurrentLogLevel() { + return currentLogLevel; + } } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/ezwatchlist/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/ezwatchlist/commons/core/Version.java index e117f91b3b2..0bea159f355 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/ezwatchlist/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.ezwatchlist.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/ezwatchlist/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/ezwatchlist/commons/core/index/Index.java index 19536439c09..315be3dc11a 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/ezwatchlist/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.ezwatchlist.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/ezwatchlist/commons/core/messages/Messages.java b/src/main/java/seedu/ezwatchlist/commons/core/messages/Messages.java new file mode 100644 index 00000000000..786ab6abbad --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/commons/core/messages/Messages.java @@ -0,0 +1,16 @@ +package seedu.ezwatchlist.commons.core.messages; + +/** + * 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_SHOW_DISPLAYED_INDEX = "The show index provided is invalid"; + public static final String MESSAGE_SYNC_INVALID_INDEX = "The INDEX only refers to the results found in" + + " search result page. Please" + + " ensure the index is valid."; + public static final String MESSAGE_INVALID_COMMAND = "Can't execute that command in this tab. Please switch over " + + "to the watchlist or watched tab and enter the command"; +} diff --git a/src/main/java/seedu/ezwatchlist/commons/core/messages/SearchMessages.java b/src/main/java/seedu/ezwatchlist/commons/core/messages/SearchMessages.java new file mode 100644 index 00000000000..5fb1f6562cc --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/commons/core/messages/SearchMessages.java @@ -0,0 +1,38 @@ +package seedu.ezwatchlist.commons.core.messages; + +/** + * Container for user visible messages when executing the search command. + */ +public class SearchMessages { + public static final String MESSAGE_USAGE = + "Search : Searches for shows whose names contain any of the given keywords from the watchlist, " + + "watched list and internal/online database.\n" + + "- by name: search n/SHOW_NAME… [g/GENRE]… [a/ACTOR_NAME]… [o/FROM_ONLINE] [t/SHOW_TYPE] " + + "[w/HAS_WATCHED]\n" + + "- by genre: search g/GENRE… [n/SHOW_NAME]… [a/ACTOR_NAME]… [o/FROM_ONLINE] [t/SHOW_TYPE] " + + "[w/HAS_WATCHED]\n" + + "- by actor: search a/ACTOR_NAME… [n/SHOW_NAME]… [g/GENRE]… [o/FROM_ONLINE] [t/SHOW_TYPE] " + + "[w/HAS_WATCHED]\n" + + "Enter 'help' command for more detailed examples."; + + public static final String MESSAGE_SHOWS_FOUND_OVERVIEW = "%1$d shows found!"; + + public static final String MESSAGE_INTERNAL_SHOW_LISTED_OVERVIEW = "You are offline. " + + "If required, shows would be searched from the internal database instead of the online database.\n" + + MESSAGE_SHOWS_FOUND_OVERVIEW; + + public static final String MESSAGE_INVALID_FROM_ONLINE_COMMAND = + "Invalid input. For o/[OPTION], OPTION can only be 'true', 'yes', 'false' or 'no'."; + + public static final String MESSAGE_INVALID_IS_WATCHED_COMMAND = + "Invalid input. For w/[IS_WATCHED], IS_WATCHED can only be 'true', 'yes', 'false' or 'no'."; + + public static final String MESSAGE_UNABLE_TO_SEARCH_FROM_ONLINE_WHEN_SEARCHING_BY_ACTOR = + "Invalid input. When searching by actor, it is not possible to search from online.\n" + MESSAGE_USAGE; + + public static final String MESSAGE_INVALID_TYPE_COMMAND = + "Invalid type. t/[TYPE] where TYPE can only be 'movie' or 'tv'"; + + public static final String MESSAGE_INVALID_GENRE_COMMAND = "Invalid input. Ensure that genre is not empty.\n" + + "search g/GENRE… [n/SHOW_NAME]… [a/ACTOR_NAME]… [o/FROM_ONLINE] [t/TYPE] [w/IS_WATCH]"; +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/ezwatchlist/commons/exceptions/DataConversionException.java similarity index 83% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/ezwatchlist/commons/exceptions/DataConversionException.java index 1f689bd8e3f..c5fa20e52f5 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/ezwatchlist/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.ezwatchlist.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/ezwatchlist/commons/exceptions/IllegalValueException.java similarity index 92% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/ezwatchlist/commons/exceptions/IllegalValueException.java index 19124db485c..df7313bcf76 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/ezwatchlist/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.ezwatchlist.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/ezwatchlist/commons/util/AppUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/AppUtil.java index da90201dfd6..d945a9a0297 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.ezwatchlist.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/ezwatchlist/commons/util/CollectionUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/CollectionUtil.java index eafe4dfd681..8dd21413319 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.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/ezwatchlist/commons/util/ConfigUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/ConfigUtil.java index f7f8a2bd44c..837db5f369a 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.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.ezwatchlist.commons.core.Config; +import seedu.ezwatchlist.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/ezwatchlist/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/FileUtil.java index b1e2767cdd9..c2fa803e8bb 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.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/ezwatchlist/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/JsonUtil.java index 8ef609f055d..03b1e076d13 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.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.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/ezwatchlist/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/ezwatchlist/commons/util/StringUtil.java index 61cc8c9a1cb..fe5e688b9a3 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/ezwatchlist/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.ezwatchlist.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.ezwatchlist.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/ezwatchlist/logic/Logic.java b/src/main/java/seedu/ezwatchlist/logic/Logic.java new file mode 100644 index 00000000000..deb1b732af2 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/Logic.java @@ -0,0 +1,87 @@ +package seedu.ezwatchlist.logic; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.GuiSettings; +import seedu.ezwatchlist.logic.commands.CommandResult; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.ui.MainWindow; + +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @param mainWindow + * @param currentTab + * @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, MainWindow mainWindow, String currentTab) + throws CommandException, ParseException, OnlineConnectionException, InterruptedException; + + /** + * Returns the model. + * @return the model + */ + Model getModel(); + + /** + * Returns the WatchList. + * + * @see Model#getWatchList() + */ + ReadOnlyWatchList getWatchList(); + + /** + * Returns the database of shows. + * + * @see Model#getDatabase() + */ + ReadOnlyWatchList getDatabase(); + + /** Returns an unmodifiable view of the filtered shows that have not been watched */ + ObservableList getUnWatchedList(); + + /** Returns an unmodifiable view of the filtered watched list of shows */ + ObservableList getWatchedList(); + + /** Returns an unmodifiable view of the filtered list of shows */ + ObservableList getFilteredShowList(); + + /** Updates the filter of the filtered show list by the given {@code predicate}. */ + void updateFilteredShowList(Predicate predicate); + + /** Returns an unmodifiable view of the search results of shows */ + ObservableList getSearchResultList(); + + /** + * Returns the user prefs' watchlist file path. + */ + Path getWatchListFilePath(); + + /** + * Returns the user prefs' database file path. + */ + Path getDatabaseFilePath(); + + /** + * 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/ezwatchlist/logic/LogicManager.java b/src/main/java/seedu/ezwatchlist/logic/LogicManager.java new file mode 100644 index 00000000000..2edc88fd4d6 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/LogicManager.java @@ -0,0 +1,138 @@ +package seedu.ezwatchlist.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.GuiSettings; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.logic.commands.Command; +import seedu.ezwatchlist.logic.commands.CommandResult; +import seedu.ezwatchlist.logic.commands.SearchCommand; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.WatchListParser; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.storage.Storage; +import seedu.ezwatchlist.ui.MainWindow; + +/** + * 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 WatchListParser watchListParser; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + watchListParser = new WatchListParser(); + } + + @Override + public CommandResult execute(String commandText, MainWindow mainWindow, String currentTab) + throws CommandException, ParseException, OnlineConnectionException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + final CommandResult[] commandResult = new CommandResult[1]; + Command command = watchListParser.parseCommand(commandText, currentTab); + + if (command instanceof SearchCommand) { + mainWindow.setIsSearchLoading(); + mainWindow.goToSearch(); + mainWindow.getResultDisplay().setFeedbackToUser("Loading..."); + Task task = new Task() { + @Override + protected CommandResult call() throws Exception { + return command.execute(model); + } + }; + task.setOnSucceeded(event -> { + mainWindow.setIsSearchLoading(); + mainWindow.goToSearch(); + commandResult[0] = task.getValue(); + mainWindow.searchResultLogger(commandResult[0]); + }); + new Thread(task).start(); + return null; + } else { + commandResult[0] = command.execute(model); + } + try { + storage.saveWatchList(model.getWatchList()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + return commandResult[0]; + } + + @Override + public Model getModel() { + return model; + } + + @Override + public ReadOnlyWatchList getWatchList() { + return model.getWatchList(); + } + + @Override + public ReadOnlyWatchList getDatabase() { + return model.getDatabase(); + } + + @Override + public ObservableList getUnWatchedList() { + return model.getUnWatchedShowList(); + } + + @Override + public ObservableList getWatchedList() { + return model.getWatchedShowList(); + } + + @Override + public ObservableList getFilteredShowList() { + return model.getFilteredShowList(); + } + + @Override + public void updateFilteredShowList(Predicate predicate) { + model.updateFilteredShowList(predicate); + } + + @Override + public ObservableList getSearchResultList() { + return model.getSearchResultList(); + } + + @Override + public Path getWatchListFilePath() { + return model.getWatchListFilePath(); + } + + @Override + public Path getDatabaseFilePath() { + return model.getDatabaseFilePath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/AddCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/AddCommand.java new file mode 100644 index 00000000000..1b29bd551c7 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/AddCommand.java @@ -0,0 +1,131 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_ACTOR; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DATE_OF_RELEASE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_IS_WATCHED; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_RUNNING_TIME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.util.List; + +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.show.Show; + +/** @@author wongchuankai + * Adds a show to the watchlist. + */ +public class AddCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a show to the watchlist. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_TYPE + "TYPE ('movie' or 'tv') " + + "[" + PREFIX_DATE_OF_RELEASE + "DATE OF RELEASE] " + + "[" + PREFIX_IS_WATCHED + "WATCHED ('true' or 'false')] " + + "[" + PREFIX_RUNNING_TIME + "RUNNING TIME] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_ACTOR + "ACTOR]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Joker " + + PREFIX_TYPE + "movie " + + PREFIX_DATE_OF_RELEASE + "4 October 2019 " + + PREFIX_IS_WATCHED + "true " + + PREFIX_RUNNING_TIME + "122 " + + PREFIX_DESCRIPTION + "Joker is funny " + + PREFIX_ACTOR + "Joaquin Phoenix " + + PREFIX_ACTOR + "Robert De Niro"; + + public static final String MESSAGE_USAGE2 = COMMAND_WORD + ": Sync a show found online to the watchlist. " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "New show added: %1$s"; + public static final String MESSAGE_DUPLICATE_SHOW = "This show already exists in the watchlist"; + public static final String MESSAGE_SUCCESS2 = "Add movie: %1$s"; + + public static final String UNSUCCESSFUL_INDEX = "Search Result Page is currently empty."; + public static final String UNSUCCESSFUL_LARGER = "The index is larger than the total number" + + " of shows in search page."; + public static final String NOT_AT_SEARCH_LIST_PAGE = "'Add Index' command is only available in Search Panel"; + + private final Show toAdd; + + + + private final int index; + private final boolean isFromSearch; + + /** + * Creates an AddCommand to add the specified {@code Show} + */ + public AddCommand(Show show) { + requireNonNull(show); + toAdd = show; + index = -1; + isFromSearch = false; + } + + public AddCommand(int index) { + requireNonNull(index); + this.index = index; + toAdd = null; + isFromSearch = true; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (isFromSearch) { + return fromSearch(model); + } + if (model.hasShow(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_SHOW); + } + + model.addShow(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), true); + } + + /** + * Retrieve movies from searchlist found in model. + * @param model + * @return + * @throws CommandException + */ + + public CommandResult fromSearch(Model model) throws CommandException { + List searchResultList = model.getSearchResultList(); + if (searchResultList.isEmpty()) { + throw new CommandException(UNSUCCESSFUL_INDEX); + } + if (index > searchResultList.size()) { + throw new CommandException(UNSUCCESSFUL_LARGER); + } + Show fromImdb = searchResultList.get(index - 1); + if (model.hasShow(fromImdb)) { + throw new CommandException(MESSAGE_DUPLICATE_SHOW); + } + + model.addShow(fromImdb); + return new CommandResult(String.format(MESSAGE_SUCCESS2, fromImdb), true); + } + public boolean isFromSearch() { + return isFromSearch; + } + + @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) + && index == (((AddCommand) other).index) + && isFromSearch == ((AddCommand) other).isFromSearch); + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/ClearCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/ClearCommand.java new file mode 100644 index 00000000000..856dc3e3d13 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/ClearCommand.java @@ -0,0 +1,23 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.WatchList; + +/** + * Clears the watchlist. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Watchlist has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setWatchList(new WatchList()); + return new CommandResult(MESSAGE_SUCCESS, true); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/ezwatchlist/logic/commands/Command.java similarity index 65% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/ezwatchlist/logic/commands/Command.java index 64f18992160..58ee97b8676 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/Command.java @@ -1,7 +1,8 @@ -package seedu.address.logic.commands; +package seedu.ezwatchlist.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -15,6 +16,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(Model model) throws CommandException, OnlineConnectionException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/ezwatchlist/logic/commands/CommandResult.java similarity index 73% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/ezwatchlist/logic/commands/CommandResult.java index 92f900b7916..0b33faa6422 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.ezwatchlist.logic.commands; import static java.util.Objects.requireNonNull; @@ -17,21 +17,28 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + private final boolean shortCutKey; + + private final boolean isChangedList; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, + boolean shortCutKey, boolean isChangedList) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.shortCutKey = shortCutKey; + this.isChangedList = isChangedList; } /** * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, * and other fields set to their default value. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + public CommandResult(String feedbackToUser, boolean isChangedList) { + this(feedbackToUser, false, false, false, isChangedList); } public String getFeedbackToUser() { @@ -46,6 +53,14 @@ public boolean isExit() { return exit; } + public boolean isShortCutKey() { + return shortCutKey; + } + + public boolean isChangedList() { + return isChangedList; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -58,6 +73,7 @@ public boolean equals(Object other) { } CommandResult otherCommandResult = (CommandResult) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp && exit == otherCommandResult.exit; diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/DeleteCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/ezwatchlist/logic/commands/DeleteCommand.java index 02fd256acba..1b3428afdba 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/DeleteCommand.java @@ -1,28 +1,28 @@ -package seedu.address.logic.commands; +package seedu.ezwatchlist.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; +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.show.Show; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a show identified using it's displayed index from the watchlist. */ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the show identified by the index number used in the displayed show list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_SHOW_SUCCESS = "Deleted Show: %1$s"; private final Index targetIndex; @@ -33,15 +33,15 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredShowList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_SHOW_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + Show showToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteShow(showToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_SHOW_SUCCESS, showToDelete), true); } @Override diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/EditCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/EditCommand.java new file mode 100644 index 00000000000..23dfdc42b18 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/EditCommand.java @@ -0,0 +1,346 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_ACTOR; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DATE_OF_RELEASE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_IS_WATCHED; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_RUNNING_TIME; + +import static seedu.ezwatchlist.model.Model.PREDICATE_ALL_SHOWS; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.commons.util.CollectionUtil; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvSeason; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Edits the details of an existing show in the watchlist. + */ +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 show identified " + + "by the index number used in the displayed show list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_DATE_OF_RELEASE + "DATE OF RELEASE] " + + "[" + PREFIX_IS_WATCHED + "WATCHED ('true' or 'false')] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_RUNNING_TIME + "RUNNING TIME] " + + "[" + PREFIX_ACTOR + "ACTOR]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NAME + "Joker " + + PREFIX_DATE_OF_RELEASE + "3 October 2019"; + + public static final String MESSAGE_EDIT_SHOW_SUCCESS = "Edited Show: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_SHOW = "This show already exists in the watchlist."; + + private final Index index; + private final EditShowDescriptor editShowDescriptor; + + /** + * @param index of the show in the filtered show list to edit + * @param editShowDescriptor details to edit the show with + */ + public EditCommand(Index index, EditShowDescriptor editShowDescriptor) { + requireNonNull(index); + requireNonNull(editShowDescriptor); + + this.index = index; + this.editShowDescriptor = new EditShowDescriptor(editShowDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredShowList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_SHOW_DISPLAYED_INDEX); + } + + Show showToEdit = lastShownList.get(index.getZeroBased()); + Show editedShow = createEditedShow(showToEdit, editShowDescriptor); + + if (!showToEdit.isSameShow(editedShow) && model.hasShow(editedShow)) { + throw new CommandException(MESSAGE_DUPLICATE_SHOW); + } + model.setShow(showToEdit, editedShow); + model.updateFilteredShowList(PREDICATE_ALL_SHOWS); + return new CommandResult(String.format(MESSAGE_EDIT_SHOW_SUCCESS, editedShow), true); + } + + /** + * Creates and returns a {@code Show} with the details of {@code showToEdit} + * edited with {@code editShowDescriptor}. + */ + private static Show createEditedShow(Show showToEdit, EditShowDescriptor editShowDescriptor) { + assert showToEdit != null; + + Name updatedName = editShowDescriptor.getName().orElse(showToEdit.getName()); + Date updatedDateOfRelease = editShowDescriptor.getDateOfRelease().orElse(showToEdit.getDateOfRelease()); + IsWatched updatedIsWatched = editShowDescriptor.getIsWatched().orElse(showToEdit.isWatched()); + Description updatedDescription = editShowDescriptor.getDescription().orElse(showToEdit.getDescription()); + RunningTime updatedRunningTime = editShowDescriptor.getRunningTime().orElse(showToEdit.getRunningTime()); + Set updatedActors = editShowDescriptor.getActors().orElse(showToEdit.getActors()); + Poster updatedPoster = editShowDescriptor.getPoster().orElse(showToEdit.getPoster()); + Set updatedGenres = editShowDescriptor.getGenres().orElse(showToEdit.getGenres()); + + if (showToEdit.getType().equals("Movie")) { + Movie editedShow = new Movie(updatedName, updatedDescription, updatedIsWatched, + updatedDateOfRelease, updatedRunningTime, updatedActors); + editedShow.setPoster(updatedPoster); + return editedShow; + } else { //Tv show + int updatedNumberOfEpisodesWatched = showToEdit.getNumOfEpisodesWatched(); + int updatedTotalNumOfEpisodes = showToEdit.getTotalNumOfEpisodes(); + List updatedSeasons = editShowDescriptor.getSeasons().orElse(showToEdit.getTvSeasons()); + + TvShow editedShow = new TvShow(updatedName, updatedDescription, updatedIsWatched, + updatedDateOfRelease, updatedRunningTime, updatedActors, updatedNumberOfEpisodesWatched, + updatedTotalNumOfEpisodes, updatedSeasons); + editedShow.setPoster(updatedPoster); + editedShow.addGenres(updatedGenres); + + return editedShow; + } + } + + /** + * createEditedShowTest is a public method to access the private createEditedShow. + * This method is used to testing to retrieve information from private createEditedShow. + * @param showToEdit + * @param editShowDescriptor + * @return + */ + public Show createEditedShowTest(Show showToEdit, EditShowDescriptor editShowDescriptor) { + return createEditedShow(showToEdit, editShowDescriptor); + } + @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) + && editShowDescriptor.equals(e.editShowDescriptor); + } + + /** + * Stores the details to edit the show with. Each non-empty field value will replace the + * corresponding field value of the show. + */ + public static class EditShowDescriptor { + private Name name; + private String type; + private Date dateOfRelease; + private IsWatched isWatched; + private Description description; + private RunningTime runningTime; + private Set actors; + private Poster poster; + private Set genres; + private int numOfEpisodesWatched; + private int totalNumOfEpisodes; + private List seasons; + + public EditShowDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code actors} is used internally. + */ + public EditShowDescriptor(EditShowDescriptor toCopy) { + setName(toCopy.name); + setType(toCopy.type); + setDateOfRelease(toCopy.dateOfRelease); + setIsWatched(toCopy.isWatched); + setDescription(toCopy.description); + setRunningTime(toCopy.runningTime); + setActors(toCopy.actors); + setPoster(toCopy.poster); + setGenres(toCopy.genres); + setNumOfEpisodesWatched(toCopy.numOfEpisodesWatched); + setTotalNumOfEpisodes(toCopy.totalNumOfEpisodes); + setSeasons(toCopy.seasons); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, dateOfRelease, isWatched, description, runningTime, actors); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setType(String type) { + this.type = type; + } + + public Optional getType() { + return Optional.ofNullable(type); + } + + public void setDateOfRelease(Date dateOfRelease) { + this.dateOfRelease = dateOfRelease; + } + + public Optional getDateOfRelease() { + return Optional.ofNullable(dateOfRelease); + } + + public void setIsWatched(IsWatched isWatched) { + this.isWatched = isWatched; + } + + public Optional getIsWatched() { + return Optional.ofNullable(isWatched); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setRunningTime(RunningTime runningTime) { + this.runningTime = runningTime; + } + + public Optional getRunningTime() { + return Optional.ofNullable(runningTime); + } + + public void setPoster(Poster poster) { + this.poster = poster; + } + + public Optional getPoster() { + return Optional.ofNullable(poster); + } + + public void setNumOfEpisodesWatched(int numOfEpisodesWatched) { + this.numOfEpisodesWatched = numOfEpisodesWatched; + } + + public void setTotalNumOfEpisodes(int totalNumOfEpisodes) { + this.totalNumOfEpisodes = totalNumOfEpisodes; + } + + /** + * Sets {@code actors} to this object's {@code actors}. + * A defensive copy of {@code actors} is used internally. + */ + public void setActors(Set actors) { + this.actors = (actors != null) ? new HashSet<>(actors) : null; + } + + /** + * Returns an unmodifiable actor set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code actor} is null. + */ + public Optional> getActors() { + return (actors != null) ? Optional.of(Collections.unmodifiableSet(actors)) : Optional.empty(); + } + + /** + * Sets {@code genres} to this object's {@code genres}. + * A defensive copy of {@code genres} is used internally. + */ + public void setGenres(Set genres) { + this.genres = (genres != null) ? new HashSet<>(genres) : null; + } + + /** + * Returns an unmodifiable genre set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code genre} is null. + */ + public Optional> getGenres() { + return (genres != null) ? Optional.of(Collections.unmodifiableSet(genres)) : Optional.empty(); + } + + /** + * Sets {@code seasons} to this object's {@code seasons}. + * A defensive copy of {@code seasons} is used internally. + */ + public void setSeasons(List seasons) { + this.seasons = (seasons != null) ? new ArrayList<>(seasons) : null; + } + + /** + * Returns an unmodifiable season set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code season} is null. + */ + public Optional> getSeasons() { + return (seasons != null) ? Optional.of(Collections.unmodifiableList(seasons)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditShowDescriptor)) { + return false; + } + + // state check + EditShowDescriptor e = (EditShowDescriptor) other; + + return getName().equals(e.getName()) + && getDateOfRelease().equals(e.getDateOfRelease()) + && getIsWatched().equals(e.getIsWatched()) + && getDescription().equals(e.getDescription()) + && getRunningTime().equals(e.getRunningTime()) + && getActors().equals(e.getActors()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/ExitCommand.java similarity index 69% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/ezwatchlist/logic/commands/ExitCommand.java index 3dd85a8ba90..facb45d9ddf 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.ezwatchlist.logic.commands; -import seedu.address.model.Model; +import seedu.ezwatchlist.model.Model; /** * Terminates the program. @@ -9,11 +9,11 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Ezwatchlist as requested ..."; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, false); } } diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/GoToCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/GoToCommand.java new file mode 100644 index 00000000000..7f77eabbc5f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/GoToCommand.java @@ -0,0 +1,41 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; + +/** + * Go To short cut key for watchlist + */ +public class GoToCommand extends Command { + + public static final String COMMAND_WORD = ""; + public static final String MESSAGE_USAGE = "Go to page: %1$s"; + public static final String MESSAGE_SUCCESS = "Go to page: %1$s"; + public static final String MESSAGE_UNSUCCESSFUL_INPUT = "wrong input"; + + private final String shortcut; + /** + * Creates an GoToCommand to add the specified {@code shortcut} + * @param shortcut + */ + public GoToCommand(String shortcut) { + requireNonNull(shortcut); + this.shortcut = shortcut; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + String pageTitle = model.getPage(shortcut); + return new CommandResult(pageTitle, false, false, true, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoToCommand // instanceof handles nulls + && shortcut.equals(((GoToCommand) other).shortcut)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/HelpCommand.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/ezwatchlist/logic/commands/HelpCommand.java index bf824f91bd0..0f091ac3cc8 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/HelpCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.ezwatchlist.logic.commands; -import seedu.address.model.Model; +import seedu.ezwatchlist.model.Model; /** * Format full help instructions for every command for display. @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, false); } } diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/ListCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/ListCommand.java new file mode 100644 index 00000000000..b29ccb65f44 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/ListCommand.java @@ -0,0 +1,24 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.model.Model.PREDICATE_ALL_SHOWS; + +import seedu.ezwatchlist.model.Model; + +/** + * Lists all shows in the watchlist to the user. + */ +public class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all shows"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredShowList(PREDICATE_ALL_SHOWS); + return new CommandResult(MESSAGE_SUCCESS, false); + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/SearchCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/SearchCommand.java new file mode 100644 index 00000000000..47523b31ef6 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/SearchCommand.java @@ -0,0 +1,483 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.api.model.ApiInterface; +import seedu.ezwatchlist.api.model.ApiManager; +import seedu.ezwatchlist.commons.core.messages.SearchMessages; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.SearchKey; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvShow; +import seedu.ezwatchlist.model.show.Type; + +/** + * Finds and lists all shows in watchlist whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class SearchCommand extends Command { + public static final String COMMAND_WORD = "search"; + + private static final String INPUT_TRUE = "true"; + private static final String INPUT_YES = "yes"; + private static final String INPUT_FALSE = "false"; + private static final String INPUT_NO = "no"; + + private ApiInterface onlineSearch; + private List nameList; + private List typeList; + private List actorList; + private List genreList; + private List isWatchedList; + private List fromOnlineList; + private List searchResult = new ArrayList<>(); + + private boolean isOffline = false; + + public SearchCommand(HashMap> searchShowsHashMap) { + nameList = searchShowsHashMap.get(SearchKey.KEY_NAME); + typeList = searchShowsHashMap.get(SearchKey.KEY_TYPE); + actorList = searchShowsHashMap.get(SearchKey.KEY_ACTOR); + genreList = searchShowsHashMap.get(SearchKey.KEY_GENRE); + isWatchedList = searchShowsHashMap.get(SearchKey.KEY_IS_WATCHED); + fromOnlineList = searchShowsHashMap.get(SearchKey.KEY_FROM_ONLINE); + + try { + onlineSearch = new ApiManager(); + } catch (OnlineConnectionException e) { + isOffline = true; + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + searchByName(model); + searchByGenre(model); + searchByActor(model); + + filterOutDuplicatesInSearchResult(model); + + if (isOffline) { + return new CommandResult(String.format(SearchMessages.MESSAGE_INTERNAL_SHOW_LISTED_OVERVIEW, + model.getSearchResultList().size()), false); + } else { + return new CommandResult(String.format(SearchMessages.MESSAGE_SHOWS_FOUND_OVERVIEW, + model.getSearchResultList().size()), false); + } + } + + /** + * Search for shows by name. + * @param model Model used. + * @throws CommandException If command exception occurred. + */ + private void searchByName(Model model) throws CommandException { + if (nameList.isEmpty()) { + return; + } + try { + if (requestedSearchFromInternal()) { + for (String showName : nameList) { + addShowFromWatchListIfSameNameAs(showName, model); + } + } else if (requestedSearchFromOnline()) { + for (String showName : nameList) { + addShowFromOnlineIfSameNameAs(showName); + } + } else if (!requestedFromOnline()) { + for (String showName : nameList) { + addShowFromOnlineIfSameNameAs(showName); + } + } else { + throw new CommandException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + } catch (OnlineConnectionException oce) { + for (String showName : nameList) { + addShowFromWatchListIfSameNameAs(showName, model); + addShowFromDatabaseIfSameNameAs(showName, model); + } + isOffline = true; + } + } + + /** + * Search for shows by actor. + * @param model Model used. + * @throws CommandException If command exception occurred. + */ + private void searchByActor(Model model) throws CommandException { + if (actorList.isEmpty()) { + return; + } + Set actorSet = new HashSet(); + for (String actorName : actorList) { + if (actorName.isBlank()) { + continue; + } + Actor actor = new Actor(actorName.trim()); + actorSet.add(actor); + } + + if (requestedSearchFromInternal()) { + addShowFromWatchListIfHasActor(actorSet, model); + } else if (requestedSearchFromOnline()) { + throw new CommandException(SearchMessages.MESSAGE_UNABLE_TO_SEARCH_FROM_ONLINE_WHEN_SEARCHING_BY_ACTOR); + } else if (!requestedFromOnline()) { + addShowFromWatchListIfHasActor(actorSet, model); + } else { + throw new CommandException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + } + + /** + * Search for shows by genre. + * @param model Model used. + * @throws CommandException If command exception occurred. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void searchByGenre(Model model) throws CommandException { + if (genreList.isEmpty()) { + return; + } + Set genreSet = new HashSet(); + for (String genreName : genreList) { + if (genreName.isBlank()) { + continue; + } + Genre genre = new Genre(genreName.trim()); + genreSet.add(genre); + } + + try { + if (requestedSearchFromInternal()) { + addShowFromWatchListIfIsGenre(genreSet, model); + } else if (requestedSearchFromOnline()) { + addMovieFromOnlineIfIsGenre(genreSet); + } else if (!requestedFromOnline()) { + addMovieFromOnlineIfIsGenre(genreSet); + } else { + throw new CommandException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + } catch (OnlineConnectionException oce) { + addShowFromWatchListIfIsGenre(genreSet, model); + addShowFromDatabaseIfIsGenre(genreSet, model); + isOffline = true; + } + } + + /** + * Adds show from list if it has the same name as in {@code showName}. + * @param showName name of the given show. + * @param model current model of the program. + */ + private void addShowFromWatchListIfSameNameAs(String showName, Model model) throws CommandException { + if (showName.isBlank()) { + return; + } + List filteredShowList = model.getShowFromWatchlistIfHasName(new Name(showName.trim())); + addShowToSearchResult(filteredShowList); + } + + /** + * Adds show from offline database if it has the same name as in {@code showName}. + * @param showName name of the given show. + * @param model current model of the program. + */ + private void addShowFromDatabaseIfSameNameAs(String showName, Model model) throws CommandException { + if (showName.isBlank()) { + return; + } + List filteredShowList = model.getShowFromDatabaseIfHasName(new Name(showName)); + addShowToSearchResult(filteredShowList); + } + + /** + * Adds show from list if it has any actor in {@code actorSet}. + * @param actorSet Set of actors to be searched for. + * @param model current model of the program. + */ + private void addShowFromWatchListIfHasActor(Set actorSet, Model model) throws CommandException { + if (actorSet.isEmpty()) { + return; + } + List filteredShowList = model.getShowFromWatchlistIfHasActor(actorSet); + addShowToSearchResult(filteredShowList); + } + + /** + * Adds shows from database if it has any actor in {@code actorSet}. + * @param actorSet Set of actors to be searched for. + * @param model current model of the program. + */ + private void addShowFromDatabaseIfHasActor(Set actorSet, Model model) throws CommandException { + if (actorSet.isEmpty()) { + return; + } + List filteredShowList = model.getShowFromDatabaseIfHasActor(actorSet); + addShowToSearchResult(filteredShowList); + } + + /** + * Adds show from watchlist if it has any genre in {@code genreSet}. + * @param genreSet set of actors to be searched for. + * @param model current model of the program. + */ + private void addShowFromWatchListIfIsGenre(Set genreSet, Model model) throws CommandException { + if (genreSet.isEmpty()) { + return; + } + List filteredShowList = model.getShowFromWatchlistIfIsGenre(genreSet); + addShowToSearchResult(filteredShowList); + } + + /** + * Adds show from list if it has any genre in {@code genreSet}. + * @param genreSet set of actors to be searched for. + * @param model current model of the program. + */ + private void addShowFromDatabaseIfIsGenre(Set genreSet, Model model) throws CommandException { + if (genreSet.isEmpty()) { + return; + } + List filteredShowList = model.getShowFromDatabaseIfIsGenre(genreSet); + addShowToSearchResult(filteredShowList); + } + + /** + * Add show to search result. + * @param showList List of shows to be added. + */ + private void addShowToSearchResult(List showList) throws CommandException { + for (Show show : showList) { + if (requestedSearchFromWatched() && !show.isWatched().getIsWatchedBoolean()) { + continue; // skip if request to be watched but show is not watched + } else if (requestedSearchFromWatchList() && show.isWatched().getIsWatchedBoolean()) { + continue; // skip if requested to be in watchlist but show is watched + } else if (requestedSearchForMovie() && !show.getType().equals("Movie")) { + continue; // skip if requested search for movie but show is tv + } else if (requestedSearchForTv() && !show.getType().equals("Tv Show")) { + continue; // skip if requested search for tv but show is movie + } + searchResult.add(show); + } + } + + /** + * Add shows, both movies and tv series, searched by name from online to the search result list. + * @param showName Name of the show to be searched. + * @throws OnlineConnectionException when not connected to the internet. + * @throws CommandException If command exception occurred. + */ + private void addShowFromOnlineIfSameNameAs(String showName) throws OnlineConnectionException, CommandException { + if (!requestedSearchFromWatched() && !showName.isBlank()) { + if (requestedSearchForMovie()) { + addOnlineMovieSearchedByNameToResult(showName); + } else if (requestedSearchForTv()) { + addOnlineTvSearchedByNameToResult(showName); + } else if (requestedType()) { + throw new CommandException(SearchMessages.MESSAGE_INVALID_TYPE_COMMAND); + } else { + addOnlineMovieSearchedByNameToResult(showName); + addOnlineTvSearchedByNameToResult(showName); + } + } + } + + /** + * Add movies, searched by name from online to the search result list. + * @param showName Name of the show to be searched. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void addOnlineMovieSearchedByNameToResult(String showName) throws OnlineConnectionException { + if (onlineSearch == null) { + throw new OnlineConnectionException("User is offline."); + } + List movies = onlineSearch.getMovieByName(showName.trim()); + for (Movie movie : movies) { + searchResult.add(movie); + } + } + + /** + * Add tv series, searched by name from online to the search result list. + * @param showName Name of the show to be searched. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void addOnlineTvSearchedByNameToResult(String showName) throws OnlineConnectionException { + if (onlineSearch == null) { + throw new OnlineConnectionException("User is offline."); + } + List tvShows = onlineSearch.getTvShowByName(showName.trim()); + for (TvShow tvShow : tvShows) { + searchResult.add(tvShow); + } + } + + /** + * Returns a list of movies from the API search method based on the genre searched. + * @param genreSet the set of genres that the user wants to search. + * @throws OnlineConnectionException when not connected to the internet. + */ + private void addMovieFromOnlineIfIsGenre(Set genreSet) throws OnlineConnectionException { + if (onlineSearch == null) { + throw new OnlineConnectionException("User is offline."); + } + if (genreSet.isEmpty()) { + return; + } + List movies = onlineSearch.getMovieByGenre(genreSet); + for (Movie movie : movies) { + searchResult.add(movie); + } + } + + /** + * Returns true if user requests to search for tv series or movies only. + * @return True if user requests to search for tv series or movies only. + */ + private boolean requestedType() throws CommandException { + for (String input : typeList) { + if (!(input.equals(Type.MOVIE.getType()) || input.equals(Type.TV_SHOW.getType()))) { + throw new CommandException(SearchMessages.MESSAGE_INVALID_TYPE_COMMAND); + } + } + return !typeList.isEmpty(); + } + + /** + * Returns true if user requests to search for movies only. + * @return True if user requests to search for movies only. + */ + private boolean requestedSearchForMovie() throws CommandException { + return requestedType() && (typeList.get(0).equals(Type.MOVIE.getType())); + } + + /** + * Returns true if user requests to search for tv series only. + * @return True if user requests to search for tv series only. + */ + private boolean requestedSearchForTv() throws CommandException { + return requestedType() && (typeList.get(0).equals(Type.TV_SHOW.getType())); + } + + /** + * Returns true if user requests to search from watch or watched list. + * @return True if user requests to search from watch or watched list. + */ + private boolean requestedIsWatched() throws CommandException { + for (String input : isWatchedList) { + if (!(input.equals(INPUT_FALSE) || input.equals(INPUT_NO) || input.equals(INPUT_TRUE) + || input.equals(INPUT_YES))) { + throw new CommandException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + } + return !isWatchedList.isEmpty(); + } + + /** + * Returns true if user requests to search from watched list. + * @return True if user requests to search from watched list. + */ + private boolean requestedSearchFromWatched() throws CommandException { + return requestedIsWatched() + && (isWatchedList.get(0).equals(INPUT_TRUE) || isWatchedList.get(0).equals(INPUT_YES)); + } + + /** + * Returns true if user requests to search from watch list. + * @return True if user requests to search from watch list. + */ + private boolean requestedSearchFromWatchList() throws CommandException { + return requestedIsWatched() + && (isWatchedList.get(0).equals(INPUT_FALSE) || isWatchedList.get(0).equals(INPUT_NO)); + } + + /** + * Returns true if user requests to search from internal or online. + * @return True if user requests to search from internal or online. + */ + private boolean requestedFromOnline() throws CommandException { + for (String input : fromOnlineList) { + if (!(input.equals(INPUT_FALSE) || input.equals(INPUT_NO) || input.equals(INPUT_TRUE) + || input.equals(INPUT_YES))) { + throw new CommandException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + } + return !fromOnlineList.isEmpty(); + } + + /** + * Returns true if user requests to search from internal. + * @return True if user requests to search from internal. + */ + private boolean requestedSearchFromInternal() throws CommandException { + boolean requestedFromOnline = requestedFromOnline(); + for (String input : fromOnlineList) { + if (requestedFromOnline && (input.equals(INPUT_FALSE) || input.equals(INPUT_NO))) { + return true; + } + } + return false; + } + + /** + * Returns true if user requests to search from online. + * @return True if user requests to search from online. + */ + private boolean requestedSearchFromOnline() throws CommandException { + boolean requestedFromOnline = requestedFromOnline(); + for (String input : fromOnlineList) { + if (requestedFromOnline && (input.equals(INPUT_TRUE) || input.equals(INPUT_YES))) { + return true; + } + } + return false; + } + + /** + * Filters out the duplicates in the search result. + * @param model current model of the program. + */ + private void filterOutDuplicatesInSearchResult(Model model) { + List result = searchResult.stream().distinct().collect(Collectors.toList()); + + + + model.updateSearchResultList(result); + } + + /** + * Returns the list of search results. + * @return List of search results. + */ + public List getSearchResult() { + return searchResult; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SearchCommand // instanceof handles nulls + && nameList.equals(((SearchCommand) other).nameList) + && typeList.equals(((SearchCommand) other).typeList) + && actorList.equals(((SearchCommand) other).actorList) + && genreList.equals(((SearchCommand) other).genreList) + && isWatchedList.equals(((SearchCommand) other).isWatchedList) + && fromOnlineList.equals(((SearchCommand) other).fromOnlineList)); + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/SyncCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/SyncCommand.java new file mode 100644 index 00000000000..da0efc51e2f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/SyncCommand.java @@ -0,0 +1,80 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Show; + +/** @@author wongchuankai + * Syncs a result from the search list into the watch list. + */ +public class SyncCommand extends Command { + + public static final String COMMAND_WORD = "sync"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sync a show found online to the watchlist. " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Sync movie: %1$s"; + public static final String MESSAGE_DUPLICATE_SHOW = "?"; //"This show already exists in the watchlist"; + + public static final String MESSAGE_UNSUCCESSFUL = "No matching name found in local internal storage."; + public static final String MESSAGE_UNSUCCESFUL2 = "You may use the add INDEX command to add searched-online shows" + + " into Watchlist."; + public static final String MESSAGE_UNSUCCESSFUL3 = "You can only sync show of the same type. " + + "For example: Sync show of type 'Movie' from search page with show of Type 'Movie'" + + " found in local internal storage."; + private Index toSync; + + public SyncCommand(Index toSync) { + requireNonNull(toSync); + this.toSync = toSync; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List searchResultList = model.getSearchResultList(); + List unWatchedList = model.getUnWatchedShowList(); + if (toSync.getZeroBased() >= searchResultList.size()) { + throw new CommandException(Messages.MESSAGE_SYNC_INVALID_INDEX); + } + Show fromImdb = searchResultList.get(toSync.getZeroBased()); + Name name = new Name(fromImdb.getName().toString().toLowerCase()); + String fromImdbType = fromImdb.getType(); + boolean matchingShowName = false; + int matchingIndex = -1; + for (int i = 0; i < unWatchedList.size(); i++) { + Name nameFromUnWatched = new Name(unWatchedList.get(i).getName().toString().toLowerCase()); + if (name.equals(nameFromUnWatched)) { + if (unWatchedList.get(i).getType().equals(fromImdbType)) { + matchingShowName = true; + matchingIndex = i; + break; + } + } + } + if (matchingShowName) { + Show fromUnWatchedList = unWatchedList.get(matchingIndex); + model.setShow(fromUnWatchedList, fromImdb); + return new CommandResult(String.format(MESSAGE_SUCCESS, fromImdb), true); + } else { + throw new CommandException(MESSAGE_UNSUCCESSFUL + " " + MESSAGE_UNSUCCESFUL2 + + "\n" + MESSAGE_UNSUCCESSFUL3); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SyncCommand // instanceof handles nulls + && toSync.equals(((SyncCommand) other).toSync)); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/logic/commands/WatchCommand.java b/src/main/java/seedu/ezwatchlist/logic/commands/WatchCommand.java new file mode 100644 index 00000000000..1eb28ebe1e6 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/commands/WatchCommand.java @@ -0,0 +1,471 @@ +package seedu.ezwatchlist.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NUM_OF_EPISODES; +import static seedu.ezwatchlist.model.Model.PREDICATE_ALL_SHOWS; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvSeason; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Marks an existing show in the watchlist as watched. + */ +public class WatchCommand extends Command { + + public static final String COMMAND_WORD = "watch"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks an existing show in the watchlist as watched " + + "by the index number used in the displayed show list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "[" + PREFIX_NUM_OF_EPISODES + "NUMBER OF EPISODES] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_NUM_OF_EPISODES + "12 "; + + public static final String MESSAGE_WATCH_SHOW_SUCCESS = "Marked show as watched: %1$s"; + public static final String MESSAGE_UNWATCH_SHOW_SUCCESS = "Unmarked show as watched: %1$s"; + public static final String MESSAGE_MARK_EPISODES_SUCCESS = "Marked %1$s episodes as watched: %2$s"; + public static final String MESSAGE_DUPLICATE_SHOW = "This show already exists in the watchlist."; + public static final String MESSAGE_EDITING_MOVIE_EPISODES_OR_SEASONS = "Movies do not have episodes and seasons."; + public static final String MESSAGE_INVALID_EPISODE_NUMBER = "The provided number of episodes is too large, there" + + " are only %1$s episode(s) in %2$s."; + public static final String MESSAGE_INVALID_SEASON_NUMBER = "The provided number of seasons is too large, there are" + + " only %1$s season(s) in %2$s."; + public static final String MESSAGE_INVALID_EPISODE_NUMBER_OF_SEASON = "Season %1$s of %2$s only has" + + " %3$s episode(s)."; + + private final Index index; + private final WatchShowDescriptor watchShowDescriptor; + private final boolean isToggle; + private final boolean seasonsArePresent; + private final boolean episodesArePresent; + + /** + * @param index of the show in the filtered show list to edit + * @param watchShowDescriptor details to edit the show with + */ + public WatchCommand(Index index, WatchShowDescriptor watchShowDescriptor, + boolean seasonsArePresent, boolean episodesArePresent) { + requireNonNull(index); + requireNonNull(watchShowDescriptor); + requireNonNull(seasonsArePresent); + requireNonNull(episodesArePresent); + + this.index = index; + this.watchShowDescriptor = new WatchShowDescriptor(watchShowDescriptor); + this.seasonsArePresent = seasonsArePresent; + this.episodesArePresent = episodesArePresent; + this.isToggle = (!seasonsArePresent && !episodesArePresent); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredShowList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_SHOW_DISPLAYED_INDEX); + } + + Show showToEdit = lastShownList.get(index.getZeroBased()); + Show editedShow = createEditedShow(showToEdit, watchShowDescriptor, isToggle, + seasonsArePresent, episodesArePresent); + + if (!showToEdit.isSameShow(editedShow) && model.hasShow(editedShow)) { + throw new CommandException(MESSAGE_DUPLICATE_SHOW); + } + + if (showToEdit.getType().equals("Movie") && !isToggle) { + throw new CommandException(MESSAGE_EDITING_MOVIE_EPISODES_OR_SEASONS); + } + + model.setShow(showToEdit, editedShow); + model.updateFilteredShowList(PREDICATE_ALL_SHOWS); + + boolean isWatched = editedShow.isWatched().value; + + if (editedShow.getType().equals("Movie") || isToggle) { + if (isWatched) { + return new CommandResult(String.format(MESSAGE_WATCH_SHOW_SUCCESS, editedShow), false); + } else { + return new CommandResult(String.format(MESSAGE_UNWATCH_SHOW_SUCCESS, editedShow), false); + } + } else { + return new CommandResult(String.format( + MESSAGE_MARK_EPISODES_SUCCESS, editedShow.getNumOfEpisodesWatched(), editedShow), false); + } + } + + /** + * Creates and returns a {@code Show} with the details of {@code showToEdit} + * edited with {@code editShowDescriptor}. + */ + private Show createEditedShow(Show showToEdit, WatchShowDescriptor watchShowDescriptor, boolean isToggle, + boolean seasonsArePresent, boolean episodesArePresent) throws CommandException { + assert showToEdit != null; + + Name name = showToEdit.getName(); + Date dateOfRelease = showToEdit.getDateOfRelease(); + Description description = showToEdit.getDescription(); + RunningTime runningTime = showToEdit.getRunningTime(); + Set actors = showToEdit.getActors(); + Poster poster = showToEdit.getPoster(); + int totalNumOfEpisodes = showToEdit.getTotalNumOfEpisodes(); + List seasons = showToEdit.getTvSeasons(); + Set genres = showToEdit.getGenres(); + + if (showToEdit.getType().equals("Movie")) { + + IsWatched updatedIsWatched = new IsWatched(Boolean.toString(!showToEdit.isWatched().value)); + Movie editedShow = new Movie(name, description, updatedIsWatched, dateOfRelease, runningTime, actors); + editedShow.setPoster(poster); + editedShow.addGenres(genres); + + return editedShow; + } else { // show is a tv show + + int numOfEpisodesWatched = watchShowDescriptor.getNumOfEpisodesWatched(); + int numOfSeasonsWatched = watchShowDescriptor.getNumOfSeasonsWatched(); + + IsWatched updatedIsWatched = showToEdit.isWatched(); + + if (isToggle) { + updatedIsWatched = new IsWatched(Boolean.toString(!showToEdit.isWatched().value)); + } + + checkValidityOfArguments(showToEdit, numOfEpisodesWatched, numOfSeasonsWatched); + + if (seasonsArePresent && episodesArePresent) { + numOfEpisodesWatched = calcEpisodesWatched(showToEdit, numOfSeasonsWatched, numOfEpisodesWatched); + } else if (seasonsArePresent) { + numOfEpisodesWatched = calcEpisodesWatched(showToEdit, numOfSeasonsWatched); + } + + checkIfValidNumOfEpisodesWatched(showToEdit, numOfEpisodesWatched, totalNumOfEpisodes); + + if (isToggle) { + if (updatedIsWatched.value) { + numOfEpisodesWatched = totalNumOfEpisodes; + } else { + numOfEpisodesWatched = 0; + } + } else { + if (numOfEpisodesWatched == totalNumOfEpisodes) { + updatedIsWatched = new IsWatched("true"); + } else { + updatedIsWatched = new IsWatched("false"); + } + } + + TvShow editedShow = new TvShow(name, description, updatedIsWatched, dateOfRelease, runningTime, + actors, numOfEpisodesWatched, totalNumOfEpisodes, seasons); + editedShow.setPoster(poster); + editedShow.addGenres(genres); + + return editedShow; + } + } + + /** + * Checks the validity of {@code numOfEpisodesWatched} and {@code numOfSeasonsWatched} user inputs. + * @param showToEdit show that is being edited. + * @param numOfEpisodesWatched number of episodes given by user. + * @param numOfSeasonsWatched number of seasons given by user. + * @throws CommandException if there is any invalid input. + */ + private void checkValidityOfArguments(Show showToEdit, int numOfEpisodesWatched, int numOfSeasonsWatched) + throws CommandException { + if (seasonsArePresent && !isValidSeasonNumber(showToEdit, numOfSeasonsWatched)) { + throw new CommandException(String.format(MESSAGE_INVALID_SEASON_NUMBER, + showToEdit.getNumOfSeasons(), showToEdit.getName())); + } + if (seasonsArePresent && episodesArePresent + && !isValidEpisodeNumberOfSeason(showToEdit, numOfEpisodesWatched, numOfSeasonsWatched)) { + throw new CommandException(String.format(MESSAGE_INVALID_EPISODE_NUMBER_OF_SEASON, + showToEdit.getTvSeasons().get(numOfSeasonsWatched - 1).getSeasonNum(), + showToEdit.getName(), + showToEdit.getNumOfEpisodesOfSeason(numOfSeasonsWatched))); + } + } + + /** + * Checks the validity of {@code numOfEpisodesWatched} and {@code numOfSeasonsWatched} user inputs. + * @param showToEdit show that is being edited. + * @param numOfEpisodesWatched number of episodes given by user. + * @param totalNumOfEpisodes total number of episodes. + * @throws CommandException if there is any invalid input. + */ + private void checkIfValidNumOfEpisodesWatched(Show showToEdit, int numOfEpisodesWatched, int totalNumOfEpisodes) + throws CommandException { + if (numOfEpisodesWatched > totalNumOfEpisodes) { + throw new CommandException(String.format(MESSAGE_INVALID_EPISODE_NUMBER, + totalNumOfEpisodes, showToEdit.getName())); + } + } + + /** + * Calculates the total number of episodes watched given and the season + * number and number of episodes watched in that particular season. + * @param show The show to be edited. + * @param numOfSeasons the number of seasons provided by the user. + * @param numOfEpisodes the number of episodes of the season provided by the user. + * @return the total number of episodes watched. + */ + private int calcEpisodesWatched(Show show, int numOfSeasons, int numOfEpisodes) { + int numOfEpisodesWatched = 0; + for (int seasonNum = 1; seasonNum < numOfSeasons; seasonNum++) { + numOfEpisodesWatched += show.getTvSeasons().get(seasonNum - 1).getTotalNumOfEpisodes(); + } + numOfEpisodesWatched += numOfEpisodes; + return numOfEpisodesWatched; + } + + /** + * Calculates the total number of episodes watched given the number of seasons watched. + * @param show The show to be edited. + * @param numOfSeasons the number of seasons provided by the user. + * @return the total number of episodes watched. + */ + private int calcEpisodesWatched(Show show, int numOfSeasons) { + int numOfEpisodesWatched = 0; + for (int seasonNum = 1; seasonNum <= numOfSeasons; seasonNum++) { + numOfEpisodesWatched += show.getNumOfEpisodesOfSeason(seasonNum); + } + return numOfEpisodesWatched; + } + + private boolean isValidSeasonNumber(Show showToEdit, int seasonNum) { + return showToEdit.getType().equals("Tv Show") + && (seasonNum <= showToEdit.getNumOfSeasons()); + } + + private boolean isValidEpisodeNumberOfSeason(Show showToEdit, int episodeNum, int seasonNum) { + return showToEdit.getType().equals("Tv Show") + && episodeNum <= showToEdit.getTvSeasons().get(seasonNum - 1).getTotalNumOfEpisodes(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof WatchCommand)) { + return false; + } + + // state check + WatchCommand e = (WatchCommand) other; + return index.equals(e.index) + && watchShowDescriptor.equals(e.watchShowDescriptor); + } + + /** + * Stores the details to edit the show with. Each non-empty field value will replace the + * corresponding field value of the show. + */ + public static class WatchShowDescriptor { + private Name name; + private String type; + private Date dateOfRelease; + private IsWatched isWatched; + private Description description; + private RunningTime runningTime; + private Set actors; + private Poster poster; + private Set genres; + private int numOfEpisodesWatched; + private int numOfSeasonsWatched; + private int totalNumOfEpisodes; + private List seasons; + + public WatchShowDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code actors} is used internally. + */ + public WatchShowDescriptor(WatchShowDescriptor toCopy) { + setName(toCopy.name); + setType(toCopy.type); + setDateOfRelease(toCopy.dateOfRelease); + setIsWatched(toCopy.isWatched); + setDescription(toCopy.description); + setRunningTime(toCopy.runningTime); + setActors(toCopy.actors); + setPoster(toCopy.poster); + setGenres(toCopy.genres); + setNumOfEpisodesWatched(toCopy.numOfEpisodesWatched); + setNumOfSeasonsWatched(toCopy.numOfSeasonsWatched); + setTotalNumOfEpisodes(toCopy.totalNumOfEpisodes); + setSeasons(toCopy.seasons); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setType(String type) { + this.type = type; + } + + public Optional getType() { + return Optional.ofNullable(type); + } + + public void setDateOfRelease(Date dateOfRelease) { + this.dateOfRelease = dateOfRelease; + } + + public Optional getDateOfRelease() { + return Optional.ofNullable(dateOfRelease); + } + + public void setIsWatched(IsWatched isWatched) { + this.isWatched = isWatched; + } + + public Optional getIsWatched() { + return Optional.ofNullable(isWatched); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setRunningTime(RunningTime runningTime) { + this.runningTime = runningTime; + } + + public Optional getRunningTime() { + return Optional.ofNullable(runningTime); + } + + public void setPoster(Poster poster) { + this.poster = poster; + } + + public Optional getPoster() { + return Optional.ofNullable(poster); + } + + /** + * Sets {@code genres} to this object's {@code genres}. + * A defensive copy of {@code genres} is used internally. + */ + public void setGenres(Set genres) { + this.genres = (genres != null) ? new HashSet<>(genres) : null; + } + + /** + * Returns an unmodifiable genre set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code genre} is null. + */ + public Optional> getGenres() { + return (genres != null) ? Optional.of(Collections.unmodifiableSet(genres)) : Optional.empty(); + } + + public void setNumOfEpisodesWatched(int numOfEpisodesWatched) { + this.numOfEpisodesWatched = numOfEpisodesWatched; + } + + public int getNumOfEpisodesWatched() { + return numOfEpisodesWatched; + } + + public void setTotalNumOfEpisodes(int totalNumOfEpisodes) { + this.totalNumOfEpisodes = totalNumOfEpisodes; + } + + public void setNumOfSeasonsWatched(int numOfSeasonsWatched) { + this.numOfSeasonsWatched = numOfSeasonsWatched; + } + + public int getNumOfSeasonsWatched() { + return numOfSeasonsWatched; + } + + /** + * Sets {@code actors} to this object's {@code actors}. + * A defensive copy of {@code actors} is used internally. + */ + public void setActors(Set actors) { + this.actors = (actors != null) ? new HashSet<>(actors) : null; + } + + /** + * Returns an unmodifiable actor set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code actor} is null. + */ + public Optional> getActors() { + return (actors != null) ? Optional.of(Collections.unmodifiableSet(actors)) : Optional.empty(); + } + + /** + * Sets {@code seasons} to this object's {@code seasons}. + * A defensive copy of {@code seasons} is used internally. + */ + public void setSeasons(List seasons) { + this.seasons = (seasons != null) ? new ArrayList<>(seasons) : null; + } + + /** + * Returns an unmodifiable season set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code season} is null. + */ + public Optional> getSeasons() { + return (seasons != null) ? Optional.of(Collections.unmodifiableList(seasons)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof WatchShowDescriptor)) { + return false; + } + + // state check + WatchShowDescriptor e = (WatchShowDescriptor) other; + return getNumOfEpisodesWatched() == e.getNumOfEpisodesWatched() + && getNumOfSeasonsWatched() == e.getNumOfSeasonsWatched(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/ezwatchlist/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/ezwatchlist/logic/commands/exceptions/CommandException.java index a16bd14f2cd..ad791e1ad73 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/ezwatchlist/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.ezwatchlist.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/AddCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/AddCommandParser.java new file mode 100644 index 00000000000..370947ec165 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/AddCommandParser.java @@ -0,0 +1,111 @@ +package seedu.ezwatchlist.logic.parser; + +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_ACTOR; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DATE_OF_RELEASE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_IS_WATCHED; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_RUNNING_TIME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.ezwatchlist.commons.core.messages.Messages; + +import seedu.ezwatchlist.logic.commands.AddCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * 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, String currentPanel) throws ParseException { + if (currentPanel.equals("search-list")) { + int index = ParserUtil.parseAddIndex(args); + return new AddCommand(index); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TYPE, PREFIX_DATE_OF_RELEASE, PREFIX_IS_WATCHED, + PREFIX_DESCRIPTION, PREFIX_RUNNING_TIME, PREFIX_ACTOR); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_TYPE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + String type = ParserUtil.parseType(argMultimap.getValue(PREFIX_TYPE).get()); + + Date dateOfRelease; + IsWatched isWatched; + Description description; + RunningTime runningTime; + Set actorList = new HashSet<>(); + + if (argMultimap.getValue(PREFIX_DATE_OF_RELEASE).isPresent()) { + String getDateFromUserInput = argMultimap.getValue(PREFIX_DATE_OF_RELEASE).get(); + dateOfRelease = ParserUtil.parseDateAddEditCommand(getDateFromUserInput); + } else { + dateOfRelease = new Date(); + } + + if (argMultimap.getValue(PREFIX_IS_WATCHED).isPresent()) { + isWatched = ParserUtil.parseIsWatched(argMultimap.getValue(PREFIX_IS_WATCHED).get()); + } else { + isWatched = new IsWatched("false"); + } + + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + } else { + description = new Description(null); + } + + if (argMultimap.getValue(PREFIX_RUNNING_TIME).isPresent()) { + runningTime = ParserUtil.parseRunningTime(argMultimap.getValue(PREFIX_RUNNING_TIME).get()); + } else { + runningTime = new RunningTime(); + } + + if (argMultimap.getValue(PREFIX_ACTOR).isPresent()) { + actorList = ParserUtil.parseActors(argMultimap.getAllValues(PREFIX_ACTOR)); + } + + + if (type.equals("movie")) { + Movie movie = new Movie(name, description, isWatched, dateOfRelease, runningTime, actorList); + return new AddCommand(movie); + } else { // show type is "tv" + TvShow tvShow = new TvShow(name, description, isWatched, dateOfRelease, runningTime, actorList, + 0, 0, new ArrayList<>()); + return new AddCommand(tvShow); + } + } + + /** + * 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/ArgumentMultimap.java b/src/main/java/seedu/ezwatchlist/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/ezwatchlist/logic/parser/ArgumentMultimap.java index 954c8e18f8e..5b94efb6026 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/ezwatchlist/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.ezwatchlist.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/ezwatchlist/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/ezwatchlist/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..cc74f63b4c0 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/ezwatchlist/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.ezwatchlist.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/CliSyntax.java b/src/main/java/seedu/ezwatchlist/logic/parser/CliSyntax.java new file mode 100644 index 00000000000..05b32072225 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/CliSyntax.java @@ -0,0 +1,20 @@ +package seedu.ezwatchlist.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_ACTOR = new Prefix("a/"); + public static final Prefix PREFIX_DATE_OF_RELEASE = new Prefix("d/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("s/"); + public static final Prefix PREFIX_FROM_ONLINE = new Prefix("o/"); + public static final Prefix PREFIX_GENRE = new Prefix("g/"); + public static final Prefix PREFIX_IS_WATCHED = new Prefix("w/"); + public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_NUM_OF_EPISODES = new Prefix("e/"); + public static final Prefix PREFIX_NUM_OF_SEASONS = new Prefix("s/"); + public static final Prefix PREFIX_RUNNING_TIME = new Prefix("r/"); + public static final Prefix PREFIX_TYPE = new Prefix("t/"); +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/DeleteCommandParser.java new file mode 100644 index 00000000000..114600636e3 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/DeleteCommandParser.java @@ -0,0 +1,34 @@ +package seedu.ezwatchlist.logic.parser; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.DeleteCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.ui.MainWindow; + +/** + * 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 currentPanel) throws ParseException { + + if (currentPanel.equals(MainWindow.SEARCH_TAB) || currentPanel.equals(MainWindow.STATISTICS_TAB)) { + throw new ParseException(Messages.MESSAGE_INVALID_COMMAND); + } + + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/EditCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/EditCommandParser.java new file mode 100644 index 00000000000..c0a5bb6df47 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/EditCommandParser.java @@ -0,0 +1,96 @@ +package seedu.ezwatchlist.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_ACTOR; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DATE_OF_RELEASE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_IS_WATCHED; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_RUNNING_TIME; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.EditCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.ui.MainWindow; + +/** + * 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, String currentPanel) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE_OF_RELEASE, PREFIX_DESCRIPTION, + PREFIX_IS_WATCHED, PREFIX_RUNNING_TIME, PREFIX_ACTOR); + + Index index; + + if (currentPanel.equals(MainWindow.SEARCH_TAB) || currentPanel.equals(MainWindow.STATISTICS_TAB)) { + throw new ParseException(Messages.MESSAGE_INVALID_COMMAND); + } + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format( + Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + } + + EditCommand.EditShowDescriptor editShowDescriptor = new EditCommand.EditShowDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editShowDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + + if (argMultimap.getValue(PREFIX_DATE_OF_RELEASE).isPresent()) { + String getDateFromUserInput = argMultimap.getValue(PREFIX_DATE_OF_RELEASE).get(); + editShowDescriptor.setDateOfRelease(ParserUtil.parseDateAddEditCommand(getDateFromUserInput)); + } + if (argMultimap.getValue(PREFIX_IS_WATCHED).isPresent()) { + editShowDescriptor.setIsWatched(ParserUtil.parseIsWatched(argMultimap.getValue(PREFIX_IS_WATCHED).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editShowDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_RUNNING_TIME).isPresent()) { + editShowDescriptor.setRunningTime( + ParserUtil.parseRunningTime(argMultimap.getValue(PREFIX_RUNNING_TIME).get())); + } + parseActorsForEdit(argMultimap.getAllValues(PREFIX_ACTOR)).ifPresent(editShowDescriptor::setActors); + + if (!editShowDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editShowDescriptor); + } + + /** + * Parses {@code Collection actors} into a {@code Set} if {@code actors} is non-empty. + * If {@code actors} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero actors. + */ + private Optional> parseActorsForEdit(Collection actors) throws ParseException { + assert actors != null; + + if (actors.isEmpty()) { + return Optional.empty(); + } + Collection actorSet = actors.size() == 1 && actors.contains("") ? Collections.emptySet() : actors; + return Optional.of(ParserUtil.parseActors(actorSet)); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/GoToParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/GoToParser.java new file mode 100644 index 00000000000..0f042689cfb --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/GoToParser.java @@ -0,0 +1,60 @@ +package seedu.ezwatchlist.logic.parser; + +import seedu.ezwatchlist.logic.commands.GoToCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new GoToParser object + */ +public class GoToParser implements Parser { + + //userinput is "search" for example + @Override + public GoToCommand parse(String args, String currentPanel) throws ParseException { + String goTo = args.toLowerCase(); + if (goToEqualCurrentPanel(args, currentPanel)) { + + } + if (goTo.equals("watchlist") || args.equals("1")) { + return new GoToCommand("1"); + } + if (goTo.equals("watched") || args.equals("2")) { + return new GoToCommand("2"); + } + if (goTo.equals("search") || args.equals("3")) { + return new GoToCommand("3"); + } + if (goTo.equals("statistics") || args.equals("4")) { + return new GoToCommand("4"); + } + throw new ParseException(GoToCommand.MESSAGE_UNSUCCESSFUL_INPUT); + } + + /** + * goToEqualCurrentPanel checks if user input is same as the name of current panel user is currently at. + * @param args + * @param currentPanel + * @return + */ + public boolean goToEqualCurrentPanel(String args, String currentPanel) { + String goTo = args.toLowerCase(); + switch (currentPanel) { + case "watch-list": + return goTo.equals("watchlist"); + + case "watched-list": + return goTo.equals("watched"); + + case "search-list": + return goTo.equals("search"); + + case "statistics tab": + return goTo.equals("statistics"); + + default: + return false; + } + + } + +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/ezwatchlist/logic/parser/Parser.java similarity index 50% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/ezwatchlist/logic/parser/Parser.java index d6551ad8e3f..ab56b1cacf3 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/ezwatchlist/logic/parser/Parser.java @@ -1,7 +1,8 @@ -package seedu.address.logic.parser; +package seedu.ezwatchlist.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.logic.commands.Command; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. @@ -12,5 +13,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 userInput, String currentPanel) throws ParseException, OnlineConnectionException; } diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/ParserUtil.java b/src/main/java/seedu/ezwatchlist/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..e6ebfb96a7e --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/ParserUtil.java @@ -0,0 +1,263 @@ +package seedu.ezwatchlist.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.util.StringUtil; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Episode; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.RunningTime; + +/** + * Contains utility methods used for parsing strings in the various *Parser classes. + */ +public class ParserUtil { + + public static final String MESSAGE_INVALID_TYPE = "Type can only be 'movie' or 'tv'."; + public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_NUM_OF_EPISODES = "Number of episodes is an unsigned integer."; + public static final String MESSAGE_INVALID_NUM_OF_SEASONS = "Number of seasons is a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX2 = "Index cannot be equal or less than 0, or " + + "larger than Java Max Value"; + public static final String MESSAGE_INVALID_DATE_FORMAT = "Invalid date format. Must be 'dd/MM/yyyy'."; + + + /** + * 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 type} into a {@code type}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code type} is invalid. + */ + public static String parseType(String type) throws ParseException { + requireNonNull(type); + String trimmedType = type.trim().toLowerCase(); + if (!trimmedType.equals("movie") && !trimmedType.equals("tv")) { + throw new ParseException(MESSAGE_INVALID_TYPE); + } + return trimmedType; + } + + /** + * Parses a {@code String date} into a {@code date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static Date parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + if (!Date.isValidDate(trimmedDate)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS); + } + String[] dateArr = trimmedDate.split("/"); + String newDate = dateArr[2] + "-" + dateArr[1] + "-" + dateArr[0]; + Date resultDate = new Date(newDate); + return resultDate; + } + + /** + * Parses a {@code String date} into a {@code date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static Date parseDateAddEditCommand(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + dateFormat.setLenient(false); + java.util.Date date1 = dateFormat.parse(trimmedDate); + String output = dateFormat.format(date1); + return parseDate(output); + } catch (java.text.ParseException e) { + throw new ParseException(MESSAGE_INVALID_DATE_FORMAT); + } + } + + /** + * Parses a {@code String isWatched} into an {@code IsWatched}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code isWatched} is invalid. + */ + public static IsWatched parseIsWatched(String isWatched) throws ParseException { + requireNonNull(isWatched); + String trimmedIsWatched = isWatched.trim().toLowerCase(); + if (!IsWatched.isValidIsWatched(trimmedIsWatched)) { + throw new ParseException(IsWatched.MESSAGE_CONSTRAINTS); + } + return new IsWatched(trimmedIsWatched); + } + + /** + * Parses a {@code String description} into an {@code Description}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code description} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String runningTime} into an {@code RunningTIME}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code runningTime} is invalid. + */ + public static RunningTime parseRunningTime(String runningTime) throws ParseException { + requireNonNull(runningTime); + String trimmedRunningTime = runningTime.trim(); + try { + if (!RunningTime.isValidRunningTime(Integer.parseInt(trimmedRunningTime))) { + throw new ParseException(RunningTime.MESSAGE_CONSTRAINTS2); + } + } catch (NumberFormatException e) { + throw new ParseException(RunningTime.MESSAGE_CONSTRAINTS2); + } catch (ParseException e) { + throw e; + } + return new RunningTime(Integer.parseInt(trimmedRunningTime)); + } + + /** + * Parses a {@code String actor} into a {@code Actor}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code actor} is invalid. + */ + public static Actor parseActor(String actor) throws ParseException { + requireNonNull(actor); + String trimmedActor = actor.trim(); + if (!Actor.isValidActorName(trimmedActor)) { + throw new ParseException(Actor.MESSAGE_CONSTRAINTS); + } + return new Actor(trimmedActor); + } + + /** + * Parses {@code Collection actors} into a {@code Set}. + */ + public static Set parseActors(Collection actors) throws ParseException { + requireNonNull(actors); + final Set actorSet = new HashSet<>(); + for (String actorName : actors) { + actorSet.add(parseActor(actorName)); + } + return actorSet; + } + + /** + * Parses a {@code String numOfEpisodesWatched} into an {@code int}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code numOfEpisodesWatched} is invalid. + */ + public static int parseNumOfEpisodesWatched(String numOfEpisodesWatched) throws ParseException { + requireNonNull(numOfEpisodesWatched); + String trimmedNumOfEpisodesWatched = numOfEpisodesWatched.trim(); + int intNumberOfEpisodesWatched; + + try { + intNumberOfEpisodesWatched = Integer.parseInt(trimmedNumOfEpisodesWatched); + } catch (NumberFormatException e) { + throw new ParseException(MESSAGE_INVALID_NUM_OF_EPISODES); + } + + if (!Episode.isValidEpisodeNum(Integer.parseInt(numOfEpisodesWatched))) { + throw new ParseException(MESSAGE_INVALID_NUM_OF_EPISODES); + } + + return intNumberOfEpisodesWatched; + } + + /** + * Parses a {@code String numOfSeasonsWatched} into an {@code int}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code numOfSeasonsWatched} is invalid. + */ + public static int parseNumOfSeasonsWatched(String numOfSeasonsWatched) throws ParseException { + requireNonNull(numOfSeasonsWatched); + String trimmedNumOfSeasonsWatched = numOfSeasonsWatched.trim(); + int intNumberOfSeasonsWatched; + + try { + intNumberOfSeasonsWatched = Integer.parseInt(trimmedNumOfSeasonsWatched); + } catch (NumberFormatException e) { + throw new ParseException(MESSAGE_INVALID_NUM_OF_SEASONS); + } + + if (intNumberOfSeasonsWatched <= 0) { + throw new ParseException(MESSAGE_INVALID_NUM_OF_SEASONS); + } + + return intNumberOfSeasonsWatched; + } + + /** + * Parses a {@code String args} into an {@code int}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code args} is invalid. + */ + public static int parseAddIndex(String args) throws ParseException { + requireNonNull(args); + String trimmedargs = args.trim(); + int index; + + try { + index = Integer.parseInt(trimmedargs); + } catch (NumberFormatException e) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + if (index <= 0 || index > Integer.MAX_VALUE) { + throw new ParseException(MESSAGE_INVALID_INDEX2); + } + return index; + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/ezwatchlist/logic/parser/Prefix.java similarity index 90% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/ezwatchlist/logic/parser/Prefix.java index c859d5fa5db..0e553a5ed3c 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/ezwatchlist/logic/parser/Prefix.java @@ -1,8 +1,8 @@ -package seedu.address.logic.parser; +package seedu.ezwatchlist.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. - * E.g. 't/' in 'add James t/ friend'. + * E.g. 'n/' in 'add n/Avengers'. */ public class Prefix { private final String prefix; diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/SearchCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/SearchCommandParser.java new file mode 100644 index 00000000000..b5ba7e16bed --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/SearchCommandParser.java @@ -0,0 +1,233 @@ +package seedu.ezwatchlist.logic.parser; + +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_ACTOR; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_FROM_ONLINE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_GENRE; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_IS_WATCHED; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_TYPE; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import java.util.stream.Stream; + +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.commons.core.messages.SearchMessages; +import seedu.ezwatchlist.logic.commands.SearchCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.show.Type; + +/** + * Parses input arguments and creates a new SearchCommand object. + */ +public class SearchCommandParser implements Parser { + private static final String INPUT_TRUE = "true"; + private static final String INPUT_YES = "yes"; + private static final String INPUT_FALSE = "false"; + private static final String INPUT_NO = "no"; + + private HashMap> searchShowsHashMap = new HashMap<>(); + + /** + * Parses the given {@code String} of arguments in the context of the SearchCommand. + * and returns a SearchCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public SearchCommand parse(String args, String currentPanel) throws ParseException { + checkNoOtherPrefixPresent(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize( + args, PREFIX_NAME, PREFIX_TYPE, PREFIX_ACTOR, PREFIX_GENRE, PREFIX_IS_WATCHED, PREFIX_FROM_ONLINE); + + checkPrefixPresent(argMultimap); + + List nameList = argMultimap.getAllValues(PREFIX_NAME); + Optional typeOptional = argMultimap.getValue(PREFIX_TYPE); + List actorList = argMultimap.getAllValues(PREFIX_ACTOR); + List genreList = argMultimap.getAllValues(PREFIX_GENRE); + Optional isWatchedOptional = argMultimap.getValue(PREFIX_IS_WATCHED); + Optional fromOnlineOptional = argMultimap.getValue(PREFIX_FROM_ONLINE); + + if (emptyCompulsoryKeyword(nameList, actorList, genreList)) { + throw new ParseException("Make sure keyword(s) for n/, a/ or g/ is not empty.\n" + + SearchMessages.MESSAGE_USAGE); + } + + parseNameToBeSearched(nameList); + parseTypeToBeSearched(typeOptional); + parseActorToBeSearched(actorList); + parseGenreToBeSearched(genreList); + parseIsWatchedToBeSearched(isWatchedOptional); + parseFromOnlineToBeSearched(fromOnlineOptional); + + return new SearchCommand(searchShowsHashMap); + } + + /** + * Returns true if any of the prefixes for name, genre or actor is present + * in the given {@code ArgumentMultimap}. + * @throws ParseException if the user input does not conform the expected format. + */ + private void checkPrefixPresent(ArgumentMultimap argMultimap) throws ParseException { + if (!anyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_GENRE, PREFIX_ACTOR) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + SearchMessages.MESSAGE_USAGE)); + } + } + + /** + * Returns whether there is any compulsory keyword present. + * One of the following keyword needs to be present: show name, actor or genre. + * @return True if all compulsory keyword is empty. + */ + private boolean emptyCompulsoryKeyword(List nameList, List actorList, List genreList) { + if (nameList.isEmpty() && actorList.isEmpty() && genreList.isEmpty()) { + return true; + } + for (String name : nameList) { + if (name.isBlank()) { + return true; + } + } + for (String actor : actorList) { + if (actor.isBlank()) { + return true; + } + } + for (String genre : genreList) { + if (genre.isBlank()) { + return true; + } + } + return false; + } + + /** + * Returns true if any of the prefixes does not contain empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Checks if the user input the command correctly with the correct syntax. + * @param args User input to be checked for the correct syntax. + * @throws ParseException if the user input does not conform the expected format. + */ + private void checkNoOtherPrefixPresent(String args) throws ParseException { + String[] keywordsArray = args.split("/"); + int arrayLength = keywordsArray.length; + for (int i = 0; i < arrayLength - 1; i++) { + String s = keywordsArray[i]; + String[] sArray = s.split(" "); + int sLength = sArray.length; + String prefix = sArray[sLength - 1]; + if (!prefix.equals("n") && !prefix.equals("a") && !prefix.equals("g") && !prefix.equals("t") + && !prefix.equals("o") && !prefix.equals("w")) { + throw new ParseException("Invalid syntax.\n" + SearchMessages.MESSAGE_USAGE); + } + } + } + + /** + * Parses the names to be searched. + * @param nameList List of names to be searched. + */ + private void parseNameToBeSearched(List nameList) { + searchShowsHashMap.put(SearchKey.KEY_NAME, nameList); + } + + /** + * Parses the type to be searched. + * @param typeOptional Type to be searched. + */ + private void parseTypeToBeSearched(Optional typeOptional) throws ParseException { + ArrayList listOfType = new ArrayList(); + if (typeOptional.isPresent()) { + String type = typeOptional.get().toLowerCase(); + if (type.isBlank()) { + throw new ParseException("Make sure keyword for t/ is not empty." + + SearchMessages.MESSAGE_INVALID_TYPE_COMMAND); + } + String typeTrimmed = type.trim(); + if (!(typeTrimmed.equals(Type.MOVIE.getType()) || typeTrimmed.equals(Type.TV_SHOW.getType()))) { + throw new ParseException(SearchMessages.MESSAGE_INVALID_TYPE_COMMAND); + } + listOfType.add(typeTrimmed); + } + searchShowsHashMap.put(SearchKey.KEY_TYPE, listOfType); + } + + /** + * Parses the actors to be searched. + * @param actorList List of actors to be searched. + */ + private void parseActorToBeSearched(List actorList) { + searchShowsHashMap.put(SearchKey.KEY_ACTOR, actorList); + } + + /** + * Parses the genres to be searched. + * @param genreList List of genres to be searched. + */ + private void parseGenreToBeSearched(List genreList) { + searchShowsHashMap.put(SearchKey.KEY_GENRE, genreList); + } + + /** + * Parses whether the show is watched. + * @param isWatchedOptional True/Yes if is watched, else, False/No + */ + private void parseIsWatchedToBeSearched(Optional isWatchedOptional) throws ParseException { + ArrayList listOfIsWatched = new ArrayList(); + if (isWatchedOptional.isPresent()) { + String isWatched = isWatchedOptional.get().toLowerCase(); + if (isWatched.isBlank()) { + throw new ParseException("Make sure keyword for w/ is not empty." + + SearchMessages.MESSAGE_INVALID_IS_WATCHED_COMMAND); + } + String isWatchedTrimmed = isWatched.trim(); + if (!(isWatchedTrimmed.equals(INPUT_FALSE) || isWatchedTrimmed.equals(INPUT_NO) + || isWatchedTrimmed.equals(INPUT_TRUE) || isWatchedTrimmed.equals(INPUT_YES))) { + throw new ParseException(SearchMessages.MESSAGE_INVALID_IS_WATCHED_COMMAND); + } + listOfIsWatched.add(isWatchedTrimmed); + } + searchShowsHashMap.put(SearchKey.KEY_IS_WATCHED, listOfIsWatched); + } + + /** + * Parses whether the show searched should be from online. + * @param fromOnlineOptional True/Yes if is from online, else, False/No. + */ + private void parseFromOnlineToBeSearched(Optional fromOnlineOptional) throws ParseException { + ArrayList listOfFromOnline = new ArrayList(); + if (fromOnlineOptional.isPresent()) { + String fromOnline = fromOnlineOptional.get().toLowerCase(); + if (fromOnline.isBlank()) { + throw new ParseException("Make sure keyword for o/ is not empty." + + SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + String fromOnlineTrimmed = fromOnline.trim(); + if (!(fromOnlineTrimmed.equals(INPUT_FALSE) || fromOnlineTrimmed.equals(INPUT_NO) + || fromOnlineTrimmed.equals(INPUT_TRUE) || fromOnlineTrimmed.equals(INPUT_YES))) { + throw new ParseException(SearchMessages.MESSAGE_INVALID_FROM_ONLINE_COMMAND); + } + listOfFromOnline.add(fromOnlineTrimmed); + } + searchShowsHashMap.put(SearchKey.KEY_FROM_ONLINE, listOfFromOnline); + } + + /** + * Return the hash map of the shows to be watched based on the different category. + * @return Hash map of the shows to be watched based on the different category. + */ + public HashMap> getSearchShowsHashMap() { + return searchShowsHashMap; + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/SearchKey.java b/src/main/java/seedu/ezwatchlist/logic/parser/SearchKey.java new file mode 100644 index 00000000000..a6f16b08514 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/SearchKey.java @@ -0,0 +1,23 @@ +package seedu.ezwatchlist.logic.parser; + +/** + * Enumeration for the possible search key in a search command. + */ +public enum SearchKey { + KEY_NAME("name"), + KEY_TYPE("type"), + KEY_ACTOR("actor"), + KEY_GENRE("genre"), + KEY_IS_WATCHED("is_watched"), + KEY_FROM_ONLINE("from_online"); + + public final String key; + + SearchKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/SyncCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/SyncCommandParser.java new file mode 100644 index 00000000000..5ebfd666024 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/SyncCommandParser.java @@ -0,0 +1,30 @@ +package seedu.ezwatchlist.logic.parser; + +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.SyncCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SyncCommand object + */ +public class SyncCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an SyncCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public SyncCommand parse(String args, String currentPanel) throws ParseException, OnlineConnectionException { + try { + Index index = ParserUtil.parseIndex(args); + return new SyncCommand(index); + } catch (ParseException e) { + throw new ParseException( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, SyncCommand.MESSAGE_USAGE), e); + } + } + +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/WatchCommandParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/WatchCommandParser.java new file mode 100644 index 00000000000..586a941f056 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/WatchCommandParser.java @@ -0,0 +1,63 @@ +package seedu.ezwatchlist.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NUM_OF_EPISODES; +import static seedu.ezwatchlist.logic.parser.CliSyntax.PREFIX_NUM_OF_SEASONS; + +import seedu.ezwatchlist.commons.core.index.Index; +import seedu.ezwatchlist.commons.core.messages.Messages; +import seedu.ezwatchlist.logic.commands.WatchCommand; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.ui.MainWindow; + +/** + * Parses input arguments and creates a new WatchCommand object + */ +public class WatchCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the WatchCommand + * and returns a WatchCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public WatchCommand parse(String args, String currentPanel) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NUM_OF_EPISODES, PREFIX_NUM_OF_SEASONS); + + Index index; + + if (currentPanel.equals(MainWindow.SEARCH_TAB) || currentPanel.equals(MainWindow.STATISTICS_TAB)) { + throw new ParseException(Messages.MESSAGE_INVALID_COMMAND); + } + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, WatchCommand.MESSAGE_USAGE), pe); + } + + int numOfEpisodesWatched; + int numOfSeasonsWatched; + boolean seasonsArePresent = false; + boolean episodesArePresent = false; + WatchCommand.WatchShowDescriptor watchShowDescriptor = new WatchCommand.WatchShowDescriptor(); + + if (argMultimap.getValue(PREFIX_NUM_OF_SEASONS).isPresent()) { + numOfSeasonsWatched = + ParserUtil.parseNumOfSeasonsWatched(argMultimap.getValue(PREFIX_NUM_OF_SEASONS).get()); + watchShowDescriptor.setNumOfSeasonsWatched(numOfSeasonsWatched); + seasonsArePresent = true; + } + if (argMultimap.getValue(PREFIX_NUM_OF_EPISODES).isPresent()) { + numOfEpisodesWatched = + ParserUtil.parseNumOfEpisodesWatched(argMultimap.getValue(PREFIX_NUM_OF_EPISODES).get()); + watchShowDescriptor.setNumOfEpisodesWatched(numOfEpisodesWatched); + episodesArePresent = true; + } + + return new WatchCommand(index, watchShowDescriptor, seasonsArePresent, episodesArePresent); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/logic/parser/WatchListParser.java b/src/main/java/seedu/ezwatchlist/logic/parser/WatchListParser.java new file mode 100644 index 00000000000..b56dcc68653 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/logic/parser/WatchListParser.java @@ -0,0 +1,108 @@ +package seedu.ezwatchlist.logic.parser; + +import static seedu.ezwatchlist.commons.core.messages.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.ezwatchlist.commons.core.messages.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; + +import seedu.ezwatchlist.logic.commands.AddCommand; +import seedu.ezwatchlist.logic.commands.ClearCommand; +import seedu.ezwatchlist.logic.commands.Command; +import seedu.ezwatchlist.logic.commands.DeleteCommand; +import seedu.ezwatchlist.logic.commands.EditCommand; +import seedu.ezwatchlist.logic.commands.ExitCommand; +import seedu.ezwatchlist.logic.commands.HelpCommand; +import seedu.ezwatchlist.logic.commands.SearchCommand; +import seedu.ezwatchlist.logic.commands.SyncCommand; +import seedu.ezwatchlist.logic.commands.WatchCommand; + +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class WatchListParser { + + /** + * 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 + * @param currentTab + * @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, String currentTab) throws ParseException, OnlineConnectionException { + if (shortCutKey(userInput)) { + return new GoToParser().parse(userInput, currentTab); + } + + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord").toLowerCase(); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments, currentTab); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments, currentTab); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments, currentTab); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case WatchCommand.COMMAND_WORD: + return new WatchCommandParser().parse(arguments, currentTab); + + case SearchCommand.COMMAND_WORD: + return new SearchCommandParser().parse(arguments, currentTab); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case SyncCommand.COMMAND_WORD: + return new SyncCommandParser().parse(arguments, currentTab); + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + + /** + * Check if user input refers to a short cut key + * @param userInput + * @return check if is short cut key + */ + private boolean shortCutKey(String userInput) { + try { + int shortCutKey = Integer.parseInt(userInput); + boolean isShortCutKey = shortCutKey == 1 || shortCutKey == 2 || shortCutKey == 3 || shortCutKey == 4; + return isShortCutKey; + } catch (NumberFormatException e) { + String userInputLowerCase = userInput.toLowerCase(); + boolean stringShortCutKey = userInputLowerCase.equals("watchlist") + || userInputLowerCase.equals("watched") + || userInputLowerCase.equals("search") + || userInputLowerCase.equals("statistics"); + return stringShortCutKey; + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/ezwatchlist/logic/parser/exceptions/ParseException.java similarity index 71% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/ezwatchlist/logic/parser/exceptions/ParseException.java index 158a1a54c1c..81e247bfc04 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/ezwatchlist/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.ezwatchlist.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/ezwatchlist/model/Model.java b/src/main/java/seedu/ezwatchlist/model/Model.java new file mode 100644 index 00000000000..f89070a3f4f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/Model.java @@ -0,0 +1,210 @@ +package seedu.ezwatchlist.model; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.ezwatchlist.commons.core.GuiSettings; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Show; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_ALL_SHOWS = unused -> true; + + /** {@code Predicate} that evaluates to true when a show is not watched*/ + Predicate PREDICATE_UNWATCHED_SHOWS = show -> !show.isWatched().value; + + /** {@code Predicate} that evaluates to true when a show is watched */ + Predicate PREDICATE_WATCHED_SHOWS = show -> show.isWatched().value; + + /** {@code Predicate} that always evaluates to false */ + Predicate PREDICATE_NO_SHOWS = unused -> false; + + /** + * 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' watchlist file path. + */ + Path getWatchListFilePath(); + + /** + * Returns the user prefs' database file path. + */ + Path getDatabaseFilePath(); + + /** + * Sets the user prefs' watchlist file path. + */ + void setWatchListFilePath(Path watchListFilePath); + + /** + * Sets the user prefs' database file path. + */ + void setDatabaseFilePath(Path databaseFilePath); + + /** + * Replaces watchlist data with the data in {@code watchList}. + */ + void setWatchList(ReadOnlyWatchList watchList); + + /** + * Replaces database with the data in {@code database}. + */ + void setDatabase(ReadOnlyWatchList database); + + /** + * Returns the WatchList. + */ + ReadOnlyWatchList getWatchList(); + + /** + * Returns the database. + */ + ReadOnlyWatchList getDatabase(); + + /** + * Returns true if a show with the same identity as {@code show} exists in the watchlist. + * @param show The show to be checked. + * @return True if a same show exists in the watchlist. + */ + boolean hasShow(Show show); + + /** + * Returns true if a show with the same name as {@code showName} exists in the watchlist. + * @param showName The name of the show to be searched. + * @return True if a show with the same name exists in the watchlist. + */ + boolean hasShowName(Name showName); + + /** + * Returns the list of shows that has the same name as the given argument as the current watch list. + * @param showName The name of the show to be searched. + * @return The list of shows that has the same name as exists in the watchlist. + */ + List getShowFromWatchlistIfHasName(Name showName); + + /** + * Returns the list of shows that has the same name as the given argument as the offline database. + * @param showName The name of the show to be searched. + * @return The list of shows that has the same name as exists in the offline database. + */ + List getShowFromDatabaseIfHasName(Name showName); + + /** + * Returns true if a show with any of the actor in {@code actorSet} exists in the watchlist. + * @param actorSet The set of actors to be searched in the watchlist. + * @return True if a show with any actor in the {@code actorSet} exists in the watchlist. + */ + boolean hasActor(Set actorSet); + + /** + * Returns the list of shows from watchlist that has any of the actor in {@code actorSet}. + * @param actorSet The set of actors to be searched in the watchlist. + * @return The list of shows that has any of the actor in {@code actorSet}. + */ + List getShowFromWatchlistIfHasActor(Set actorSet); + + /** + * Returns the list of shows from database that has any of the actor in {@code actorSet}. + * @param actorSet The set of actors to be searched in the database. + * @return The list of shows that has any of the actor in {@code actorSet}. + */ + List getShowFromDatabaseIfHasActor(Set actorSet); + + /** + * Returns the list of shows in the watchlist that has any of the genre in {@code genreSet}. + * @param genreSet The set of genres to be searched in the watchlist. + * @return The list of shows that has any of the genre in {@code genreSet}. + */ + List getShowFromWatchlistIfIsGenre(Set genreSet); + + /** + * Returns the list of shows from the database that has any of the genre in {@code genreSet}. + * @param genreSet The set of genres to be searched in the database. + * @return The list of shows that has any of the genre in {@code genreSet}. + */ + List getShowFromDatabaseIfIsGenre(Set genreSet); + + /** + * Deletes the given show. + * The show must exist in the watchlist. + */ + void deleteShow(Show target); + + /** + * Adds the given show. + * {@code show} must not already exist in the watchlist. + */ + void addShow(Show show); + + /** + * Replaces the given show {@code target} with {@code editedShow}. + * {@code target} must exist in the watchlist. + * The show identity of {@code editedShow} must not be the same as another existing show in the watchlist. + */ + void setShow(Show target, Show editedShow); + + /** Returns an unmodifiable view of the filtered show list */ + ObservableList getFilteredShowList(); + + /** + * Updates the filter of the filtered show list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredShowList(Predicate predicate); + + /** Returns an unmodifiable view of the unwatched show list */ + ObservableList getUnWatchedShowList(); + + /** Returns an unmodifiable view of the watched show list */ + ObservableList getWatchedShowList(); + + /** + * Updates the unwatched show list to filter shows that have not been watched. + */ + void updateUnWatchedShowList(); + + /** + * Updates the watched show list to filter shows that have been watched. + */ + void updateWatchedShowList(); + + /** Returns an unmodifiable view of the search result list */ + ObservableList getSearchResultList(); + + /** + * Updates the filter of the filtered show list to filter by the given {@code searchResult}. + * @throws NullPointerException if the {@code searchResult} if null. + */ + void updateSearchResultList(List searchResult); + /** + * Returns a string representing the name of the page. + */ + String getPage(String shortCutKey); +} diff --git a/src/main/java/seedu/ezwatchlist/model/ModelManager.java b/src/main/java/seedu/ezwatchlist/model/ModelManager.java new file mode 100644 index 00000000000..07b30dfd5f8 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/ModelManager.java @@ -0,0 +1,285 @@ +package seedu.ezwatchlist.model; + +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.ezwatchlist.commons.core.GuiSettings; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.util.CollectionUtil; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Show; + +/** + * Represents the in-memory model of the watchlist data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final WatchList watchList; + private final WatchList database; + private final WatchList searchResult = new WatchList(); + private final FilteredList unWatchedList; + private final FilteredList watchedList; + private final UserPrefs userPrefs; + + private FilteredList filteredShows; + private final String[] pageResult = {"Watchlist", "Watchedlist", "Search", "Statistics"}; + + + /** + * Initializes a ModelManager with the given watchList and userPrefs. + */ + public ModelManager(ReadOnlyWatchList watchList, ReadOnlyWatchList database, ReadOnlyUserPrefs userPrefs) { + super(); + CollectionUtil.requireAllNonNull(watchList, userPrefs); + + logger.fine("Initializing with watchlist: " + watchList + ", database: " + database + " and user prefs " + + userPrefs); + + this.watchList = new WatchList(watchList); + this.database = new WatchList(database); + this.userPrefs = new UserPrefs(userPrefs); + + filteredShows = new FilteredList<>(this.watchList.getShowList()); + + unWatchedList = new FilteredList<>(this.watchList.getShowList()); + updateUnWatchedShowList(); + watchedList = new FilteredList<>(this.watchList.getShowList()); + updateWatchedShowList(); + } + + public ModelManager() { + this(new WatchList(), new WatchList(), 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 getWatchListFilePath() { + return userPrefs.getWatchListFilePath(); + } + + @Override + public void setWatchListFilePath(Path watchListFilePath) { + requireNonNull(watchListFilePath); + userPrefs.setWatchListFilePath(watchListFilePath); + } + + //=========== WatchList ================================================================================ + + @Override + public void setWatchList(ReadOnlyWatchList watchList) { + this.watchList.resetData(watchList); + } + + @Override + public ReadOnlyWatchList getWatchList() { + return watchList; + } + + @Override + public boolean hasShow(Show show) { + requireNonNull(show); + return watchList.hasShow(show); + } + + @Override + public boolean hasShowName(Name showName) { + requireNonNull(showName); + return watchList.hasName(showName); + } + + @Override + public List getShowFromWatchlistIfHasName(Name showName) { + requireNonNull(showName); + return watchList.getShowIfHasName(showName); + } + + @Override + public boolean hasActor(Set actorSet) { + requireNonNull(actorSet); + return watchList.hasActor(actorSet); + } + + @Override + public List getShowFromWatchlistIfHasActor(Set actorSet) { + requireNonNull(actorSet); + return watchList.getShowIfHasActor(actorSet); + } + + @Override + public List getShowFromWatchlistIfIsGenre(Set genreSet) { + requireNonNull(genreSet); + return watchList.getShowIfIsGenre(genreSet); + } + + @Override + public void deleteShow(Show target) { + watchList.removeShow(target); + } + + @Override + public void addShow(Show show) { + watchList.addShow(show); + } + + @Override + public void setShow(Show target, Show editedShow) { + CollectionUtil.requireAllNonNull(target, editedShow); + watchList.setShow(target, editedShow); + } + + @Override + public String getPage(String shortCutKey) { + int pageNumber = Integer.parseInt(shortCutKey) - 1; + return pageResult[pageNumber]; + } + + //=========== Filtered Show List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Show} backed by the internal list of + * {@code versionedWatchList} + */ + @Override + public ObservableList getFilteredShowList() { + return filteredShows; + } + + @Override + public void updateFilteredShowList(Predicate predicate) { + requireNonNull(predicate); + filteredShows.setPredicate(predicate); + } + + /** + * Returns an unmodifiable view of the watched list of {@code Show} backed by the internal list of + * {@code versionedWatchList} + */ + @Override + public ObservableList getUnWatchedShowList() { + return unWatchedList; + } + + /** + * Returns an unmodifiable view of the watched list of {@code Show} backed by the internal list of + * {@code versionedWatchList} + */ + @Override + public ObservableList getWatchedShowList() { + return watchedList; + } + + @Override + public void updateUnWatchedShowList() { + unWatchedList.setPredicate(Model.PREDICATE_UNWATCHED_SHOWS); + } + + @Override + public void updateWatchedShowList() { + watchedList.setPredicate(Model.PREDICATE_WATCHED_SHOWS); + } + + @Override + public void updateSearchResultList(List shows) { + searchResult.setShows(shows); + updateFilteredShowList(PREDICATE_ALL_SHOWS); + } + + public ObservableList getSearchResultList() { + return searchResult.getShowList(); + } + + //=========== Database ================================================================================ + @Override + public Path getDatabaseFilePath() { + return userPrefs.getDatabaseFilePath(); + } + + @Override + public void setDatabaseFilePath(Path databaseFilePath) { + requireNonNull(databaseFilePath); + userPrefs.setDatabaseFilePath(databaseFilePath); + } + + @Override + public void setDatabase(ReadOnlyWatchList database) { + this.database.resetData(database); + } + + @Override + public ReadOnlyWatchList getDatabase() { + return database; + } + + @Override + public List getShowFromDatabaseIfHasName(Name showName) { + requireNonNull(showName); + return database.getShowIfHasName(showName); + } + + @Override + public List getShowFromDatabaseIfHasActor(Set actorSet) { + requireNonNull(actorSet); + return database.getShowIfHasActor(actorSet); + } + + @Override + public List getShowFromDatabaseIfIsGenre(Set genreSet) { + requireNonNull(genreSet); + return database.getShowIfIsGenre(genreSet); + } + + @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 watchList.equals(other.watchList) + && watchedList.equals(other.watchedList) + && database.equals(other.database) + && userPrefs.equals(other.userPrefs) + && filteredShows.equals(other.filteredShows); + } + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/ezwatchlist/model/ReadOnlyUserPrefs.java similarity index 56% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/seedu/ezwatchlist/model/ReadOnlyUserPrefs.java index befd58a4c73..6a3dac248cd 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/ezwatchlist/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package seedu.ezwatchlist.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import seedu.ezwatchlist.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getWatchListFilePath(); } diff --git a/src/main/java/seedu/ezwatchlist/model/ReadOnlyWatchList.java b/src/main/java/seedu/ezwatchlist/model/ReadOnlyWatchList.java new file mode 100644 index 00000000000..a2bb529f7d3 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/ReadOnlyWatchList.java @@ -0,0 +1,16 @@ +package seedu.ezwatchlist.model; + +import javafx.collections.ObservableList; +import seedu.ezwatchlist.model.show.Show; + +/** + * Unmodifiable view of a watchlist + */ +public interface ReadOnlyWatchList { + + /** + * Returns an unmodifiable view of the watchlist. + * This list will not contain any duplicate shows. + */ + ObservableList getShowList(); +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/ezwatchlist/model/UserPrefs.java similarity index 61% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/ezwatchlist/model/UserPrefs.java index 25a5fd6eab9..ca27382fa48 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/ezwatchlist/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package seedu.ezwatchlist.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.ezwatchlist.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,8 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path watchListFilePath = Paths.get("data" , "watchlist.json"); + private Path databaseFilePath = Paths.get("data" , "database.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +36,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setWatchListFilePath(newUserPrefs.getWatchListFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +48,22 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getWatchListFilePath() { + return watchListFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public Path getDatabaseFilePath() { + return databaseFilePath; + } + + public void setWatchListFilePath(Path watchListFilePath) { + requireNonNull(watchListFilePath); + this.watchListFilePath = watchListFilePath; + } + + public void setDatabaseFilePath(Path databaseFilePath) { + requireNonNull(databaseFilePath); + this.databaseFilePath = databaseFilePath; } @Override @@ -68,19 +78,20 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && watchListFilePath.equals(o.watchListFilePath) + && databaseFilePath.equals(o.databaseFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, watchListFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + watchListFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/ezwatchlist/model/WatchList.java b/src/main/java/seedu/ezwatchlist/model/WatchList.java new file mode 100644 index 00000000000..88b48bf40f7 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/WatchList.java @@ -0,0 +1,176 @@ +package seedu.ezwatchlist.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Set; + +import javafx.collections.ObservableList; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.UniqueShowList; + +/** + * Wraps all data at the EzWatchlist level + * Duplicates are not allowed (by .isSameShow comparison) + */ +public class WatchList implements ReadOnlyWatchList { + + private final UniqueShowList shows; + + /* + * 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. + */ + { + shows = new UniqueShowList(); + } + + public WatchList() {} + + /** + * Creates an WatchList using the Shows in the {@code toBeCopied} + */ + public WatchList(ReadOnlyWatchList toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the watchlist with {@code shows}. + * {@code shows} must not contain duplicate shows. + */ + public void setShows(List shows) { + this.shows.setShows(shows); + } + + /** + * Resets the existing data of this {@code WatchList} with {@code newData}. + */ + public void resetData(ReadOnlyWatchList newData) { + requireNonNull(newData); + + setShows(newData.getShowList()); + } + + //// show-level operations + /** + * Returns true if a show with the same identity as {@code show} exists in the WatchList. + * @param show The show to be checked if contained in the unique watchlist. + * @return True if the show is contained in the unique watchlist. + */ + public boolean hasShow(Show show) { + requireNonNull(show); + return shows.contains(show); + } + + /** + * Returns true if a show with the same name as {@code name} exists in the WatchList. + * @param name The name of the show to be checked. + * @return True if the show with the same name as {@code name} exists in the WatchList. + */ + public boolean hasName(Name name) { + requireNonNull(name); + return shows.hasShowName(name); + } + + /** + * Returns the list of shows that has the same name as {@code showName} as the current watch list. + * @param showName The name of the show to be checked. + * @return The list of shows that has the same name as {@code showName} as the current watch list. + */ + public List getShowIfHasName(Name showName) { + requireNonNull(showName); + return shows.getShowIfHasName(showName); + } + + /** + * Returns true if a show with any of the actor as that in {@code actorSet} exists in the WatchList. + * @param actorSet The set of the actors to be checked. + * @return True if the show with any of the actor as that in {@code actorSet} exists in the WatchList. + */ + public boolean hasActor(Set actorSet) { + requireNonNull(actorSet); + return shows.hasActor(actorSet); + } + + /** + * Returns the list of shows that has the same name as {@code actorSet} as the current watch list. + * @param actorSet The set of the actors to be checked. + * @return The list of shows that has the same name as {@code actorSet} as the current watch list. + */ + public List getShowIfHasActor(Set actorSet) { + requireNonNull(actorSet); + return shows.getShowIfHasActor(actorSet); + } + + /** + * Returns the list of shows that has the same genre as {@code actorSet} as the current watch list. + * @param genreSet The set of the genres to be checked. + * @return The list of shows that has the same genre as {@code actorSet} as the current watch list. + */ + public List getShowIfIsGenre(Set genreSet) { + requireNonNull(genreSet); + return shows.getShowIfIsGenre(genreSet); + } + + /** + * Adds a show to the watchlist. + * The show must not already exist in the watchlist. + * @param s The show to be added. + */ + public void addShow(Show s) { + shows.add(s); + } + + /** + * Replaces the given show {@code target} in the list with {@code editedShow}. + * {@code target} must exist in the watchlist. + * The show identity of {@code editedShow} must not be the same as another existing show in the watchlist. + */ + public void setShow(Show target, Show editedShow) { + requireNonNull(editedShow); + + shows.setShow(target, editedShow); + } + + /** + * Removes {@code key} from this {@code WatchList}. + * {@code key} must exist in the watchlist. + */ + public void removeShow(Show key) { + shows.remove(key); + } + + //// util methods + + @Override + public String toString() { + return shows.asUnmodifiableObservableList().size() + " shows"; + // TODO: refine later + } + + @Override + public ObservableList getShowList() { + return shows.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof WatchList // instanceof handles nulls + && shows.equals(((WatchList) other).shows)); + } + + @Override + public int hashCode() { + return shows.hashCode(); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/actor/Actor.java b/src/main/java/seedu/ezwatchlist/model/actor/Actor.java new file mode 100644 index 00000000000..a71f13d172a --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/actor/Actor.java @@ -0,0 +1,62 @@ +package seedu.ezwatchlist.model.actor; + +import static java.util.Objects.isNull; + +/** + * Represents an Actor in a show in the watchlist. + * Guarantees: immutable; name is valid as declared in {@link #isValidActorName(String)} + */ +public class Actor { + public static final String MESSAGE_CONSTRAINTS = + "Actor names should only contain characters and spaces, and it should not be blank"; + + /* + * The first character of the actor name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String DEFAULT_VALUE = "n.a."; + + public final String actorName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public Actor(String name) { + if (isNull(name)) { + name = DEFAULT_VALUE; + } + actorName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidActorName(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getActorName() { + return actorName; + } + + @Override + public String toString() { + return actorName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Actor // instanceof handles nulls + && actorName.equals(((Actor) other).actorName)); // state check + } + + @Override + public int hashCode() { + return actorName.hashCode(); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Date.java b/src/main/java/seedu/ezwatchlist/model/show/Date.java new file mode 100644 index 00000000000..62345912aca --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Date.java @@ -0,0 +1,56 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.isNull; + +/** + * Represents a Show's date of release in the watchlist. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} + */ +public class Date { + public static final String MESSAGE_CONSTRAINTS = "Dates can take any values, and it should not be blank"; + public static final String DEFAULT_VALUE = "?"; + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + public Date() { + value = DEFAULT_VALUE; + } + + /** + * Constructs an {@code Date}. + * + * @param date A valid date. + */ + public Date(String date) { + //instead of having constraints perhaps could check if the object is null in the creation + if (isNull(date) || date.equals("")) { + date = DEFAULT_VALUE; + } + value = date; + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(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 Date // instanceof handles nulls + && value.equals(((Date) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Description.java b/src/main/java/seedu/ezwatchlist/model/show/Description.java new file mode 100644 index 00000000000..4153eb888ef --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Description.java @@ -0,0 +1,63 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; + +/** + * Represents a Show's description in the watchlist. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = "Description should not be blank"; + + public static final String DEFAULT_DESCRIPTION = "n.a."; + /* + * The first character of the show's description must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String fullDescription; + + public Description() { + fullDescription = ""; + } + + /** + * Constructs a {@code Name}. + * + * @param description A valid description. + */ + public Description(String description) { + if (isNull(description) || description.isEmpty()) { + description = DEFAULT_DESCRIPTION; + } + requireNonNull(description); + fullDescription = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullDescription; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Description // instanceof handles nulls + && fullDescription.equals(((Description) other).fullDescription)); // state check + } + + @Override + public int hashCode() { + return fullDescription.hashCode(); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Episode.java b/src/main/java/seedu/ezwatchlist/model/show/Episode.java new file mode 100644 index 00000000000..6bbad7978dc --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Episode.java @@ -0,0 +1,48 @@ +package seedu.ezwatchlist.model.show; + +/** + * Represents an Episode of a TvShow in the watchlist. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Episode { + public static final String MESSAGE_CONSTRAINTS_EPISODE_NAME = + "Episode names should only contain characters and spaces, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS_EPISODE_NUMBER = + "Episode numbers should only be integers greater than 0, and it should not be blank"; + + /* + * The first character of the episode name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + private final String episodeName; + private final int episodeNum; + + public Episode(String episodeName, int episodeNum) { + this.episodeName = episodeName; + this.episodeNum = episodeNum; + } + + public String getEpisodeName() { + return episodeName; + } + + public int getEpisodeNum() { + return episodeNum; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidEpisodeName(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given integer is a valid episode number. + */ + public static boolean isValidEpisodeNum(int test) { + return test > 0; + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Genre.java b/src/main/java/seedu/ezwatchlist/model/show/Genre.java new file mode 100644 index 00000000000..284a7a6d098 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Genre.java @@ -0,0 +1,62 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.isNull; + +/** + * Represents an Genre of a show in the watchlist. + * Guarantees: immutable; name is valid as declared in {@link #isValidGenreName(String)} + */ +public class Genre { + public static final String MESSAGE_CONSTRAINTS = + "Genre names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the genre name must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String DEFAULT_VALUE = "n.a."; + + public final String genreName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public Genre(String name) { + if (isNull(name)) { + name = DEFAULT_VALUE; + } + genreName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidGenreName(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getGenreName() { + return genreName; + } + + @Override + public String toString() { + return genreName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Genre // instanceof handles nulls + && genreName.equals(((Genre) other).genreName)); // state check + } + + @Override + public int hashCode() { + return genreName.hashCode(); + } +} + diff --git a/src/main/java/seedu/ezwatchlist/model/show/IsWatched.java b/src/main/java/seedu/ezwatchlist/model/show/IsWatched.java new file mode 100644 index 00000000000..96e32da3718 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/IsWatched.java @@ -0,0 +1,66 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.commons.util.AppUtil.checkArgument; + +/** + * Represents a Show's watched or not field in the watchlist. + * Guarantees: is valid as declared in {@link #isValidIsWatched(String)} + */ +public class IsWatched { + + public static final String MESSAGE_CONSTRAINTS = + "The watched value of a show can only be 'true' or 'false' (case in-sensitive)"; + + /* + * The first character of the show must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final boolean value; + + public IsWatched() { + value = false; + } + + /** + * Constructs a {@code Name}. + * + * @param isWatched A valid boolean. + */ + public IsWatched(String isWatched) { + requireNonNull(isWatched); + checkArgument(isValidIsWatched(isWatched), MESSAGE_CONSTRAINTS); + value = Boolean.parseBoolean(isWatched); + } + + /** + * Returns true if a given String is a valid isWatched. + */ + public static boolean isValidIsWatched(String test) { + return test.equals("true") || test.equals("false"); + } + + public boolean getIsWatchedBoolean() { + return value; + } + + @Override + public String toString() { + return Boolean.toString(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof IsWatched // instanceof handles nulls + && value == ((IsWatched) other).value); // state check + } + + @Override + public int hashCode() { + return value ? 1 : 0; + } + +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Movie.java b/src/main/java/seedu/ezwatchlist/model/show/Movie.java new file mode 100644 index 00000000000..9d98188dca3 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Movie.java @@ -0,0 +1,55 @@ +package seedu.ezwatchlist.model.show; + +import java.util.List; +import java.util.Set; + +import seedu.ezwatchlist.model.actor.Actor; + +/** + * Represents a Movie in the watchlist. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Movie extends Show { + + public Movie(Name name, Description description, IsWatched isWatched, + Date dateOfRelease, RunningTime runningTime, Set actors) { + super(name, description, isWatched, dateOfRelease, runningTime, actors); + super.setType("Movie"); + } + + @Override + public int getNumOfEpisodesWatched() { + return 0; + } + + @Override + public int getTotalNumOfEpisodes() { + return 0; + } + + @Override + public List getTvSeasons() { + return null; + } + + @Override + public int getLastWatchedSeasonNum() { + return 0; + } + + @Override + public int getLastWatchedSeasonEpisode() { + return 0; + } + + @Override + public int getNumOfSeasons() { + return 0; + } + + @Override + public int getNumOfEpisodesOfSeason(int seasonNum) { + return 0; + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/ezwatchlist/model/show/Name.java similarity index 52% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/ezwatchlist/model/show/Name.java index 79244d71cf7..4d538fbcf49 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/ezwatchlist/model/show/Name.java @@ -1,24 +1,27 @@ -package seedu.address.model.person; +package seedu.ezwatchlist.model.show; -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static java.util.Objects.isNull; /** - * Represents a Person's name in the address book. + * Represents a Show's name in the watchlist. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { - + public static final String DEFAULT_NAME = "n.a."; public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Show names should only contain characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the show must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String showName; - public final String fullName; + public Name() { + showName = DEFAULT_NAME; + } /** * Constructs a {@code Name}. @@ -26,9 +29,14 @@ public class Name { * @param name A valid name. */ public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + if (isNull(name)) { + name = DEFAULT_NAME; + } + showName = name; + } + + public String getName() { + return showName; } /** @@ -41,19 +49,19 @@ public static boolean isValidName(String test) { @Override public String toString() { - return fullName; + return showName; } @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 + && showName.equals(((Name) other).showName)); // state check } @Override public int hashCode() { - return fullName.hashCode(); + return showName.hashCode(); } } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/ezwatchlist/model/show/NameContainsKeywordsPredicate.java similarity index 73% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/ezwatchlist/model/show/NameContainsKeywordsPredicate.java index c9b5868427c..4f1fb713afc 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/ezwatchlist/model/show/NameContainsKeywordsPredicate.java @@ -1,14 +1,14 @@ -package seedu.address.model.person; +package seedu.ezwatchlist.model.show; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import seedu.ezwatchlist.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Show}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Show show) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(show.getName().showName, keyword)); } @Override diff --git a/src/main/java/seedu/ezwatchlist/model/show/Poster.java b/src/main/java/seedu/ezwatchlist/model/show/Poster.java new file mode 100644 index 00000000000..4246e9ef526 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Poster.java @@ -0,0 +1,99 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.isNull; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; + +import seedu.ezwatchlist.api.model.ImageRetrieval; +import seedu.ezwatchlist.commons.core.LogsCenter; + +/** + * Represents a Show's poster in the watchlist. + */ +public class Poster { + private static final Logger logger = LogsCenter.getLogger(Poster.class); + private static final String PLACEHOLDER_IMAGE = "/images/poster-placeholder.png"; + private static final String ROOT_LOCATION = ImageRetrieval.IMAGE_CACHE_LOCATION + File.separator; + private Image image; + private String imagePath; + private boolean isPlaceholder; + + /** + * Constructs a Poster class which defaults to a placeholder image to be displayed. + */ + public Poster() { + isPlaceholder = true; + imagePath = PLACEHOLDER_IMAGE; + } + + /** + * Constructs a {@code Poster} with the path of the image given. + * @param path the path of the image in the save location. + */ + public Poster(String path) { + if (isNull(path)) { + isPlaceholder = true; + imagePath = PLACEHOLDER_IMAGE; + } else { + isPlaceholder = false; + imagePath = path; + } + } + + /** + * Returns the image path of the image. + * @return string format of the image path. + */ + public String getImagePath() { + return imagePath; + } + + /** + * Returns the image of the Poster. + * @return the Image to be displayed in the application. + */ + public Image getImage() { + if (isPlaceholder) { + return new Image(PLACEHOLDER_IMAGE); + } + + try { + if (isOfflineImage()) { + return new Image(imagePath); //return offline image. + } + + String url = ROOT_LOCATION + imagePath; + File file = new File(url); + image = SwingFXUtils.toFXImage(ImageIO.read(file), null); + + return image; + } catch (IOException i) { + logger.info("Cause: " + i + " in Poster class for imagePath " + imagePath); + return new Image(PLACEHOLDER_IMAGE); + } catch (NullPointerException e) { + logger.info("Cause: " + e + " in Poster class for imagePath " + imagePath); + return new Image(PLACEHOLDER_IMAGE); + } + } + + /** + * Checks if the image path is a local image used for offline display. + * @return true if the image is offline. + */ + private boolean isOfflineImage() { + try { + Image offlineImage = new Image(imagePath); + //checks if the image could be in the offline storage. + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/RunningTime.java b/src/main/java/seedu/ezwatchlist/model/show/RunningTime.java new file mode 100644 index 00000000000..72e2d29ddbe --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/RunningTime.java @@ -0,0 +1,58 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.requireNonNull; +import static seedu.ezwatchlist.commons.util.AppUtil.checkArgument; + +/** + * Represents a Show's Running Time in the watchlist. + * Guarantees: immutable. + */ +public class RunningTime { + // For now the running time will be an integer, since that is what the API returns it as. + + public static final String MESSAGE_CONSTRAINTS = + "Running time cannot be blank and can take only non-negative integers."; + public static final String MESSAGE_CONSTRAINTS2 = MESSAGE_CONSTRAINTS + + "It cannot be more than JAVA max integer value"; + + public final int value; + + public RunningTime() { + value = 0; + } + + /** + * Constructs an {@code RunningTime}. + * + * @param runningTime A valid running time. + */ + public RunningTime(int runningTime) { + requireNonNull(runningTime); + checkArgument(isValidRunningTime(runningTime), MESSAGE_CONSTRAINTS); + value = runningTime; + } + + /** + * Returns true if a given integer is a valid running time. + */ + public static boolean isValidRunningTime(int test) { + return test >= 0 && test < Integer.MAX_VALUE; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RunningTime // instanceof handles nulls + && value == ((RunningTime) other).value); // state check + } + + @Override + public int hashCode() { + return value; + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Show.java b/src/main/java/seedu/ezwatchlist/model/show/Show.java new file mode 100644 index 00000000000..379db74afe8 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Show.java @@ -0,0 +1,236 @@ +package seedu.ezwatchlist.model.show; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.ezwatchlist.commons.util.CollectionUtil; +import seedu.ezwatchlist.model.actor.Actor; + +/** + * Represents a Show in the watchlist. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public abstract class Show { + + private String type; + + //identity fields + private final Name name; + private final Date dateOfRelease; + private final IsWatched isWatched; + + //data fields + private final Description description; + private final RunningTime runningTime; + private final Set actors = new HashSet<>(); + private Poster poster; + private final Set genres = new HashSet<>(); + + public Show(Name name, Description description, IsWatched isWatched, Date dateOfRelease, + RunningTime runningTime, Set actors) { + this.poster = new Poster(); + CollectionUtil.requireAllNonNull(name, description, isWatched, dateOfRelease, runningTime, actors); + this.name = name; + this.description = description; + this.isWatched = isWatched; + this.dateOfRelease = dateOfRelease; + this.runningTime = runningTime; + this.actors.addAll(actors); + } + + public void setPoster(Poster poster) { + this.poster = poster; + } + + public Poster getPoster() { + return poster; + } + + public Name getName() { + return name; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public Date getDateOfRelease() { + return dateOfRelease; + } + + public IsWatched isWatched() { + return isWatched; + } + + public Description getDescription() { + return description; + } + + public RunningTime getRunningTime() { + return runningTime; + } + + public abstract int getNumOfEpisodesWatched(); + + public abstract int getTotalNumOfEpisodes(); + + public abstract List getTvSeasons(); + + public abstract int getLastWatchedSeasonNum(); + + public abstract int getLastWatchedSeasonEpisode(); + + public abstract int getNumOfSeasons(); + + public abstract int getNumOfEpisodesOfSeason(int seasonNum); + + /** + * Returns an immutable actor set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getActors() { + return Collections.unmodifiableSet(actors); + } + + /** + * Returns an immutable genre set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getGenres() { + return Collections.unmodifiableSet(genres); + } + + public void addGenres(Set genres) { + this.genres.addAll(genres); + } + + /** + * Returns true if both Shows of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two shows. + */ + public boolean isSameShow(Show otherShow) { + if (otherShow == this) { + return true; + } + + return otherShow != null + && otherShow.getName().equals(getName()) + && otherShow.getType().equals(getType()) + && (otherShow.getDateOfRelease().equals(getDateOfRelease()) || otherShow.isWatched() == (isWatched())); + } + + /** + * Return true if the other show has name similar to the current show. + * @param showToBeSearched Show to be compare to this show. + * @return True if the other show has name similar to the current show. + */ + public boolean hasNameWithWord(Show showToBeSearched) { + if (isSameName(showToBeSearched)) { + return true; + } else { + return this.getName().getName().toLowerCase().contains(showToBeSearched.getName().getName().toLowerCase()); + } + } + + /** + * Checks if two shows have the same name. + * @param otherShow other show to be checked. + * @return boolean whether the 2 shows are the same. + */ + public boolean isSameName(Show otherShow) { + if (otherShow == this) { + return true; + } + return otherShow != null && otherShow.getName().equals(getName()); + } + + /** + * Return true if the other show has actor similar to the current show. + * @param showToBeSearched Show to be compare to this show. + * @return True if the other show has name similar to the current show. + */ + public boolean hasActorWithName(Show showToBeSearched) { + Set actorSearchedSet = showToBeSearched.getActors(); + for (Actor actorSearched : actorSearchedSet) { + Set actorDataSet = this.getActors(); + for (Actor actorData : actorDataSet) { + if (actorData.getActorName().toLowerCase().contains(actorSearched.getActorName().toLowerCase())) { + return true; + } + } + } + return false; + } + + /** + * tba + * @param showToBeSearched tba + * @return tba + */ + public boolean hasGenre(Show showToBeSearched) { + Set genreSearchedSet = showToBeSearched.getGenres(); + for (Genre genreSearched : genreSearchedSet) { + Set genreDataSet = this.getGenres(); + for (Genre genreData : genreDataSet) { + if (genreData.getGenreName().toLowerCase().contains(genreSearched.getGenreName().toLowerCase())) { + return true; + } + } + } + return false; + } + + /** + * Returns true if both shows have the same identity and data fields. + * This defines a stronger notion of equality between two shows. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Show)) { + return false; + } + + Show otherShow = (Show) other; + return otherShow.getName().equals(getName()) + && otherShow.getType().equals(getType()) + && otherShow.getDateOfRelease().equals(getDateOfRelease()) + && otherShow.getDescription().equals(getDescription()) + && otherShow.getRunningTime().equals(getRunningTime()) + && otherShow.getActors().equals(getActors()) + && otherShow.getGenres().equals(getGenres()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, type, dateOfRelease, isWatched, description, runningTime, actors); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Date of Release: ") + .append(getDateOfRelease()) + .append(" Description: ") + .append(getDescription()) + .append(" Running Time: ") + .append(getRunningTime()) + .append(" Watched: ") + .append(isWatched().toString()) + .append(" Actors: "); + getActors().forEach(builder::append); + return builder.toString(); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/TvSeason.java b/src/main/java/seedu/ezwatchlist/model/show/TvSeason.java new file mode 100644 index 00000000000..1691b8b3c68 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/TvSeason.java @@ -0,0 +1,50 @@ +package seedu.ezwatchlist.model.show; + +import java.util.ArrayList; + +/** + * Represents a TvSeason of a TvShow in the watchlist. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class TvSeason { + public static final String MESSAGE_CONSTRAINTS_SEASON_NUM = + "Season numbers should be integers greater than 0."; + public static final String MESSAGE_CONSTRAINTS_TOTAL_EPISODES = + "Total number of episodes in a season should be integers greater or equal to 0."; + + private final int seasonNum; + private final int totalNumOfEpisodes; + private final ArrayList episodes; + + public TvSeason(int seasonNum, int totalNumOfEpisodes, ArrayList episodes) { + this.seasonNum = seasonNum; + this.totalNumOfEpisodes = totalNumOfEpisodes; + this.episodes = episodes; + } + + public int getSeasonNum() { + return seasonNum; + } + + public int getTotalNumOfEpisodes() { + return totalNumOfEpisodes; + } + + public ArrayList getEpisodes() { + return episodes; + } + + /** + * Returns true if a given integer is a valid seasonNum. + */ + public static boolean isValidTvSeasonNumber(int test) { + return test > 0; + } + + /** + * Returns true if a given integer is a valid total number of episodes. + */ + public static boolean isValidTotalNumOfEpisodes(int test) { + return test >= 0; + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/TvShow.java b/src/main/java/seedu/ezwatchlist/model/show/TvShow.java new file mode 100644 index 00000000000..743a6c664ec --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/TvShow.java @@ -0,0 +1,77 @@ +package seedu.ezwatchlist.model.show; + +import java.util.List; +import java.util.Set; + +import seedu.ezwatchlist.model.actor.Actor; + +/** + * Represents a TvShow in the watchlist. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class TvShow extends Show { + + private int numOfEpisodesWatched; + private List tvSeasons; + private final int totalNumOfEpisodes; + + public TvShow(Name name, Description description, IsWatched isWatched, + Date dateOfRelease, RunningTime runningTime, Set actors, + int numOfEpisodesWatched, int totalNumOfEpisodes, List tvSeasons) { + super(name, description, isWatched, dateOfRelease, runningTime, actors); + this.numOfEpisodesWatched = numOfEpisodesWatched; + this.totalNumOfEpisodes = totalNumOfEpisodes; + this.tvSeasons = tvSeasons; + super.setType("Tv Show"); + } + + @Override + public int getNumOfEpisodesWatched() { + return numOfEpisodesWatched; + } + + @Override + public List getTvSeasons() { + return tvSeasons; + } + + @Override + public int getTotalNumOfEpisodes() { + return totalNumOfEpisodes; + } + + public int getLastWatchedSeasonNum() { + int seasonNum = 0; + int episodeNum = 0; + while (episodeNum < numOfEpisodesWatched) { + episodeNum += tvSeasons.get(seasonNum).getTotalNumOfEpisodes(); + seasonNum++; + } + return seasonNum; + } + + public int getLastWatchedSeasonEpisode() { + int cumulativeNumberOfEpisodes = 0; + int lastWatchedSeasonNum = getLastWatchedSeasonNum(); + for (int seasonNum = 1; seasonNum < lastWatchedSeasonNum; seasonNum++) { + cumulativeNumberOfEpisodes += tvSeasons.get(seasonNum - 1).getTotalNumOfEpisodes(); + } + int episodeNum = numOfEpisodesWatched - cumulativeNumberOfEpisodes; + + if (lastWatchedSeasonNum > 0 && episodeNum == 0) { + episodeNum = tvSeasons.get(lastWatchedSeasonNum).getTotalNumOfEpisodes(); + } + + return episodeNum; + } + + @Override + public int getNumOfSeasons() { + return tvSeasons.size(); + } + + @Override + public int getNumOfEpisodesOfSeason(int seasonNum) { + return tvSeasons.get(seasonNum - 1).getTotalNumOfEpisodes(); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/Type.java b/src/main/java/seedu/ezwatchlist/model/show/Type.java new file mode 100644 index 00000000000..ea4c6006e11 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/Type.java @@ -0,0 +1,19 @@ +package seedu.ezwatchlist.model.show; + +/** + * Enumeration for the possible type of a show, movie or tv. + */ +public enum Type { + MOVIE("movie"), + TV_SHOW("tv"); + + public final String type; + + Type(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/UniqueShowList.java b/src/main/java/seedu/ezwatchlist/model/show/UniqueShowList.java new file mode 100644 index 00000000000..0ebfeb5527a --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/UniqueShowList.java @@ -0,0 +1,241 @@ +package seedu.ezwatchlist.model.show; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.ezwatchlist.commons.util.CollectionUtil; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.exceptions.DuplicateShowException; +import seedu.ezwatchlist.model.show.exceptions.ShowNotFoundException; + +/** + * A list of shows that enforces uniqueness between its elements and does not allow nulls. + * A show is considered unique by comparing using {@code show#isSameShow(Show)}. As such, adding and updating of + * shows uses Show#isSameShow(show) for equality so as to ensure that the show being added or updated is + * unique in terms of identity in the UniqueShowList. However, the removal of a show uses Show#equals(Object) so + * as to ensure that the show with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Show#isSameShow(Show) + */ +public class UniqueShowList implements Iterable { + + public static final String MESSAGE_SHOW_NOT_FOUND = + "The indicated show is not part of your watchlist, please add it first."; + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent show as the given argument. + * @param toCheck The show to be checked if present. + * @return True if the show to present in the internal list. + */ + public boolean contains(Show toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameShow); + } + + /** + * Returns true if the list contains an equivalent show name as the given argument. + * @param showName Name of show to be searched. + * @return True if a show has the same name as showName. + */ + public boolean hasShowName(Name showName) { + requireNonNull(showName); + Show movie = new Movie(showName, new Description(), new IsWatched("false"), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>())); + Show tvShow = new TvShow(showName, new Description(), new IsWatched("false"), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>()), + 0, 0, new ArrayList<>()); + return internalList.stream().anyMatch(movie::isSameName) && internalList.stream().anyMatch(tvShow::isSameName); + } + + /** + * Returns the list of shows that has the same name as showName. + * @param showName Name of show to be searched. + * @return List of Show that has the same name as showName. + */ + public List getShowIfHasName(Name showName) { + requireNonNull(showName); + Show currentMovie = new Movie(showName, new Description(), new IsWatched(), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>())); + Show currentTvShow = new TvShow(showName, new Description(), new IsWatched(), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>()), + 0, 0, new ArrayList<>()); + return internalList.stream() + .filter(show -> show.hasNameWithWord(currentMovie) || show.hasNameWithWord(currentTvShow)) + .collect(Collectors.toList()); + } + + /** + * Returns true if the list contains any of the actors in actorSet. + * @param actorSet Set of actors to be searched. + * @return True if a show has the same name as showName. + */ + public boolean hasActor(Set actorSet) { + requireNonNull(actorSet); + Show movie = new Movie(new Name(), new Description(), new IsWatched(), new Date(), new RunningTime(), actorSet); + Show tvShow = new TvShow(new Name(), new Description(), new IsWatched(), + new Date(), new RunningTime(), actorSet, 0, 0, new ArrayList<>()); + return internalList.stream().anyMatch(movie::hasActorWithName) + || internalList.stream().anyMatch(tvShow::hasActorWithName); + } + + /** + * Returns the list of shows that has any of the actor in actorSet. + * @param actorSet Set of actor(s) to be searched. + * @return List of shows that has the actor. + */ + public List getShowIfHasActor(Set actorSet) { + requireNonNull(actorSet); + Show currentMovie = new Movie(new Name(), new Description(), new IsWatched("false"), new Date(), + new RunningTime(), actorSet); + Show currentTvShow = new TvShow(new Name(), new Description(), new IsWatched("false"), new Date(), + new RunningTime(), actorSet, + 0, 0, new ArrayList<>()); + return internalList.stream().filter(show -> show.hasActorWithName(currentMovie) + || show.hasActorWithName(currentTvShow)).collect(Collectors.toList()); + } + + /** + * Returns the list of shows that has any of the genre in genreSet. + * @param genreSet Set of genre(s) to be searched. + * @return List of shows that has the genre. + */ + public List getShowIfIsGenre(Set genreSet) { + requireNonNull(genreSet); + Show currentMovie = new Movie(new Name(), new Description(), new IsWatched("false"), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>())); + currentMovie.addGenres(genreSet); + Show currentTvShow = new TvShow(new Name(), new Description(), new IsWatched("false"), new Date(), + new RunningTime(), new HashSet<>(new ArrayList<>()), + 0, 0, new ArrayList<>()); + currentTvShow.addGenres(genreSet); + return internalList.stream().filter(show -> show.hasGenre(currentMovie) + || show.hasGenre(currentTvShow)).collect(Collectors.toList()); + } + + /** + * Adds a show to the list. + * The show must not already exist in the list. + * @param toAdd Show to be added. + */ + public void add(Show toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateShowException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the show {@code target} in the list with {@code editedShow}. + * {@code target} must exist in the list. + * The show identity of {@code editedShow} must not be the same as another existing show in the list. + */ + public void setShow(Show target, Show editedShow) { + CollectionUtil.requireAllNonNull(target, editedShow); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ShowNotFoundException(MESSAGE_SHOW_NOT_FOUND); + } + + if (!target.isSameShow(editedShow) && contains(editedShow)) { + throw new DuplicateShowException(); + } + + internalList.set(index, editedShow); + } + + /** + * Removes the equivalent show from the list. + * The show must exist in the list. + */ + public void remove(Show toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ShowNotFoundException(MESSAGE_SHOW_NOT_FOUND); + } + } + + public void setShows(UniqueShowList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code shows}. + * {@code shows} must not contain duplicate shows. + */ + public void setShows(List shows) { + CollectionUtil.requireAllNonNull(shows); + //currently remove the check if shows are unique + //if (!showsAreUnique(shows)) { + //throw new DuplicateShowException(); + //} + + internalList.setAll(shows); + } + + /** + * 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 UniqueShowList // instanceof handles nulls + && internalList.equals(((UniqueShowList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code shows} contains only unique shows. + */ + private boolean showsAreUnique(List shows) { + for (int i = 0; i < shows.size() - 1; i++) { + for (int j = i + 1; j < shows.size(); j++) { + if (shows.get(i).isSameShow(shows.get(j))) { + return false; + } + } + } + return true; + } + + public boolean showsAreUniquePublic(List shows) { + return showsAreUnique(shows); + } + + public ObservableList getInternalList() { + return internalList; + } + + public ObservableList getInternalUnmodifiableList() { + return internalUnmodifiableList; + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/exceptions/DuplicateShowException.java b/src/main/java/seedu/ezwatchlist/model/show/exceptions/DuplicateShowException.java new file mode 100644 index 00000000000..283ab1e700f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/exceptions/DuplicateShowException.java @@ -0,0 +1,11 @@ +package seedu.ezwatchlist.model.show.exceptions; + +/** + * Signals that the operation will result in duplicate Shows (Shows are considered duplicates if they have the same + * identity). + */ +public class DuplicateShowException extends RuntimeException { + public DuplicateShowException() { + super("Operation would result in duplicate shows"); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/show/exceptions/ShowNotFoundException.java b/src/main/java/seedu/ezwatchlist/model/show/exceptions/ShowNotFoundException.java new file mode 100644 index 00000000000..4774c40d157 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/show/exceptions/ShowNotFoundException.java @@ -0,0 +1,17 @@ +package seedu.ezwatchlist.model.show.exceptions; + +/** + * Signals that the operation is unable to find the specified show. + */ +public class ShowNotFoundException extends RuntimeException { + public ShowNotFoundException(String message) { + super(message); + } + + /** + * Constructs a new {@code CommandException} with the specified detail {@code message} and {@code cause}. + */ + public ShowNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/ezwatchlist/model/tag/Tag.java similarity index 88% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/seedu/ezwatchlist/model/tag/Tag.java index b0ea7e7dad7..c65e56b3921 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/ezwatchlist/model/tag/Tag.java @@ -1,7 +1,8 @@ -package seedu.address.model.tag; +package seedu.ezwatchlist.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.ezwatchlist.commons.util.AppUtil; /** * Represents a Tag in the address book. @@ -21,7 +22,7 @@ public class Tag { */ public Tag(String tagName) { requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); + AppUtil.checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); this.tagName = tagName; } diff --git a/src/main/java/seedu/ezwatchlist/model/util/DataBaseUtil.java b/src/main/java/seedu/ezwatchlist/model/util/DataBaseUtil.java new file mode 100644 index 00000000000..e4a2a15e76f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/util/DataBaseUtil.java @@ -0,0 +1,170 @@ +package seedu.ezwatchlist.model.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.WatchList; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; + +/** + * Contains utility methods for populating {@code WatchList} with sample data. + */ +public class DataBaseUtil { + private static final Genre GENRE_ACTION = new Genre("Action"); + private static final Genre GENRE_COMEDY = new Genre("Comedy"); + private static final Genre GENRE_ADVENTURE = new Genre("Adventure"); + private static final Genre GENRE_FANTASY = new Genre("Fantasy"); + private static final Genre GENRE_FAMILY = new Genre("Family"); + private static final Genre GENRE_SCIENCE_FICTION = new Genre("Science Fiction"); + + private static final Show SHOW_AVENGER_INFINITY_WAR = new Movie( + new Name("Avengers: Infinity War"), + new Description("As the Avengers and their allies have continued to protect the world from threats too " + + "large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A " + + "despot of intergalactic infamy, his goal is to collect all six Infinity Stones, artifacts of " + + "unimaginable power, and use them to inflict his twisted will on all of reality. Everything the " + + "Avengers have fought for has led up to this moment - the fate of Earth and existence itself has " + + "never been more uncertain."), + new IsWatched("false"), + new Date("2018-04-25"), + new RunningTime(149), + getActorSet("Cobie Smulders", "Idris Elba", "Isabella Amara", "William Hurt", "Olaniyan Thurmon", + "Kenneth Branagh", "Zoe Saldana", "Mark Ruffalo", "Matthew Zuk", "Josh Brolin", "Chris Evans", + "Jacob Batalon", "Laura Miller", "Danai Gurira", "Chadwick Boseman", "Elizabeth Olsen", + "Bradley Cooper", "Kerry Condon", "Sebastian Stan", "Letitia Wright", "Robert Downey Jr.", + "Tom Hiddleston", "Stephen McFeely", "Terry Notary", "Ariana Greenblatt", "Anthony Mackie", + "Benedict Cumberbatch", "Blair Jasin", "Paul Bettany", "Chris Hemsworth", "Michael James Shaw", + "Aaron Lazar", "Stan Lee", "Winston Duke", "Don Cheadle", "Scarlett Johansson", "Ross Marquand", + "Tom Holland", "Karen Gillan", "Ethan Dizon", "Ameenah Kaplan", "Benedict Wong", "Gwyneth Paltrow", + "Michael Anthony Rogers", "Carrie Coon", "Florence Kasumba", "Samuel L. Jackson", "Peter Dinklage", + "Sean Gunn", "Monique Ganderton", "Tom Vaughan-Lawlor", "Robert Pralgo", "Pom Klementieff", + "Vin Diesel", "Tiffany Espensen", "Dave Bautista", "Benicio del Toro", "Chris Pratt")); + + private static final Show SHOW_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM = new Movie( + new Name("Fantastic Beasts and Where to Find Them"), + new Description("In 1926, Newt Scamander arrives at the Magical Congress of the United States of America " + + "with a magically expanded briefcase, which houses a number of dangerous creatures and their " + + "habitats. When the creatures escape from the briefcase, it sends the American wizarding " + + "authorities after Newt, and threatens to strain even further the state of magical and " + + "non-magical relations."), + new IsWatched("false"), + new Date("2016-11-16"), + new RunningTime(133), + getActorSet("Paul Dewdney", "Ryan Storey", "Alex Jaep", "Dan Trotter", "Dean Weir", "Douglas Byrne", + "Morgan Walters", "Richard Hardisty", "Andrew Parker", "Sean Cronin", "Todd Boyce", "Ian Jenkins", + "Patrick Carney Junior", "Bernardo Santos", "Lucie Pohl", "Tom Clarke Hill", "Neil Broome", + "David Soffe", "Abi Adeyemi", "Abigayle Honeywill", "Jorge Leon Martinez", "Josh Cowdery", + "Elizabeth Moynihan", "Ron Perlman", "David J Biscoe", "Jake Samuels", "Arnold Montey", + "Andy Mihalache", "Lampros Kalfuntzos", "Michael Gabbitas", "Connor Sullivan", "Aileen Archer", + "Jon Voight", "Erick Hayden", "Joshua Diffley", "Ellie Haddington", "Luke Hope", "Denis Khoroshko", + "Lobna Futers", "Guy Paul", "Tristan Tait", "Reid Anderson", "Henry Douthwaite", "Richard Douglas", + "Jane Perry", "Andreea Păduraru", "Emmi", "Marc Benanti", "Christy Meyer", "Pete Meads", + "Sam Redford", "Tom Hodgkins", "Alphonso Austin", "Miles Roughley", "Edd Osmond", "Nick Donald", + "Cristian Solimeno", "Lee Bolton", "Ezra Miller", "Paul Bergquist", "Geeta Vij", "Arinzé Kene", + "Dennis O'Donnell", "Bart Edwards", "Miquel Brown", "Camilla Talarowska", "Rudy Valentino Grant", + "Chloe de Burgh", "Annarie Boor", "Dino Fazzani", "Peter Breitmayer", "Carmen Cowell", + "Adam Lazarus", "Tom Dab", "Alan Wyn Hughes", "Nathan Benham", "Stacey Clegg", "Fanny Carbonnel", + "Faith Wood-Blagrove", "Nick Owenford", "Khristopher MacLeod", "John Murray", "Paul Birchard", + "Cory Peterson", "Nicholas McGaughey", "Zoë Kravitz", "Alan Mandel", "David Charles-Cully", + "Christian Dixon", "Simon Kerrison", "Brian F. Mulvey", "Nick Davison", "Vassiliki Tzanakou", + "Ashley Hudson", "Eddie Redmayne", "Colin Farrell", "Kirsty Grace", "Matthew Wilson", + "Kevin Guthrie", "Joseph Macnab", "Laura Bernardeschi", "Christine Marzano", "Dave Simon", + "Claire Cooper-King", "Miroslav Zaruba", "Christopher Marsh", "Guna Gultniece", "Andrei Satalov", + "Jenn Murray", "Yves O'Hara", "Robert-Anthony Artlett", "Flor Ferraco", "Akin Gazi", "Aretha Ayeh", + "Craig Davies", "Wunmi Mosaku", "Anick Wiget", "Roy Beck", "Gemma Chan", "Elizabeth Briand", + "Keith Lomas", "Johnny Depp", "Walles Hamonde", "Tim Bentinck", "Max Cazier", "Martin Oelbermann", + "Samantha Morton", "Ronan Raftery", "Dominique Tipper", "Olivia Quinn", "Alison Sudol", + "Greg Brummel", "Marketa Flynn", "Richard Price", "Jason Redshaw", "Lee Asquith-Coe", + "Anne Wittman", "Matthew Sim", "Joe Malone", "David Goodson", "Dan Hedaya", "Leo Heller", + "Michael Barron", "Paul A Munday", "Paul Low-Hang", "Rudi Dharmalingam", "Stephanie Eccles", + "Attila G. Kerekes", "Andrew G. Ogleby", "Richard Clothier", "Adam Lezemore", "Carmen Ejogo", + "Chloe Collingwood", "Gino Picciano", "Dan Fogler", "Silvia Crastan", "Cole Leman", + "Kornelia Horvath", "Katherine Waterston", "James M.L. Muller", "Paul Redfern")); + + private static final Show SHOW_FANTASTIC_BEASTS_THE_CRIMES_OF_GRINDELWALD = new Movie( + new Name("Fantastic Beasts: The Crimes of Grindelwald"), + new Description("Gellert Grindelwald has escaped imprisonment and has begun gathering followers to his " + + "cause—elevating wizards above all non-magical beings. The only one capable of putting a stop " + + "to " + + "him is the wizard he once called his closest friend, Albus Dumbledore. However, Dumbledore will " + + "need to seek help from the wizard who had thwarted Grindelwald once before, his former student " + + "Newt Scamander, who agrees to help, unaware of the dangers that lie ahead. Lines are drawn as " + + "love and loyalty are tested, even among the truest friends and family, in an increasingly " + + "divided wizarding world."), + new IsWatched("false"), + new Date("2018-11-14"), + new RunningTime(134), + getActorSet("Claudius Peters", "Maja Bloom", "Wolf Roth", "Jamie Campbell Bower", "Donna Preston", + "Ed Gaughan", "Andrew Blackall", "Ruby Woolfenden", "Zoë Kravitz", "Johanna Thea", "Andy Summers", + "Jemima Woolnough", "Simon Meacock", "Nasir Jama", "Olivia Popica", "Bernardo Santos", + "David Wilmot", "Liv Hansen", "Hollie Burgess", "Eddie Redmayne", "Jude Law", "Israel Ruiz", + "Callum Forman", "Ryan Hannaford", "Isaura Barbé-Brown", "Kevin Guthrie", "Christopher Birks", + "Callum Turner", "Dave Simon", "Stephen McDade", "Alfrun Rose", "Alexandra Ford", "Victoria Yeates", + "Sean Coleman", "David Sakurai", "Morrison Thomas", "Tahir Burhan", "Deepak Anand", "Johnny Depp", + "Alfie Simmons", "Toby Regbo", "Isaac Domingos", "Linda Santiago", "Phil Hodges", "Adrian Wheeler", + "Ezra Miller", "Hugh Quarshie", "Alison Sudol", "Jag Patel", "Jason Redshaw", "Al Clark", + "Ólafur Darri Ólafsson", "Poppy Corby-Tuech", "Danielle Hugues", "Claudia Kim", "Simon Wan", + "William Nadylam", "Cornell John", "Tim Ingall", "Connor Wolf", "Fiona Glascott", + "Bart Soroczynski", "Andrew Turner", "Michael Haydon", "Thea Lamb", "Annarie Boor", "Carmen Ejogo", + "Jessica Williams", "Jeremy Oliver", "Olwen Fouéré", "Dan Fogler", "Ingvar Eggert Sigurðsson", + "Derek Riddell", "Keith Chanter", "Sean Gislingham", "Katherine Waterston", "Aykut Hilmi", + "Sabine Crossen", "Alfie Mailley", "Natalie Lauren", "Deano Bugatti", "Brontis Jodorowsky", + "Isaac Cortinovis Johnson", "Nick Owenford", "Joshua Shea")); + + public static ArrayList getShowData() { + ArrayList showDataList = new ArrayList<>(); + + Set genreSetActionAdventureScienceFiction = new HashSet<>(); + genreSetActionAdventureScienceFiction.add(GENRE_ACTION); + genreSetActionAdventureScienceFiction.add(GENRE_ADVENTURE); + genreSetActionAdventureScienceFiction.add(GENRE_SCIENCE_FICTION); + SHOW_AVENGER_INFINITY_WAR.addGenres(genreSetActionAdventureScienceFiction); + SHOW_AVENGER_INFINITY_WAR.setPoster(new Poster("/images/Avengers__Infinity_War2018423968.png")); + showDataList.add(SHOW_AVENGER_INFINITY_WAR); + + Set genreSetAdventureFantasyFamily = new HashSet<>(); + genreSetAdventureFantasyFamily.add(GENRE_ADVENTURE); + genreSetAdventureFantasyFamily.add(GENRE_FANTASY); + genreSetAdventureFantasyFamily.add(GENRE_FAMILY); + SHOW_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM.addGenres(genreSetAdventureFantasyFamily); + SHOW_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM.setPoster( + new Poster("/images/Fantastic_Beasts_and_Where_to_Find_Them-427997046.png")); + SHOW_FANTASTIC_BEASTS_THE_CRIMES_OF_GRINDELWALD.setPoster( + new Poster("/images/Fantastic_Beasts__The_Crimes_of_Grindelwald-392145351.png")); + SHOW_FANTASTIC_BEASTS_THE_CRIMES_OF_GRINDELWALD.addGenres(genreSetAdventureFantasyFamily); + showDataList.add(SHOW_FANTASTIC_BEASTS_AND_WHERE_TO_FIND_THEM); + showDataList.add(SHOW_FANTASTIC_BEASTS_THE_CRIMES_OF_GRINDELWALD); + + return showDataList; + } + + public static ReadOnlyWatchList getShowDatabaseList() { + WatchList database = new WatchList(); + for (Show show : getShowData()) { + database.addShow(show); + } + return database; + } + + /** + * Returns an Actor set containing the list of strings given. + */ + public static Set getActorSet(String... strings) { + return Arrays.stream(strings) + .map(Actor::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/seedu/ezwatchlist/model/util/SampleDataUtil.java b/src/main/java/seedu/ezwatchlist/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..b8754b71b3a --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/model/util/SampleDataUtil.java @@ -0,0 +1,59 @@ +package seedu.ezwatchlist.model.util; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.WatchList; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; + +/** + * Contains utility methods for populating {@code WatchList} with sample data. + */ +public class SampleDataUtil { + public static Show[] getSampleShows() { + return new Show[] { + new Movie(new Name("Joker"), new Description("In Gotham City, mentally-troubled comedian Arthur Fleck" + + " embarks on a downward-spiral of social revolution and bloody crime. This path brings" + + " him face-to-face with his infamous alter-ego: \"The Joker\"."), + new IsWatched("false"), new Date("4 October 2019"), new RunningTime(122), + getActorSet("Joaquin Phoenix, Robert De Niro")) + }; + } + + public static ReadOnlyWatchList getSampleWatchList() { + WatchList sampleWl = new WatchList(); + for (Show sampleShow : getSampleShows()) { + sampleWl.addShow(sampleShow); + } + return sampleWl; + } + + /** + * Returns an Actor set containing the list of strings given. + */ + public static Set getActorSet(String... strings) { + return Arrays.stream(strings) + .map(Actor::new) + .collect(Collectors.toSet()); + } + + /** + * Returns an Genre set containing the list of strings given. + */ + public static Set getGenreSet(String... strings) { + return Arrays.stream(strings) + .map(Genre::new) + .collect(Collectors.toSet()); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/statistics/Statistics.java b/src/main/java/seedu/ezwatchlist/statistics/Statistics.java new file mode 100644 index 00000000000..66dcc901344 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/statistics/Statistics.java @@ -0,0 +1,132 @@ +package seedu.ezwatchlist.statistics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; + +import seedu.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.api.model.ApiInterface; +import seedu.ezwatchlist.api.model.ApiManager; +import seedu.ezwatchlist.api.util.ApiUtil; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvShow; +import seedu.ezwatchlist.model.show.UniqueShowList; + +/** + * Represents a Statistics object that contains relevant information. + */ +public class Statistics { + private final Model model; + + private ApiInterface apiManager; + + public Statistics (Model model) throws OnlineConnectionException { + this.model = model; + try { + apiManager = new ApiManager(); + } catch (OnlineConnectionException e) { + //have to handle + apiManager = null; + e.printStackTrace(); + } + } + + /** + * Gets the movies that are likely to be forgotten by the user. + * @return an observable list of forgotten shows + */ + public ObservableList getForgotten() { + ObservableList watchlist = model.getWatchList().getShowList().filtered(show -> !show.isWatched().value); + UniqueShowList forgotten = new UniqueShowList(); + if (watchlist != null && watchlist.size() > 4) { + forgotten.add(watchlist.get(0)); + forgotten.add(watchlist.get(1)); + forgotten.add(watchlist.get(2)); + } + return forgotten.asUnmodifiableObservableList(); + } + + /** + * Gets the favourite genre of the user. + * @return an observable list of genres strings + */ + public ObservableMap getFavouriteGenre() { + HashMap genreRecords = new HashMap<>(); + if (model.getWatchList().getShowList() == null) { + return FXCollections.observableHashMap(); + } + model.getWatchList().getShowList().stream().forEach(show -> { + show.getGenres().stream().forEach(genre -> { + if (genreRecords.containsKey(genre.getGenreName())) { + genreRecords.put(genre.getGenreName(), genreRecords.get(genre.getGenreName()) + 1); + } else { + genreRecords.put(genre.getGenreName(), 1); + } + }); + }); + + List keyList = new ArrayList<>(); + keyList.addAll(genreRecords.keySet()); + Collections.sort(keyList, (key1, key2) -> genreRecords.get(key2) - genreRecords.get(key1)); + + ObservableMap favouriteGenres = FXCollections.observableHashMap(); + for (int i = 0; i < 3 && i < keyList.size(); i++) { + favouriteGenres.put(keyList.get(i), genreRecords.get(keyList.get(i))); + } + return favouriteGenres; + } + + public ObservableList getMovieRecommendations() throws OnlineConnectionException { + List recommendations = null; + if (apiManager == null) { + apiManager = new ApiManager(); + } + List movieList = ApiUtil.filterToMovieFromShow(model.getWatchList().getShowList()); + if (movieList.isEmpty()) { + System.out.println("movie split is empty"); + } + try { + recommendations = apiManager.getMovieRecommendations(movieList, 3); + return FXCollections.observableArrayList(recommendations); + } catch (OnlineConnectionException e) { + e.printStackTrace(); + return null; + } catch (NoRecommendationsException e) { + e.printStackTrace(); + return null; + } + } + + public ObservableList getTvShowRecommendations() throws OnlineConnectionException { + if (apiManager == null) { + apiManager = new ApiManager(); + } + List tvList = ApiUtil.filterToTvShowsFromShow(model.getWatchList().getShowList()); + List recommendations = null; + + try { + recommendations = apiManager.getTvShowRecommendations(tvList, 3); + return FXCollections.observableArrayList(recommendations); + } catch (OnlineConnectionException e) { + return null; + } catch (NoRecommendationsException e) { + return null; + } + } + public Model getModel() { + return model; + } + + public ApiInterface getApiManager() { + return apiManager; + } + +} diff --git a/src/main/java/seedu/ezwatchlist/storage/DatabaseStorage.java b/src/main/java/seedu/ezwatchlist/storage/DatabaseStorage.java new file mode 100644 index 00000000000..8cdcfd06e06 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/DatabaseStorage.java @@ -0,0 +1,46 @@ +package seedu.ezwatchlist.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.WatchList; + +/** + * Represents a database storage for {@link WatchList}. + */ +public interface DatabaseStorage { + + /** + * Returns the file path of the database file. + */ + Path getDatabaseFilePath(); + + /** + * Returns database as a {@link ReadOnlyWatchList}. + * 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 readDatabase() throws DataConversionException, IOException; + + /** + * @see #getDatabaseFilePath() + */ + Optional readDatabase(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyWatchList} to the storage. + * @param database cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveDatabase(ReadOnlyWatchList database) throws IOException; + + /** + * @see #saveDatabase(ReadOnlyWatchList) + */ + void saveDatabase(ReadOnlyWatchList database, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedActor.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedActor.java new file mode 100644 index 00000000000..4550ec02af0 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedActor.java @@ -0,0 +1,48 @@ +package seedu.ezwatchlist.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.actor.Actor; + +/** + * Jackson-friendly version of {@link Actor}. + */ +class JsonAdaptedActor { + + private final String actorName; + + /** + * Constructs a {@code JsonAdaptedActor} with the given {@code actorName}. + */ + @JsonCreator + public JsonAdaptedActor(String actorName) { + this.actorName = actorName; + } + /** + * Converts a given {@code Actor} into this class for Jackson use. + */ + public JsonAdaptedActor(Actor source) { + actorName = source.actorName; + } + + @JsonValue + public String getActorName() { + return actorName; + } + + /** + * Converts this Jackson-friendly adapted actor object into the model's {@code Actor} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted actor. + */ + public Actor toModelType() throws IllegalValueException { + if (!Actor.isValidActorName(actorName)) { + throw new IllegalValueException(Actor.MESSAGE_CONSTRAINTS); + } + return new Actor(actorName); + } + + +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedEpisode.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedEpisode.java new file mode 100644 index 00000000000..cecaf7d2114 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedEpisode.java @@ -0,0 +1,57 @@ +package seedu.ezwatchlist.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.show.Episode; + +/** + * Jackson-friendly version of {@link Episode}. + */ +class JsonAdaptedEpisode { + + private final String episodeName; + private final int episodeNum; + + /** + * Constructs a {@code JsonAdaptedEpisode} with the given episode details. + */ + @JsonCreator + public JsonAdaptedEpisode(@JsonProperty("name") String episodeName, + @JsonProperty("episode number") int episodeNum) { + this.episodeName = episodeName; + this.episodeNum = episodeNum; + } + + /** + * Converts a given {@code Episode} into this class for Jackson use. + */ + public JsonAdaptedEpisode(Episode source) { + episodeName = source.getEpisodeName(); + episodeNum = source.getEpisodeNum(); + } + + public String getEpisodeName() { + return episodeName; + } + + public int getEpisodeNum() { + return episodeNum; + } + + /** + * Converts this Jackson-friendly adapted episode object into the model's {@code Episode} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted episode. + */ + public Episode toModelType() throws IllegalValueException { + + if (!Episode.isValidEpisodeNum(episodeNum)) { + throw new IllegalValueException(Episode.MESSAGE_CONSTRAINTS_EPISODE_NUMBER); + } + + return new Episode(episodeName, episodeNum); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedGenre.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedGenre.java new file mode 100644 index 00000000000..043f64b2b1c --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedGenre.java @@ -0,0 +1,47 @@ +package seedu.ezwatchlist.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.show.Genre; + +/** + * Jackson-friendly version of {@link Genre}. + */ +public class JsonAdaptedGenre { + + private final String genreName; + + /** + * Constructs a {@code JsonAdaptedGenres} with the given {@code Genre}. + */ + @JsonCreator + public JsonAdaptedGenre(String genreName) { + this.genreName = genreName; + } + + /** + * Converts a given {@code Genre} into this class for Jackson use. + */ + public JsonAdaptedGenre(Genre source) { + genreName = source.genreName; + } + + @JsonValue + public String getGenreName() { + return genreName; + } + + /** + * Converts this Jackson-friendly adapted genre object into the model's {@code Genre} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted genre. + */ + public Genre toModelType() throws IllegalValueException { + if (!Genre.isValidGenreName(genreName)) { + throw new IllegalValueException(Genre.MESSAGE_CONSTRAINTS); + } + return new Genre(genreName); + } +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedMovie.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedMovie.java new file mode 100644 index 00000000000..b32339ddfbb --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedMovie.java @@ -0,0 +1,153 @@ +package seedu.ezwatchlist.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.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; + +/** + * Jackson-friendly version of {@link Movie}. + */ +public class JsonAdaptedMovie { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Show's %s field is missing!"; + + private final String name; + private final String type; + private final String dateOfRelease; + private final String isWatched; + private final String description; + private final int runningTime; + private final String poster; + private final List actors = new ArrayList<>(); + private final List genres = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedMovie} with the given show details. + */ + @JsonCreator + public JsonAdaptedMovie(@JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("dateOfRelease") String dateOfRelease, + @JsonProperty("watched") String isWatched, + @JsonProperty("description") String description, + @JsonProperty("runningTime") int runningTime, + @JsonProperty("actors") List actors, + @JsonProperty("poster") String poster, + @JsonProperty("genres") List genres) { + this.name = name; + this.type = type; + this.dateOfRelease = dateOfRelease; + this.isWatched = isWatched; + this.description = description; + this.runningTime = runningTime; + if (actors != null) { + this.actors.addAll(actors); + } + this.poster = poster; + if (genres != null) { + this.genres.addAll(genres); + } + } + + /** + * Converts a given {@code Movie} into this class for Jackson use. + */ + public JsonAdaptedMovie(Show source) { + name = source.getName().showName; + type = source.getType(); + dateOfRelease = source.getDateOfRelease().value; + isWatched = Boolean.toString(source.isWatched().value); + description = source.getDescription().fullDescription; + runningTime = source.getRunningTime().value; + actors.addAll(source.getActors().stream() + .map(JsonAdaptedActor::new) + .collect(Collectors.toList())); + poster = source.getPoster().getImagePath(); + genres.addAll(source.getGenres().stream() + .map(JsonAdaptedGenre::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted tv show object into the model's {@code TvShow} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted tv show. + */ + public Show toModelType() throws IllegalValueException { + final List showActors = new ArrayList<>(); + for (JsonAdaptedActor actor : actors) { + showActors.add(actor.toModelType()); + } + + final List showGenres = new ArrayList<>(); + for (JsonAdaptedGenre genre : genres) { + showGenres.add(genre.toModelType()); + } + + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (dateOfRelease == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!Date.isValidDate(dateOfRelease)) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + final Date modelDateOfRelease = new Date(dateOfRelease); + + if (!IsWatched.isValidIsWatched(isWatched)) { + throw new IllegalValueException(IsWatched.MESSAGE_CONSTRAINTS); + } + final IsWatched modelIsWatched = new IsWatched(isWatched); + + if (description == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final Description modelDescription = new Description(description); + + if (!RunningTime.isValidRunningTime(runningTime)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final RunningTime modelRunningTime = new RunningTime(runningTime); + + final Set modelActors = new HashSet<>(showActors); + + final Set modelGenres = new HashSet<>(showGenres); + + Show show = new Movie(modelName, modelDescription, modelIsWatched, + modelDateOfRelease, modelRunningTime, modelActors); + + show.setType(type); + show.setPoster(new Poster(poster)); + show.addGenres(modelGenres); + + return show; + } + + public String getName() { + return this.name; + } +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedShows.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedShows.java new file mode 100644 index 00000000000..fb9066ea66a --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedShows.java @@ -0,0 +1,72 @@ +package seedu.ezwatchlist.storage; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.show.Show; + +/** + * Jackson-friendly version of list of shows. + */ +class JsonAdaptedShows { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Show's %s field is missing!"; + + private final List tvShows = new ArrayList<>(); + private final List movies = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedShows} with the given show details. + */ + @JsonCreator + public JsonAdaptedShows(@JsonProperty("tvShows") List tvShows, + @JsonProperty("movies") List movies) { + if (tvShows != null) { + this.tvShows.addAll(tvShows); + } + if (movies != null) { + this.movies.addAll(movies); + } + } + + /** + * Converts a given {@code Show} into this class for Jackson use. + */ + public JsonAdaptedShows(List source) { + for (Show show : source) { + if (show.getType().equals("Movie")) { + movies.add(new JsonAdaptedMovie(show)); + } else { + tvShows.add(new JsonAdaptedTvShow(show)); + } + } + } + + public List getTvShows() { + return tvShows; + } + + public List getMovies() { + return movies; + } + + /** + * Converts this Jackson-friendly adapted show object into the model's {@code Show} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted show. + */ + public List toModelType() throws IllegalValueException { + final List shows = new ArrayList<>(); + for (JsonAdaptedTvShow tvShow : tvShows) { + shows.add(tvShow.toModelType()); + } + for (JsonAdaptedMovie movie : movies) { + shows.add(movie.toModelType()); + } + return shows; + } +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvSeason.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvSeason.java new file mode 100644 index 00000000000..a9e64e38d56 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvSeason.java @@ -0,0 +1,79 @@ +package seedu.ezwatchlist.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 seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.show.Episode; +import seedu.ezwatchlist.model.show.TvSeason; + +/** + * Jackson-friendly version of {@link TvSeason}. + */ +class JsonAdaptedTvSeason { + + private final int seasonNumber; + private final int totalNumOfEpisodes; + private final List episodes = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedTvSeason} with the given {@code seasonNumber}. + */ + @JsonCreator + public JsonAdaptedTvSeason(@JsonProperty("seasonNumber") int seasonNumber, + @JsonProperty("totalNumOfEpisodes") int totalNumOfEpisodes, + @JsonProperty("episodes") List episodes) { + this.seasonNumber = seasonNumber; + this.totalNumOfEpisodes = totalNumOfEpisodes; + if (episodes != null) { + this.episodes.addAll(episodes); + } + } + + /** + * Converts a given {@code TvSeason} into this class for Jackson use. + */ + public JsonAdaptedTvSeason(TvSeason source) { + seasonNumber = source.getSeasonNum(); + totalNumOfEpisodes = source.getTotalNumOfEpisodes(); + episodes.addAll(source.getEpisodes().stream() + .map(JsonAdaptedEpisode::new) + .collect(Collectors.toList())); + } + + public int getSeasonNumber() { + return seasonNumber; + } + + public int getTotalNumOfEpisodes() { + return totalNumOfEpisodes; + } + + public List getEpisodes() { + return episodes; + } + + /** + * Converts this Jackson-friendly adapted tv season object into the model's {@code TvSeason} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted TvSeason. + */ + public TvSeason toModelType() throws IllegalValueException { + final ArrayList seasonEpisodes = new ArrayList<>(); + for (JsonAdaptedEpisode episode : episodes) { + seasonEpisodes.add(episode.toModelType()); + } + if (!TvSeason.isValidTvSeasonNumber(seasonNumber)) { + throw new IllegalValueException(TvSeason.MESSAGE_CONSTRAINTS_SEASON_NUM); + } + if (!TvSeason.isValidTotalNumOfEpisodes(totalNumOfEpisodes)) { + throw new IllegalValueException(TvSeason.MESSAGE_CONSTRAINTS_TOTAL_EPISODES); + } + return new TvSeason(seasonNumber, totalNumOfEpisodes, seasonEpisodes); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvShow.java b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvShow.java new file mode 100644 index 00000000000..8f935bb49f2 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonAdaptedTvShow.java @@ -0,0 +1,171 @@ +package seedu.ezwatchlist.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.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.actor.Actor; +import seedu.ezwatchlist.model.show.Date; +import seedu.ezwatchlist.model.show.Description; +import seedu.ezwatchlist.model.show.Genre; +import seedu.ezwatchlist.model.show.IsWatched; +import seedu.ezwatchlist.model.show.Name; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.RunningTime; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvSeason; +import seedu.ezwatchlist.model.show.TvShow; + +/** + * Jackson-friendly version of {@link TvShow}. + */ +public class JsonAdaptedTvShow { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Show's %s field is missing!"; + + private final String name; + private final String type; + private final String dateOfRelease; + private final String isWatched; + private final String description; + private final int runningTime; + private final String poster; + private final List actors = new ArrayList<>(); + private final int numOfEpisodesWatched; + private final int totalNumOfEpisodes; + private final List tvSeasons = new ArrayList<>(); + private final List genres = new ArrayList<>(); + /** + * Constructs a {@code JsonAdaptedShows} with the given show details. + */ + @JsonCreator + public JsonAdaptedTvShow(@JsonProperty("name") String name, + @JsonProperty("type") String type, + @JsonProperty("dateOfRelease") String dateOfRelease, + @JsonProperty("watched") String isWatched, + @JsonProperty("description") String description, + @JsonProperty("runningTime") int runningTime, + @JsonProperty("actors") List actors, + @JsonProperty("poster") String poster, + @JsonProperty("numOfEpisodesWatched") int numOfEpisodesWatched, + @JsonProperty("totalNumOfEpisodes") int totalNumOfEpisodes, + @JsonProperty("tvSeasons") List tvSeasons, + @JsonProperty("genres") List genres) { + this.name = name; + this.type = type; + this.dateOfRelease = dateOfRelease; + this.isWatched = isWatched; + this.description = description; + this.runningTime = runningTime; + if (actors != null) { + this.actors.addAll(actors); + } + this.poster = poster; + this.numOfEpisodesWatched = numOfEpisodesWatched; + this.totalNumOfEpisodes = totalNumOfEpisodes; + if (tvSeasons != null) { + this.tvSeasons.addAll(tvSeasons); + } + if (genres != null) { + this.genres.addAll(genres); + } + } + + /** + * Converts a given {@code TvShow} into this class for Jackson use. + */ + public JsonAdaptedTvShow(Show source) { + name = source.getName().showName; + type = source.getType(); + dateOfRelease = source.getDateOfRelease().value; + isWatched = Boolean.toString(source.isWatched().value); + description = source.getDescription().fullDescription; + runningTime = source.getRunningTime().value; + actors.addAll(source.getActors().stream() + .map(JsonAdaptedActor::new) + .collect(Collectors.toList())); + poster = source.getPoster().getImagePath(); + genres.addAll(source.getGenres().stream() + .map(JsonAdaptedGenre::new) + .collect(Collectors.toList())); + numOfEpisodesWatched = source.getNumOfEpisodesWatched(); + totalNumOfEpisodes = source.getTotalNumOfEpisodes(); + tvSeasons.addAll(source.getTvSeasons().stream() + .map(JsonAdaptedTvSeason::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted tv show object into the model's {@code TvShow} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted tv show. + */ + public Show toModelType() throws IllegalValueException { + final List showActors = new ArrayList<>(); + for (JsonAdaptedActor actor : actors) { + showActors.add(actor.toModelType()); + } + + final List showSeasons = new ArrayList<>(); + for (JsonAdaptedTvSeason season : tvSeasons) { + showSeasons.add(season.toModelType()); + } + + final List showGenres = new ArrayList<>(); + for (JsonAdaptedGenre genre : genres) { + showGenres.add(genre.toModelType()); + } + + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (dateOfRelease == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!Date.isValidDate(dateOfRelease)) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + final Date modelDateOfRelease = new Date(dateOfRelease); + + if (!IsWatched.isValidIsWatched(isWatched)) { + throw new IllegalValueException(IsWatched.MESSAGE_CONSTRAINTS); + } + final IsWatched modelIsWatched = new IsWatched(isWatched); + + if (description == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final Description modelDescription = new Description(description); + + if (!RunningTime.isValidRunningTime(runningTime)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + final RunningTime modelRunningTime = new RunningTime(runningTime); + + final Set modelActors = new HashSet<>(showActors); + + final Set modelGenres = new HashSet<>(showGenres); + + Show show = new TvShow(modelName, modelDescription, modelIsWatched, + modelDateOfRelease, modelRunningTime, modelActors, + numOfEpisodesWatched, totalNumOfEpisodes, showSeasons); + + show.setType(type); + show.setPoster(new Poster(poster)); + show.addGenres(modelGenres); + + return show; + } +} diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonDatabaseStorage.java b/src/main/java/seedu/ezwatchlist/storage/JsonDatabaseStorage.java new file mode 100644 index 00000000000..ff1acca47d0 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonDatabaseStorage.java @@ -0,0 +1,81 @@ +package seedu.ezwatchlist.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.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.commons.util.FileUtil; +import seedu.ezwatchlist.commons.util.JsonUtil; +import seedu.ezwatchlist.model.ReadOnlyWatchList; + +/** + * A class to access database of shows stored as a json file on the hard disk. + */ +public class JsonDatabaseStorage implements DatabaseStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonWatchListStorage.class); + + private Path filePath; + + public JsonDatabaseStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getDatabaseFilePath() { + return filePath; + } + + @Override + public Optional readDatabase() throws DataConversionException { + return readDatabase(filePath); + } + + /** + * Similar to {@link #readDatabase()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readDatabase(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonDatabase = JsonUtil.readJsonFile( + filePath, JsonSerializableWatchList.class); + if (!jsonDatabase.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonDatabase.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveDatabase(ReadOnlyWatchList database) throws IOException { + saveDatabase(database, filePath); + } + + /** + * Similar to {@link #saveDatabase(ReadOnlyWatchList)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveDatabase(ReadOnlyWatchList database, Path filePath) throws IOException { + requireNonNull(database); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableWatchList(database), filePath); + } + +} + diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonSerializableWatchList.java b/src/main/java/seedu/ezwatchlist/storage/JsonSerializableWatchList.java new file mode 100644 index 00000000000..c9a376a1695 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonSerializableWatchList.java @@ -0,0 +1,74 @@ +package seedu.ezwatchlist.storage; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import javafx.collections.ObservableList; +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.WatchList; +import seedu.ezwatchlist.model.show.Show; + +/** + * An Immutable WatchList that is serializable to JSON format. + */ +@JsonRootName(value = "watchlist") +public class JsonSerializableWatchList { + + public static final String MESSAGE_DUPLICATE_SHOW = "Show list contains duplicate show(s)."; + + private JsonAdaptedShows shows; + + /** + * Constructs a {@code JsonSerializableWatchList} with the given shows. + */ + @JsonCreator + public JsonSerializableWatchList(@JsonProperty("shows") JsonAdaptedShows shows) { + this.shows = shows; + } + + /** + * Converts a given {@code ReadOnlyWatchList} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableWatchList}. + */ + public JsonSerializableWatchList(ReadOnlyWatchList source) { + ObservableList showList = source.getShowList(); + List movies = new ArrayList<>(); + List tvShows = new ArrayList<>(); + for (Show show : showList) { + if (show.getType().equals("Movie")) { + movies.add(new JsonAdaptedMovie(show)); + } else if (show.getType().equals("Tv Show")) { + tvShows.add(new JsonAdaptedTvShow(show)); + } + } + shows = new JsonAdaptedShows(tvShows, movies); + } + + /** + * Converts this watchlist into the model's {@code WatchList} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public WatchList toModelType() throws IllegalValueException { + WatchList watchList = new WatchList(); + List list = shows.toModelType(); + for (Show show : list) { + if (watchList.hasShow(show)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_SHOW); + } + watchList.addShow(show); + } + return watchList; + } + + public JsonAdaptedShows getShows() { + return shows; + } +} + diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/ezwatchlist/storage/JsonUserPrefsStorage.java similarity index 81% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/ezwatchlist/storage/JsonUserPrefsStorage.java index bc2bbad84aa..99fb5d5867b 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/ezwatchlist/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package seedu.ezwatchlist.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.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.commons.util.JsonUtil; +import seedu.ezwatchlist.model.ReadOnlyUserPrefs; +import seedu.ezwatchlist.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/ezwatchlist/storage/JsonWatchListStorage.java b/src/main/java/seedu/ezwatchlist/storage/JsonWatchListStorage.java new file mode 100644 index 00000000000..a2ee0ece035 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/JsonWatchListStorage.java @@ -0,0 +1,81 @@ +package seedu.ezwatchlist.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.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.commons.exceptions.IllegalValueException; +import seedu.ezwatchlist.commons.util.FileUtil; +import seedu.ezwatchlist.commons.util.JsonUtil; +import seedu.ezwatchlist.model.ReadOnlyWatchList; + +/** + * A class to access WatchList data stored as a json file on the hard disk. + */ +public class JsonWatchListStorage implements WatchListStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonWatchListStorage.class); + + private Path filePath; + + public JsonWatchListStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getWatchListFilePath() { + return filePath; + } + + @Override + public Optional readWatchList() throws DataConversionException { + return readWatchList(filePath); + } + + /** + * Similar to {@link #readWatchList()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readWatchList(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonWatchList = JsonUtil.readJsonFile( + filePath, JsonSerializableWatchList.class); + if (!jsonWatchList.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonWatchList.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveWatchList(ReadOnlyWatchList watchList) throws IOException { + saveWatchList(watchList, filePath); + } + + /** + * Similar to {@link #saveWatchList(ReadOnlyWatchList)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveWatchList(ReadOnlyWatchList watchList, Path filePath) throws IOException { + requireNonNull(watchList); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableWatchList(watchList), filePath); + } + +} + diff --git a/src/main/java/seedu/ezwatchlist/storage/Storage.java b/src/main/java/seedu/ezwatchlist/storage/Storage.java new file mode 100644 index 00000000000..f82def8a955 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/Storage.java @@ -0,0 +1,40 @@ +package seedu.ezwatchlist.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.model.ReadOnlyUserPrefs; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends WatchListStorage, DatabaseStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getWatchListFilePath(); + + @Override + Path getDatabaseFilePath(); + + @Override + Optional readWatchList() throws DataConversionException, IOException; + + @Override + Optional readDatabase() throws DataConversionException, IOException; + + @Override + void saveWatchList(ReadOnlyWatchList watchList) throws IOException; + + @Override + void saveDatabase(ReadOnlyWatchList database) throws IOException; +} diff --git a/src/main/java/seedu/ezwatchlist/storage/StorageManager.java b/src/main/java/seedu/ezwatchlist/storage/StorageManager.java new file mode 100644 index 00000000000..7e6d873999f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/StorageManager.java @@ -0,0 +1,108 @@ +package seedu.ezwatchlist.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.model.ReadOnlyUserPrefs; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.UserPrefs; + +/** + * Manages storage of WatchList data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private WatchListStorage watchListStorage; + private DatabaseStorage databaseStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(WatchListStorage watchListStorage, DatabaseStorage databaseStorage, + UserPrefsStorage userPrefsStorage) { + super(); + this.watchListStorage = watchListStorage; + this.databaseStorage = databaseStorage; + 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); + } + + + // ================ WatchList methods ============================== + + @Override + public Path getWatchListFilePath() { + return watchListStorage.getWatchListFilePath(); + } + + @Override + public Optional readWatchList() throws DataConversionException, IOException { + return readWatchList(watchListStorage.getWatchListFilePath()); + } + + @Override + public Optional readWatchList(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return watchListStorage.readWatchList(filePath); + } + + @Override + public void saveWatchList(ReadOnlyWatchList watchList) throws IOException { + saveWatchList(watchList, watchListStorage.getWatchListFilePath()); + } + + @Override + public void saveWatchList(ReadOnlyWatchList watchList, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + watchListStorage.saveWatchList(watchList, filePath); + } + + // ================ Database methods ============================== + + @Override + public Path getDatabaseFilePath() { + return databaseStorage.getDatabaseFilePath(); + } + + @Override + public Optional readDatabase() throws DataConversionException, IOException { + return readDatabase(databaseStorage.getDatabaseFilePath()); + } + + @Override + public Optional readDatabase(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return databaseStorage.readDatabase(filePath); + } + + @Override + public void saveDatabase(ReadOnlyWatchList database) throws IOException { + saveDatabase(database, databaseStorage.getDatabaseFilePath()); + } + + @Override + public void saveDatabase(ReadOnlyWatchList database, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + databaseStorage.saveDatabase(database, filePath); + } +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/ezwatchlist/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/ezwatchlist/storage/UserPrefsStorage.java index 29eef178dbc..55a46ae3063 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/ezwatchlist/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package seedu.ezwatchlist.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.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.model.ReadOnlyUserPrefs; +import seedu.ezwatchlist.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link 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 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/ezwatchlist/storage/WatchListStorage.java b/src/main/java/seedu/ezwatchlist/storage/WatchListStorage.java new file mode 100644 index 00000000000..25d04e30cdf --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/storage/WatchListStorage.java @@ -0,0 +1,46 @@ +package seedu.ezwatchlist.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.ezwatchlist.commons.exceptions.DataConversionException; +import seedu.ezwatchlist.model.ReadOnlyWatchList; +import seedu.ezwatchlist.model.WatchList; + +/** + * Represents a storage for {@link WatchList}. + */ +public interface WatchListStorage { + + /** + * Returns the file path of the data file. + */ + Path getWatchListFilePath(); + + /** + * Returns WatchList data as a {@link ReadOnlyWatchList}. + * 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 readWatchList() throws DataConversionException, IOException; + + /** + * @see #getWatchListFilePath() + */ + Optional readWatchList(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyWatchList} to the storage. + * @param watchList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveWatchList(ReadOnlyWatchList watchList) throws IOException; + + /** + * @see #saveWatchList(ReadOnlyWatchList) + */ + void saveWatchList(ReadOnlyWatchList watchList, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/ezwatchlist/ui/CommandBox.java similarity index 66% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/ezwatchlist/ui/CommandBox.java index 7d76e691f52..21a1455df81 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/ezwatchlist/ui/CommandBox.java @@ -1,12 +1,15 @@ -package seedu.address.ui; +package seedu.ezwatchlist.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.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; + +import seedu.ezwatchlist.logic.commands.CommandResult; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -18,6 +21,7 @@ public class CommandBox extends UiPart { private final CommandExecutor commandExecutor; + private MainWindow mainWindow; @FXML private TextField commandTextField; @@ -28,6 +32,21 @@ public CommandBox(CommandExecutor commandExecutor) { commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } + public CommandExecutor getCommandExecutor() { + return commandExecutor; + } + + public MainWindow getMainWindow() { + return mainWindow; + } + + /** + * Sets the main window of the command box. + * @param mainWindow + */ + public void setMainWindow(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } /** * Handles the Enter button pressed event. */ @@ -36,7 +55,8 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandTextField.getText()); commandTextField.setText(""); - } catch (CommandException | ParseException e) { + } catch (NullPointerException | CommandException | ParseException + | OnlineConnectionException | NoRecommendationsException e) { setStyleToIndicateCommandFailure(); } } @@ -68,10 +88,9 @@ private void setStyleToIndicateCommandFailure() { public interface CommandExecutor { /** * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) */ - CommandResult execute(String commandText) throws CommandException, ParseException; + CommandResult execute(String commandText) throws CommandException, ParseException, OnlineConnectionException, + NoRecommendationsException; } } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/ezwatchlist/ui/HelpWindow.java similarity index 91% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/ezwatchlist/ui/HelpWindow.java index 9a665915949..2d27c763416 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/ezwatchlist/ui/HelpWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.ui; import java.util.logging.Logger; @@ -8,14 +8,15 @@ import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import seedu.ezwatchlist.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 USERGUIDE_URL = + "https://github.com/AY1920S1-CS2103T-F13-4/main/blob/master/docs/UserGuide.adoc"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/ezwatchlist/ui/LoadingPanel.java b/src/main/java/seedu/ezwatchlist/ui/LoadingPanel.java new file mode 100644 index 00000000000..cb146b68157 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/LoadingPanel.java @@ -0,0 +1,19 @@ +package seedu.ezwatchlist.ui; + +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; + +/** + * A ui for the loading panel that is displayed while api is loading. + */ +public class LoadingPanel extends UiPart { + private static final String FXML = "LoadingPanel.fxml"; + + @FXML + private StackPane loadingView; + + public LoadingPanel() { + super(FXML); + } +} diff --git a/src/main/java/seedu/ezwatchlist/ui/MainWindow.java b/src/main/java/seedu/ezwatchlist/ui/MainWindow.java new file mode 100644 index 00000000000..9975f20424e --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/MainWindow.java @@ -0,0 +1,502 @@ +package seedu.ezwatchlist.ui; + +import java.util.logging.Logger; + +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import seedu.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.GuiSettings; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.logic.Logic; + +import seedu.ezwatchlist.logic.commands.CommandResult; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.Model; +import seedu.ezwatchlist.statistics.Statistics; + +/** + * 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 { + + public static final String MAIN_TAB = "watch-list"; + public static final String WATCHED_TAB = "watched-list"; + public static final String SEARCH_TAB = "search-list"; + public static final String STATISTICS_TAB = "statistics tab"; + + private static final String ACCELERATOR_ERROR = "setAccelerator must be called when button is attached to a scene"; + + private static final String FXML = "MainWindow.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private Stage primaryStage; + private Logic logic; + private String currentTab; + private Boolean isSearchLoading = false; + private Boolean isChangedList = true; + private Statistics statistics; + + // Independent Ui parts residing in this Ui container + private ShowListPanel showListPanel; + private WatchedPanel watchedPanel; + private SearchPanel searchPanel; + private StatisticsPanel statisticsPanel; + private LoadingPanel loadingPanel; + private ResultDisplay resultDisplay; + private HelpWindow helpWindow; + @FXML + private StackPane resultDisplayPlaceHolder; + + @FXML + private StackPane commandBoxPlaceholder; + + @FXML + private MenuItem helpMenuItem; + + @FXML + private StackPane contentPanelPlaceholder; + + @FXML + private Button watchlistButton; + + @FXML + private Button watchedButton; + + @FXML + private Button searchButton; + + @FXML + private Button statisticsButton; + + private Button currentButton; + + public MainWindow(Stage primaryStage, Logic logic, Statistics statistics) { + super(FXML, primaryStage); + + // Set dependencies + this.primaryStage = primaryStage; + this.logic = logic; + this.primaryStage.setTitle("Ezwatchlist"); + this.currentTab = MAIN_TAB; + this.statistics = statistics; + + // Configure the UI + setWindowDefaultSize(logic.getGuiSettings()); + + setAccelerators(); + + helpWindow = new HelpWindow(); + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + public ShowListPanel getShowListPanel() { + return showListPanel; + } + + public Logic getLogic() { + return logic; + } + + 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 main window. + */ + void fillInnerParts() { + showListPanel = new ShowListPanel(logic.getUnWatchedList()); + showListPanel.setMainWindow(this); + watchedPanel = new WatchedPanel(logic.getWatchedList()); + watchedPanel.setMainWindow(this); + searchPanel = new SearchPanel(logic.getSearchResultList()); + searchPanel.setMainWindow(this); + loadingPanel = new LoadingPanel(); + + contentPanelPlaceholder.getChildren().add(showListPanel.getRoot()); + + resultDisplay = new ResultDisplay(); + resultDisplayPlaceHolder.getChildren().add(resultDisplay.getRoot()); + + CommandBox commandBox = new CommandBox(this::executeCommand); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + commandBox.setMainWindow(this); + + watchlistButton.getStyleClass().removeAll("button"); + watchlistButton.getStyleClass().add("button-current"); + + currentButton = watchlistButton; + setSearchAccelerator(searchButton); + setWatchListAccelerator(watchlistButton); + setWatchedAccelerator(watchedButton); + setStatisticsAccelerator(statisticsButton); + } + + private void setSearchAccelerator(final Button button) { + Scene scene = button.getScene(); + if (scene == null) { + throw new IllegalArgumentException(ACCELERATOR_ERROR); + } + scene.getAccelerators().put( + new KeyCodeCombination(KeyCode.DIGIT3), + new Runnable() { + @Override public void run() { + goToSearch(); + } + } + ); + } + + private void setWatchListAccelerator(final Button button) { + Scene scene = button.getScene(); + if (scene == null) { + throw new IllegalArgumentException(ACCELERATOR_ERROR); + } + scene.getAccelerators().put( + new KeyCodeCombination(KeyCode.DIGIT1), + new Runnable() { + @Override public void run() { + goToWatchlist(); + } + } + ); + } + + private void setWatchedAccelerator(final Button button) { + Scene scene = button.getScene(); + if (scene == null) { + throw new IllegalArgumentException(ACCELERATOR_ERROR); + } + scene.getAccelerators().put( + new KeyCodeCombination(KeyCode.DIGIT2), + new Runnable() { + @Override public void run() { + goToWatched(); + } + } + ); + } + + private void setStatisticsAccelerator(final Button button) { + Scene scene = button.getScene(); + if (scene == null) { + throw new IllegalArgumentException(ACCELERATOR_ERROR); + } + scene.getAccelerators().put( + new KeyCodeCombination(KeyCode.DIGIT4), + new Runnable() { + @Override public void run() { + try { + goToStatistics(); + } catch (NoRecommendationsException e) { + e.printStackTrace(); + } catch (OnlineConnectionException e) { + e.printStackTrace(); + } + } + } + ); + } + + /** + * 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 ResultDisplay getResultDisplay() { + return resultDisplay; + } + + /** + * Executes the command and returns the result. + * + * @see Logic#execute(String, MainWindow, String) + */ + public CommandResult executeCommand(String commandText) + throws CommandException, ParseException, OnlineConnectionException, NoRecommendationsException { + try { + switch (currentTab) { + case (MAIN_TAB): + logic.updateFilteredShowList(Model.PREDICATE_UNWATCHED_SHOWS); + break; + case (WATCHED_TAB): + logic.updateFilteredShowList(Model.PREDICATE_WATCHED_SHOWS); + break; + case (SEARCH_TAB): + logic.updateFilteredShowList(Model.PREDICATE_NO_SHOWS); + break; + case (STATISTICS_TAB): + logic.updateFilteredShowList(Model.PREDICATE_NO_SHOWS); + break; + default: + break; + } + CommandResult commandResult = logic.execute(commandText, this, currentTab); + if (!isSearchLoading) { + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + if (commandResult.isShowHelp()) { + handleHelp(); + } + if (commandResult.isExit()) { + handleExit(); + } + if (commandResult.isShortCutKey()) { + handleShortCutKey(commandResult.getFeedbackToUser()); + } + if (commandResult.isChangedList()) { + isChangedList = true; + } + return commandResult; + } + return commandResult; + + //catch ParseException here to implement spellcheck + } catch (CommandException | ParseException | OnlineConnectionException | NoRecommendationsException e) { + + logger.info("Invalid command: " + commandText); + resultDisplay.setFeedbackToUser(e.getMessage()); + throw e; + } catch (NullPointerException | InterruptedException e) { + e.printStackTrace(); + return null; + } + } + + /** + * execute short cut key on UI + * @param feedbackToUser + * @throws NoRecommendationsException + * @throws OnlineConnectionException + */ + private void handleShortCutKey(String feedbackToUser) throws NoRecommendationsException, OnlineConnectionException { + switch (feedbackToUser) { + + case "Watchlist": + goToWatchlist(); + return; + + case "Watchedlist": + goToWatched(); + return; + + case "Search": + goToSearch(); + return; + + case "Statistics": + goToStatistics(); + return; + + default: + return; + } + } + + /** + * Populates the contentPanel with watchlist content + */ + @FXML + public void goToWatchlist() { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(showListPanel.getRoot()); + logic.updateFilteredShowList(Model.PREDICATE_UNWATCHED_SHOWS); + currentTab = MAIN_TAB; + move(currentButton, watchlistButton); + currentButton = watchlistButton; + } + + /** + * Populates the contentPanel with watched list content + */ + @FXML + public void goToWatched() { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(watchedPanel.getRoot()); + logic.updateFilteredShowList(Model.PREDICATE_WATCHED_SHOWS); + currentTab = WATCHED_TAB; + move(currentButton, watchedButton); + currentButton = watchedButton; + } + + /** + * Populates the contentPanel with search content + */ + @FXML + public void goToSearch() { + if (isSearchLoading) { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(loadingPanel.getRoot()); + } else { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(searchPanel.getRoot()); + } + currentTab = SEARCH_TAB; + move(currentButton, searchButton); + currentButton = searchButton; + } + + /** + * Populates the contentPanel with statistics content + */ + @FXML + public void goToStatistics() throws NoRecommendationsException, OnlineConnectionException { + if (isChangedList) { + try { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(loadingPanel.getRoot()); + + Task task = new Task() { + @Override + protected Void call() throws Exception { + try { + statisticsPanel = new StatisticsPanel(statistics.getForgotten(), + statistics.getFavouriteGenre(), statistics.getMovieRecommendations(), + statistics.getTvShowRecommendations()); + } catch (OnlineConnectionException e) { + statisticsPanel = new StatisticsPanel(statistics.getForgotten(), + statistics.getFavouriteGenre(), + null, null); + resultDisplay.setFeedbackToUser("Note: You are not connected to the internet!"); + } + return null; + } + }; + task.setOnSucceeded(evt -> { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(statisticsPanel.getRoot()); + isChangedList = false; + currentTab = STATISTICS_TAB; + move(currentButton, statisticsButton); + currentButton = statisticsButton; + }); + new Thread(task).start(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } else { + contentPanelPlaceholder.getChildren().clear(); + contentPanelPlaceholder.getChildren().add(statisticsPanel.getRoot()); + isChangedList = false; + currentTab = STATISTICS_TAB; + move(currentButton, statisticsButton); + currentButton = statisticsButton; + } + } + + /** + * Changes the style of the button when changing panels. + * @param a the button representing the current panel + * @param b the button representing the button clicked + */ + public void move(Button a, Button b) { + a.getStyleClass().removeAll("button-current"); + a.getStyleClass().add("button"); + b.getStyleClass().removeAll("button"); + b.getStyleClass().add("button-current"); + } + + public String getCurrentTab() { + return currentTab; + } + + /** + * For logic manager. If command is search, searchResultLogger will be called so logic can update this UI (Main + * Window) + * @param commandResult + */ + public void searchResultLogger(CommandResult commandResult) { + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + + } + + public void setIsSearchLoading() { + isSearchLoading = !isSearchLoading; + } + + +} diff --git a/src/main/java/seedu/ezwatchlist/ui/NavigationBar.java b/src/main/java/seedu/ezwatchlist/ui/NavigationBar.java new file mode 100644 index 00000000000..e5fe54febf4 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/NavigationBar.java @@ -0,0 +1,15 @@ +package seedu.ezwatchlist.ui; + +import javafx.scene.layout.Region; + +/** + * A ui for the navigation bar that is displayed at the side of the application. + */ +public class NavigationBar extends UiPart { + private static final String FXML = "NavigationBar.fxml"; + + public NavigationBar() { + super(FXML); + } + +} diff --git a/src/main/java/seedu/ezwatchlist/ui/RecommendationCard.java b/src/main/java/seedu/ezwatchlist/ui/RecommendationCard.java new file mode 100644 index 00000000000..28a66ae3f17 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/RecommendationCard.java @@ -0,0 +1,40 @@ +package seedu.ezwatchlist.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.Show; + +/** + * An UI component that displays information of a recommended {@code Show}. + */ +public class RecommendationCard extends UiPart { + private static final String FXML = "RecommendationCard.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 Show show; + + @FXML + private Label name; + @FXML + private ImageView poster; + + public RecommendationCard(Show show) { + super(FXML); + this.show = show; + name.setText(show.getName().showName); + Poster poster = show.getPoster(); + Image image = poster.getImage(); + this.poster.setImage(image); + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/ezwatchlist/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/ezwatchlist/ui/ResultDisplay.java index 7d98e84eedf..f7f5d52c5fc 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/ezwatchlist/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/ezwatchlist/ui/SearchPanel.java b/src/main/java/seedu/ezwatchlist/ui/SearchPanel.java new file mode 100644 index 00000000000..b7a38ed8225 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/SearchPanel.java @@ -0,0 +1,54 @@ +package seedu.ezwatchlist.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.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.model.show.Show; + +/** + * A ui for the search panel that is displayed at the side of the application. + */ +public class SearchPanel extends UiPart { + private static final String FXML = "SearchPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(SearchPanel.class); + private MainWindow mainWindow; + private ShowListPanel showListPanel; + + @FXML + private ListView searchListView; + + public SearchPanel(ObservableList searchList) { + super(FXML); + searchListView.setItems(searchList); + searchListView.setCellFactory(listView -> new SearchListViewCell()); + } + + public void setMainWindow(MainWindow mainWindow) { + this.mainWindow = mainWindow; + this.showListPanel = mainWindow.getShowListPanel(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Show} using a {@code SearchShowCard}. + */ + class SearchListViewCell extends ListCell { + @Override + protected void updateItem(Show show, boolean empty) { + super.updateItem(show, empty); + + if (empty || show == null) { + setGraphic(null); + setText(null); + } else { + SearchShowCard searchShowCard = new SearchShowCard(show, getIndex() + 1); + setGraphic(searchShowCard.getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/ui/SearchShowCard.java b/src/main/java/seedu/ezwatchlist/ui/SearchShowCard.java new file mode 100644 index 00000000000..f893cf85b4a --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/SearchShowCard.java @@ -0,0 +1,122 @@ +package seedu.ezwatchlist.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +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 javafx.scene.layout.VBox; + +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.Show; + +/** + * An UI component that displays information of a {@code Show}. + */ +public class SearchShowCard extends UiPart { + + private static final String FXML = "SearchShowListCard.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 Show show; + private int displayedIndex; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label type; + @FXML + private Label dateOfRelease; + @FXML + private Label description; + @FXML + private Label runningTime; + @FXML + private Label lastWatched; + @FXML + private HBox actors; + @FXML + private ImageView poster; + @FXML + private HBox genres; + @FXML + private VBox information; + + private MainWindow mainWindow; + + public SearchShowCard(Show show, int displayedIndex) { + super(FXML); + this.show = show; + this.displayedIndex = displayedIndex; + id.setText(displayedIndex + ". "); + name.setText(show.getName().showName); + type.setText("Type: " + show.getType()); + dateOfRelease.setText("Date of Release: " + show.getDateOfRelease().value); + description.setText("Description: " + show.getDescription().fullDescription); + runningTime.setText("Running Time: " + Integer.toString(show.getRunningTime().value) + " minutes"); + Poster poster = show.getPoster(); + Image image = poster.getImage(); + this.poster.setImage(image); + + actors.getChildren().add(new Label("Actors: ")); + show.getActors().stream().limit(4) + .sorted(Comparator.comparing(actor -> actor.actorName)) + .forEach(actor -> actors.getChildren().add(new Label(actor.actorName))); + actors.getChildren().stream().forEach(node -> node.getStyleClass().add("cell_small_label")); + + show.getGenres().stream() + .forEach(genre -> genres.getChildren().add(new Label(genre.getGenreName()))); + + setLastWatched(); + } + + public int getDisplayedIndex() { + return displayedIndex; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SearchShowCard)) { + return false; + } + + // state check + SearchShowCard card = (SearchShowCard) other; + return id.getText().equals(card.id.getText()) + && show.equals(card.show); + } + + private void setLastWatched() { + if (show.getType().equals("Tv Show")) { + if (show.getLastWatchedSeasonNum() == 0) { + lastWatched.setText(""); + } else { + lastWatched.setText("Last Watched: \nSeason " + show.getLastWatchedSeasonNum() + + " Episode " + show.getLastWatchedSeasonEpisode()); + } + } else { + lastWatched.setText(""); + } + } + +} diff --git a/src/main/java/seedu/ezwatchlist/ui/ShowCard.java b/src/main/java/seedu/ezwatchlist/ui/ShowCard.java new file mode 100644 index 00000000000..2618b1f37bc --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/ShowCard.java @@ -0,0 +1,137 @@ +package seedu.ezwatchlist.ui; + +import java.util.Comparator; + +import javafx.beans.value.ChangeListener; +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +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 javafx.scene.layout.VBox; + +import seedu.ezwatchlist.model.show.Poster; +import seedu.ezwatchlist.model.show.Show; + +/** + * An UI component that displays information of a {@code Show}. + */ +public class ShowCard extends UiPart { + + private static final String FXML = "ShowListCard.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 Show show; + private int displayedIndex; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label type; + @FXML + private Label dateOfRelease; + @FXML + private Label description; + @FXML + private Label runningTime; + @FXML + private Label lastWatched; + @FXML + private HBox actors; + @FXML + private CheckBox watched; + @FXML + private ImageView poster; + @FXML + private HBox genres; + @FXML + private VBox information; + + private MainWindow mainWindow; + + public ShowCard(Show show, int displayedIndex) { + super(FXML); + this.show = show; + this.displayedIndex = displayedIndex; + id.setText(displayedIndex + ". "); + name.setText(show.getName().showName); + type.setText("Type: " + show.getType()); + dateOfRelease.setText("Date of Release: " + show.getDateOfRelease().value); + description.setText("Description: " + show.getDescription().fullDescription); + runningTime.setText("Running Time: " + Integer.toString(show.getRunningTime().value) + " minutes"); + Poster poster = show.getPoster(); + Image image = poster.getImage(); + this.poster.setImage(image); + + actors.getChildren().add(new Label("Actors: ")); + show.getActors().stream().limit(4) + .sorted(Comparator.comparing(actor -> actor.actorName)) + .forEach(actor -> actors.getChildren().add(new Label(actor.actorName))); + actors.getChildren().stream().forEach(node -> node.getStyleClass().add("cell_small_label")); + + show.getGenres().stream() + .forEach(genre -> genres.getChildren().add(new Label(genre.getGenreName()))); + + //sets the checkbox selected value to be equal to the watched value of the show + watched.setSelected(show.isWatched().value); + + setLastWatched(); + } + + public void setWatchedListener(ChangeListener changeListener) { + this.watched.selectedProperty().addListener(changeListener); + } + + public CheckBox getWatched() { + return watched; + } + + public int getDisplayedIndex() { + return displayedIndex; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ShowCard)) { + return false; + } + + // state check + ShowCard card = (ShowCard) other; + return id.getText().equals(card.id.getText()) + && show.equals(card.show); + } + + private void setLastWatched() { + if (show.getType().equals("Tv Show")) { + if (show.getLastWatchedSeasonNum() == 0) { + lastWatched.setText(""); + } else { + lastWatched.setText("Last Watched: \nSeason " + show.getLastWatchedSeasonNum() + + " Episode " + show.getLastWatchedSeasonEpisode()); + } + } else { + lastWatched.setText(""); + } + } + +} diff --git a/src/main/java/seedu/ezwatchlist/ui/ShowListPanel.java b/src/main/java/seedu/ezwatchlist/ui/ShowListPanel.java new file mode 100644 index 00000000000..0744dcc4e4e --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/ShowListPanel.java @@ -0,0 +1,81 @@ +package seedu.ezwatchlist.ui; + +import java.util.logging.Logger; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +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.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.show.Show; + +/** + * Panel containing the list of shows. + */ +public class ShowListPanel extends UiPart { + private static final String FXML = "ShowListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ShowListPanel.class); + private MainWindow mainWindow; + + @FXML + private ListView showListView; + + public ShowListPanel(ObservableList showList) { + super(FXML); + showListView.setItems(showList); + showListView.setCellFactory(listView -> new ShowListViewCell()); + } + + public void setMainWindow(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Show} using a {@code ShowCard}. + */ + class ShowListViewCell extends ListCell { + @Override + protected void updateItem(Show show, boolean empty) { + super.updateItem(show, empty); + + if (empty || show == null) { + setGraphic(null); + setText(null); + } else { + ShowCard showCard = new ShowCard(show, getIndex() + 1); + setGraphic(showCard.getRoot()); + showCard.setWatchedListener(new ChangeableCheckBox(showCard.getDisplayedIndex())); + } + } + } + + /** + * This class prevents the user from marking the checkbox by clicking + * + * @author AxxG "How to make checkbox or combobox readonly in JavaFX" + */ + class ChangeableCheckBox implements ChangeListener { + private int displayedIndex; + + ChangeableCheckBox(int displayedIndex) { + super(); + this.displayedIndex = displayedIndex; + } + + @Override + public void changed(ObservableValue ov, Boolean oldVal, Boolean newVal) { + try { + mainWindow.executeCommand("watch " + displayedIndex); + } catch (CommandException | ParseException | OnlineConnectionException | NoRecommendationsException e) { + mainWindow.getResultDisplay().setFeedbackToUser(e.getMessage()); + } + } + } +} diff --git a/src/main/java/seedu/ezwatchlist/ui/StatisticsPanel.java b/src/main/java/seedu/ezwatchlist/ui/StatisticsPanel.java new file mode 100644 index 00000000000..3064f77c82f --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/StatisticsPanel.java @@ -0,0 +1,147 @@ +package seedu.ezwatchlist.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import seedu.ezwatchlist.model.show.Movie; +import seedu.ezwatchlist.model.show.Show; +import seedu.ezwatchlist.model.show.TvShow; + + +/** + * An UI for the statistics panel. + */ +public class StatisticsPanel extends UiPart { + private static final String FXML = "StatisticsPanel.fxml"; + + @FXML + private ListView forgottenView; + @FXML + private HBox favouriteGenres; + @FXML + private StackPane forgottenPlaceHolder; + @FXML + private ListView movieRecommendationView; + @FXML + private ListView tvRecommendationView; + @FXML + private StackPane recommendationPlaceHolder; + + public StatisticsPanel(ObservableList forgotten, ObservableMap favourite, + ObservableList movieRecommendations, ObservableList tvRecommendations) { + //forgotten + super(FXML); + if (forgotten.size() > 0) { + forgottenView.setItems(forgotten); + forgottenView.setCellFactory(listView -> new StatisticsViewCell()); + } else { + forgottenPlaceHolder.getChildren().add(new Label("You currently do not have any forgotten entry!")); + } + //favourite + if (favourite.size() > 0) { + List favouriteKeys = new ArrayList<>(); + favouriteKeys.addAll(favourite.keySet()); + Collections.sort(favouriteKeys, (key1, key2) -> favourite.get(key2) - favourite.get(key1)); + favouriteKeys.stream().forEach(genre -> favouriteGenres.getChildren() + .add(new Label(genre + " (" + favourite.get(genre) + " entries) "))); + } else { + favouriteGenres.getChildren().add(new Label("You currently have no favourite genres")); + } + if (movieRecommendations == null && tvRecommendations == null) { + recommendationPlaceHolder.getChildren().add(new Label("You currently do not have any recommendation!")); + } else { + if (movieRecommendations != null && movieRecommendations.size() > 0) { + movieRecommendationView.setItems(movieRecommendations); + movieRecommendationView.setCellFactory(listView -> new MovieRecommendationViewCell()); + } + if (tvRecommendations != null && tvRecommendations.size() > 0) { + tvRecommendationView.setItems(tvRecommendations); + tvRecommendationView.setCellFactory(listView -> new TvRecommendationViewCell()); + } + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Show} using a {@code ShowCard}. + */ + class StatisticsViewCell extends ListCell { + @Override + protected void updateItem(Show show, boolean empty) { + super.updateItem(show, empty); + + if (empty || show == null) { + setGraphic(null); + setText(null); + } else { + ShowCard showCard = new ShowCard(show, getIndex() + 1); + setGraphic(showCard.getRoot()); + showCard.setWatchedListener(new NonChangeableCheckBox(showCard.getWatched(), show)); + } + } + } + /** + * Custom {@code ListCell} that displays the graphics of a {@code Movie} using a {@code RecommendationCard}. + */ + class MovieRecommendationViewCell extends ListCell { + @Override + protected void updateItem(Movie movie, boolean empty) { + super.updateItem(movie, empty); + + if (empty || movie == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RecommendationCard(movie).getRoot()); + } + } + } + /** + * Custom {@code ListCell} that displays the graphics of a {@code Movie} using a {@code RecommendationCard}. + */ + class TvRecommendationViewCell extends ListCell { + @Override + protected void updateItem(TvShow tv, boolean empty) { + super.updateItem(tv, empty); + + if (empty || tv == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RecommendationCard(tv).getRoot()); + } + } + } + + /** + * This class prevents the user from marking the checkbox by clicking + * + * @author AxxG "How to make checkbox or combobox readonly in JavaFX" + */ + class NonChangeableCheckBox implements ChangeListener { + private CheckBox checkBox; + private Show show; + + public NonChangeableCheckBox (CheckBox checkBox, Show show) { + this.show = show; + this.checkBox = checkBox; + } + + @Override + public void changed(ObservableValue ov, Boolean oldVal, Boolean newVal) { + this.checkBox.setSelected(show.isWatched().value); + } + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/ezwatchlist/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/ezwatchlist/ui/StatusBarFooter.java index 7e17911323f..cf805610652 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/ezwatchlist/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/ezwatchlist/ui/Ui.java similarity index 84% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/ezwatchlist/ui/Ui.java index 17aa0b494fe..879766d0625 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/ezwatchlist/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/ezwatchlist/ui/UiManager.java similarity index 83% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/ezwatchlist/ui/UiManager.java index 876621d79b9..3e91014fc49 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/ezwatchlist/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.ui; import java.util.logging.Logger; @@ -7,10 +7,11 @@ 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.ezwatchlist.MainApp; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.commons.util.StringUtil; +import seedu.ezwatchlist.logic.Logic; +import seedu.ezwatchlist.statistics.Statistics; /** * The manager of the UI component. @@ -20,14 +21,16 @@ 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/applogo.png"; private Logic logic; private MainWindow mainWindow; + private Statistics statistics; - public UiManager(Logic logic) { + public UiManager(Logic logic, Statistics statistics) { super(); this.logic = logic; + this.statistics = statistics; } @Override @@ -38,7 +41,7 @@ public void start(Stage primaryStage) { primaryStage.getIcons().add(getImage(ICON_APPLICATION)); try { - mainWindow = new MainWindow(primaryStage, logic); + mainWindow = new MainWindow(primaryStage, logic, statistics); mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/ezwatchlist/ui/UiPart.java similarity index 93% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/ezwatchlist/ui/UiPart.java index fc820e01a9c..a34f9f1044f 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/ezwatchlist/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.ezwatchlist.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.ezwatchlist.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -63,7 +63,7 @@ public T getRoot() { * @param location Location of the FXML document. * @param root Specifies the root of the object hierarchy. */ - private void loadFxmlFile(URL location, T root) { + protected void loadFxmlFile(URL location, T root) { requireNonNull(location); fxmlLoader.setLocation(location); fxmlLoader.setController(this); @@ -78,7 +78,7 @@ private void loadFxmlFile(URL location, T root) { /** * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. */ - private static URL getFxmlFileUrl(String fxmlFileName) { + protected static URL getFxmlFileUrl(String fxmlFileName) { requireNonNull(fxmlFileName); String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); diff --git a/src/main/java/seedu/ezwatchlist/ui/WatchedPanel.java b/src/main/java/seedu/ezwatchlist/ui/WatchedPanel.java new file mode 100644 index 00000000000..04ab5450e51 --- /dev/null +++ b/src/main/java/seedu/ezwatchlist/ui/WatchedPanel.java @@ -0,0 +1,83 @@ +package seedu.ezwatchlist.ui; + +import java.util.logging.Logger; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +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.ezwatchlist.api.exceptions.NoRecommendationsException; +import seedu.ezwatchlist.api.exceptions.OnlineConnectionException; +import seedu.ezwatchlist.commons.core.LogsCenter; +import seedu.ezwatchlist.logic.commands.exceptions.CommandException; +import seedu.ezwatchlist.logic.parser.exceptions.ParseException; +import seedu.ezwatchlist.model.show.Show; + +/** + * Panel containing the list of shows. + */ +public class WatchedPanel extends UiPart { + private static final String FXML = "WatchedPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(WatchedPanel.class); + private MainWindow mainWindow; + + @FXML + private ListView watchedListView; + + public WatchedPanel(ObservableList showList) { + super(FXML); + watchedListView.setItems(showList); + watchedListView.setCellFactory(listView -> new WatchedListViewCell()); + } + + public void setMainWindow(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Show} using a {@code ShowCard}. + */ + class WatchedListViewCell extends ListCell { + @Override + protected void updateItem(Show show, boolean empty) { + super.updateItem(show, empty); + + if (empty || show == null) { + setGraphic(null); + setText(null); + } else { + ShowCard showCard = new ShowCard(show, getIndex() + 1); + setGraphic(showCard.getRoot()); + showCard.setWatchedListener(new ChangeableCheckBox(showCard.getDisplayedIndex())); + } + } + } + + /** + * This class prevents the user from marking the checkbox by clicking + * + * @author AxxG "How to make checkbox or combobox readonly in JavaFX" + */ + class ChangeableCheckBox implements ChangeListener { + private int displayedIndex; + + ChangeableCheckBox(int displayedIndex) { + super(); + this.displayedIndex = displayedIndex; + } + + @Override + public void changed(ObservableValue ov, Boolean oldVal, Boolean newVal) { + try { + mainWindow.executeCommand("watch " + displayedIndex); + } catch (CommandException | ParseException | OnlineConnectionException | NoRecommendationsException e) { + mainWindow.getResultDisplay().setFeedbackToUser(e.getMessage()); + } + } + } +} + diff --git a/src/main/resources/images/105-1054061_check-mark-comments-check-icon.png b/src/main/resources/images/105-1054061_check-mark-comments-check-icon.png new file mode 100644 index 00000000000..cbbd7194575 Binary files /dev/null and b/src/main/resources/images/105-1054061_check-mark-comments-check-icon.png differ diff --git a/src/main/resources/images/117912.png b/src/main/resources/images/117912.png new file mode 100644 index 00000000000..c329db256d5 Binary files /dev/null and b/src/main/resources/images/117912.png differ diff --git a/src/main/resources/images/55369.png b/src/main/resources/images/55369.png new file mode 100644 index 00000000000..ff649a6e026 Binary files /dev/null and b/src/main/resources/images/55369.png differ diff --git a/src/main/resources/images/61-512.png b/src/main/resources/images/61-512.png new file mode 100644 index 00000000000..57ac51592cb Binary files /dev/null and b/src/main/resources/images/61-512.png differ diff --git a/src/main/resources/images/Avengers__Infinity_War2018423968.png b/src/main/resources/images/Avengers__Infinity_War2018423968.png new file mode 100644 index 00000000000..74ccae8c4f6 Binary files /dev/null and b/src/main/resources/images/Avengers__Infinity_War2018423968.png differ diff --git a/src/main/resources/images/Fantastic_Beasts__The_Crimes_of_Grindelwald-392145351.png b/src/main/resources/images/Fantastic_Beasts__The_Crimes_of_Grindelwald-392145351.png new file mode 100644 index 00000000000..97c825ebaf6 Binary files /dev/null and b/src/main/resources/images/Fantastic_Beasts__The_Crimes_of_Grindelwald-392145351.png differ diff --git a/src/main/resources/images/Fantastic_Beasts_and_Where_to_Find_Them-427997046.png b/src/main/resources/images/Fantastic_Beasts_and_Where_to_Find_Them-427997046.png new file mode 100644 index 00000000000..ab2b5aafd2a Binary files /dev/null and b/src/main/resources/images/Fantastic_Beasts_and_Where_to_Find_Them-427997046.png differ diff --git a/src/main/resources/images/applogo.png b/src/main/resources/images/applogo.png new file mode 100644 index 00000000000..d18b70719ec Binary files /dev/null and b/src/main/resources/images/applogo.png differ diff --git a/src/main/resources/images/clapper.png b/src/main/resources/images/clapper.png new file mode 100644 index 00000000000..de21b277c12 Binary files /dev/null and b/src/main/resources/images/clapper.png differ diff --git a/src/main/resources/images/loading.gif b/src/main/resources/images/loading.gif new file mode 100644 index 00000000000..29343cac625 Binary files /dev/null and b/src/main/resources/images/loading.gif differ diff --git a/src/main/resources/images/poster-placeholder.png b/src/main/resources/images/poster-placeholder.png new file mode 100644 index 00000000000..08777515db0 Binary files /dev/null and b/src/main/resources/images/poster-placeholder.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..dd032f48e7f 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,24 @@ + + + - - + + + + + + + + + + + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..d7776d08185 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,5 +1,5 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(black, 20%); background-color: #383838; /* Used in the default.html file */ } @@ -11,10 +11,11 @@ } .label-bright { - -fx-font-size: 11pt; + -fx-font-size: 14pt; -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: white; + -fx-text-fill: #eeeeee; -fx-opacity: 1; + -fx-font-style: italic; } .label-header { @@ -40,9 +41,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: black; + -fx-control-inner-background: black; + -fx-background-color: black; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -77,43 +78,27 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; -} - -.list-cell:filled:even { - -fx-background-color: #3c3e3f; -} - -.list-cell:filled:odd { - -fx-background-color: #515658; -} - -.list-cell:filled:selected { - -fx-background-color: #424d5f; -} - -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; + -fx-background-color: black; } .list-cell .label { @@ -132,13 +117,25 @@ -fx-text-fill: #010504; } -.stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); +.v-box { + -fx-background-color: black; + -fx-border-color: transparent; } +h-box { + -fx-border-color: transparent; +} +.stack-pane { + -fx-background-color: black; + -fx-border-color: transparent; +} +.scroll-pane { + -fx-background-color: black; + -fx-border-color: transparent; +} .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: black; + -fx-border-color: black; -fx-border-top-width: 1px; } @@ -165,7 +162,7 @@ } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(black, 20%); -fx-border-color: derive(#1d1d1d, 25%); -fx-border-width: 1px; } @@ -175,17 +172,16 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: black; -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: black; } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: black; } .context-menu .label { @@ -193,7 +189,7 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; } .menu-bar .label { @@ -214,31 +210,19 @@ */ .button { -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; + -fx-background-color: transparent; -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; + -fx-font-size: 13pt; + -fx-text-fill: white; -fx-background-insets: 0 0 0 0, 0, 1, 2; + -fx-cursor: hand; + -fx-alignment: center-right; + -fx-font-style: italic } .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; + -fx-cursor: hand } .button:disabled, .button:default:disabled { @@ -256,12 +240,24 @@ -fx-background-color: derive(-fx-focus-color, 30%); } +.button-current { + -fx-padding: 5 22 5 22; + -fx-background-radius: 0; + -fx-background-color: transparent; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 13pt; + -fx-background-insets: 0 0 0 0, 0, 1, 2; + -fx-text-fill: grey; + -fx-cursor: DEFAULT; + -fx-alignment: center-right; + -fx-font-style: italic +} .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: black; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: black; } .dialog-pane > *.label.content { @@ -271,7 +267,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: black; } .dialog-pane:header *.header-panel *.label { @@ -282,11 +278,12 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; + -fx-border-color: transparent; } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(white, 20%); -fx-background-insets: 3; } @@ -320,7 +317,7 @@ #commandTextField { -fx-background-color: transparent #383838 transparent #383838; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: black black #ffffff black; -fx-border-insets: 0; -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; @@ -328,12 +325,12 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #showListPanel, #showWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: black, black, black, black; -fx-background-radius: 0; } @@ -350,3 +347,37 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +.check-box { + /*-fx-selected-box-color: white; + -fx-box-color: white;*/ + -fx-mark-color: black; +} + +.check-box:selected > .box { + /* background color for selected checkbox */ + -fx-background-color: white; +} + +.check-box > .box { + /* background color of unselected checkbox */ + -fx-background-color: white; +} + +.genre-tag .label { + -fx-text-fill: white; + -fx-background-color: #bc0d3e; + -fx-padding: 1 4 1 4; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +.favourite-genre-tag .label { + -fx-text-fill: white; + -fx-background-color: #bc0d3e; + -fx-padding: 1 4 3 4; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 14; +} diff --git a/src/main/resources/view/LoadingPanel.fxml b/src/main/resources/view/LoadingPanel.fxml new file mode 100644 index 00000000000..443936f9f1c --- /dev/null +++ b/src/main/resources/view/LoadingPanel.fxml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..70bb742da2d 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,58 +3,91 @@ + - + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + +