diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 000000000000..e944fee32ba6 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,2 @@ +exclude_paths: + - 'src/test/**/*' diff --git a/.gitignore b/.gitignore index 823d175eb670..f6eeb8fab98a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build/ .checkstyle .settings/ .idea/ +.project +.classpath +.vscode/ lib/* *.iml *.log diff --git a/.travis.yml b/.travis.yml index 1ffe1f2a5c77..ff6789a92334 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: script: >- ./config/travis/run-checks.sh && - travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyDummySearchPage + travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyDummySearchPage --stacktrace deploy: skip_cleanup: true diff --git a/README.adoc b/README.adoc index 450054624f48..96e1de540c4d 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,10 @@ -= Address Book (Level 4) += JxMusic ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103-AY1819S1-T13-3/main[image:https://travis-ci.org/CS2103-AY1819S1-T13-3/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/docsautopublisher/main/branch/master[image:https://ci.appveyor.com/api/projects/status/7dk5fcmu2eb6vugq/branch/master?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-T13-3/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-T13-3/main/badge.svg?branch=master[Coverage Status]] +image:https://api.codacy.com/project/badge/Grade/f91a3abe2719423cbbd035544c121ab3["Codacy code quality", link="https://www.codacy.com/app/NancyQuris/CS2103-AY1819S1-T13-3?utm_source=github.com&utm_medium=referral&utm_content=CS2103-AY1819S1-T13-3/main&utm_campaign=Badge_Grade"] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,13 +14,9 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop music player application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for users that want to listen to music on their computers. +* It is *written in OOP fashion*. == Site Map @@ -33,6 +28,7 @@ endif::[] == Acknowledgements +* This project is built upon https://github.com/se-edu/addressbook-level4. * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..26f7bd417df8 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,31 @@ +{ + "formats": ["adoc", "cs", "css", "fxml", "gradle", "html", "java", "js", "json", "jsp", "md", "py", "tag", "xml"], + "authors": + [ + { + "githubId": "Gongjie663746", + "displayName": "QI GO...NGJIE", + "authorNames": ["Gongjie", "Gongjie663746"] + }, + { + "githubId": "NancyQuris", + "displayName": "LIU Y...NGNAN", + "authorNames": ["Liu Yingnan", "NancyQuris"] + }, + { + "githubId": "hidingmode", + "displayName": "DAVID... CHOO", + "authorNames": ["hidingmode", "David Ch", "David Choo"] + }, + { + "githubId": "HaydenPhillips", + "displayName": "PHILL...N ROY", + "authorNames": ["HaydenPhillips", "Hayden Phillips"] + }, + { + "githubId": "handshou", + "displayName": "HANSE...YOUNG", + "authorNames": ["handshou"] + } + ] +} diff --git a/addressbook.jar b/addressbook.jar new file mode 100644 index 000000000000..b5c1afe9b27f Binary files /dev/null and b/addressbook.jar differ diff --git a/build.gradle b/build.gradle index f8e614f8b49b..79b077ff2870 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ if (JavaVersion.current() == JavaVersion.VERSION_1_10 } // Specifies the entry point of the application -mainClassName = 'seedu.address.MainApp' +mainClassName = 'seedu.jxmusic.MainApp' sourceCompatibility = JavaVersion.VERSION_1_9 targetCompatibility = JavaVersion.VERSION_1_9 @@ -58,6 +58,7 @@ dependencies { String testFxVersion = '4.0.12-alpha' String jUnitVersion = '5.1.0' + implementation 'com.google.code.gson:gson:2.8.5' implementation group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' 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' @@ -82,7 +83,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'jxmusic.jar' destinationDir = file("${buildDir}/jar/") } @@ -134,6 +135,7 @@ test { testLogging { events TestLogEvent.FAILED, TestLogEvent.SKIPPED + exceptionFormat 'full' // Prints the currently running test's name in the CI's build log, // so that we can check if tests are being silently skipped or @@ -157,16 +159,16 @@ test { } if (runNonGuiTests) { - test.include 'seedu/address/**' + test.include 'seedu/jxmusic/**' } if (runGuiTests) { test.include 'systemtests/**' - test.include 'seedu/address/ui/**' + test.include 'seedu/jxmusic/ui/**' } if (!runGuiTests) { - test.exclude 'seedu/address/ui/**' + test.exclude 'seedu/jxmusic/ui/**' } } } @@ -207,9 +209,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-name': 'JxMusic', + 'site-githuburl': 'https://github.com/CS2103-AY1819S1-T13-3/main', ] options['template_dirs'].each { diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..8d9ffb2a1aa5 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +JxMusic was developed by the CS2103-AY1819S1-T13-3 https://github.com/orgs/CS2103-AY1819S1-T13-3/teams/developers[developers team]. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== David Choo +image::hidingmode.png[width="130", align="left"] +{empty}[http://github.com/hidingmode[Github]] [<>] -Role: Project Advisor +Roles: Project lead, developer + +Responsibilities: Player and command implementation, refactoring coordinator ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Hansel +image::damithc.jpg[width="150", align="left"] +{empty}[http://github.com/handshou[Github]] [<>] -Role: Team Lead + -Responsibilities: UI +Roles: Refining lead, developer + +Responsibilities: Model and command implementation ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Liu Yingnan +image::nancyquris.png[width="150", align="left"] +{empty}[http://github.com/NancyQuris[Github]] [<>] -Role: Developer + -Responsibilities: Data +Roles: Design lead, developer + +Responsibilities: UI and command implementation ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Hayden Phillips +image::haydenphillips.png[width="150", align="left"] +{empty}[http://github.com/HaydenPhillips[Github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Roles: Keynote lead, developer + +Responsibilities: Logic refactoring and command implementation ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Qi Gongjie +image::gongjie663746.png[width="150", align="left"] +{empty}[http://github.com/Gongjie663746[Github]] [<>] -Role: Developer + -Responsibilities: UI +Roles: Welfare lead, developer + +Responsibilities: Storage and command implementation ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..2b6b7f907ecb 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,5 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103-AY1819S1-T13-3/main/issues[issuer 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` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 817ec81d7832..1a4e7ed86497 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += JxMusic - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -13,9 +13,9 @@ ifdef::env-github[] :warning-caption: :warning: :experimental: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-T13-3/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `CS2103-AY1819S1-T13-3`      Since: `Aug 2018`      Licence: `MIT` == Setting up @@ -47,14 +47,14 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu . Click `OK` to accept the default settings . Open a console and run the command `gradlew processResources` (Mac/Linux: `./gradlew processResources`). It should finish with the `BUILD SUCCESSFUL` message. + This will generate all resources required by the application and tests. -. Open link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson.java`] and link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow.java`] and check for any code errors +. Open link:{repoURL}/blob/master/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow.java`] and check for any code errors .. Due to an ongoing https://youtrack.jetbrains.com/issue/IDEA-189060[issue] with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully .. To resolve this, place your cursor over any of the code section highlighted in red. Press kbd:[ALT + ENTER], and select `Add '--add-modules=...' to module compiler options` for each error -. Repeat this for the test folder as well (e.g. check link:{repoURL}/src/test/java/seedu/address/commons/util/XmlUtilTest.java[`XmlUtilTest.java`] and link:{repoURL}/src/test/java/seedu/address/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) +. Repeat this for the test folder as well (e.g. check link:{repoURL}/blob/master/src/test/java/seedu/address/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) === Verifying the setup -. Run the `seedu.address.MainApp` and try a few commands +. Run the `seedu.jxmusic.MainApp` and try a few commands . <> to ensure they all pass. === Configurations to do before writing code @@ -74,13 +74,13 @@ Optionally, you can follow the <> docume ==== Updating documentation to match your fork -After forking the repo, the documentation will still have the SE-EDU branding and refer to the `se-edu/addressbook-level4` repo. +After forking the repo, the documentation will still have the CS2103-AY1819S1-T13-3 branding and refer to the `CS2103-AY1819S1-T13-3/main` repo. -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level4`), you should do the following: +If you plan to develop this fork as a separate product (i.e. instead of contributing to `CS2103-AY1819S1-T13-3/main`), you should do the following: . Configure the <> in link:{repoURL}/build.gradle[`build.gradle`], such as the `site-name`, to suit your own project. -. Replace the URL in the attribute `repoURL` in link:{repoURL}/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link:{repoURL}/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. +. Replace the URL in the attribute `repoURL` in link:{repoURL}/blob/master/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link:{repoURL}/blob/master/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. ==== Setting up CI @@ -108,8 +108,10 @@ When you are ready to start coding, [[Design-Architecture]] === Architecture +// tag::ArchitectureDiagram_david[] .Architecture Diagram image::Architecture.png[width="600"] +// end::ArchitectureDiagram_david[] The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. @@ -132,6 +134,7 @@ The rest of the App consists of four components. * <>: The command executor. * <>: Holds the data of the App in-memory. * <>: Reads data from, and writes data to, the hard disk. +* <>: Interacts with the audio interface. Each of the four components @@ -144,20 +147,21 @@ For example, the `Logic` component (see the class diagram given below) defines i image::LogicClassDiagram.png[width="800"] [discrete] +// tag::eventDrivenandUI[] ==== Events-Driven nature of the design -The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `playlist del 1`. -.Component interactions for `delete 1` command (part 1) -image::SDforDeletePerson.png[width="800"] +.Component interactions for `playlist del 1` command (part 1) +image::SDforDeletePlaylist.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `LibraryChangedEvent` when the library data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. -.Component interactions for `delete 1` command (part 2) -image::SDforDeletePersonEventHandling.png[width="800"] +.Component interactions for `playlist del 1` command (part 2) +image::SDforDeletePlaylistEventHandling.png[width="800"] [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. @@ -170,11 +174,11 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/jxmusic/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `TrackListPanel` 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`] +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}/blob/master/src/main/java/seedu/jxmusic/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/blob/master/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -182,6 +186,8 @@ The `UI` component, * Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. * Responds to events raised from various parts of the App and updates the UI accordingly. +// end::eventDrivenandUI[] + [[Design-Logic]] === Logic component @@ -192,15 +198,15 @@ image::LogicClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `LibraryParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a playlist) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("playlist del 1")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeletePersonSdForLogic.png[width="800"] +.Interactions Inside the Logic Component for the `playlist del 1` Command +image::LogicComponentSequenceDiagram.png[width="800"] [[Design-Model]] === Model component @@ -213,116 +219,394 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Library data. +* exposes an unmodifiable `ObservableList` and `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. + - + -image:ModelClassBetterOopDiagram.png[width="800"] - [[Design-Storage]] === Storage component .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/seedu/jxmusic/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 xml format and read it back. +* can save the library data in json format and read it back. + +// tag::DesignPlayerComponent_david[] +[[Design-Player]] +=== Player component + +.Structure of the Player Component +image::PlayerClassDiagram.png[width="400"] + +*API* : link:{repoURL}/src/main/java/seedu/jxmusic/player/Player.java[`Player.java`] + +The `Player` component, + +* interfaces with JavaFX Media to play sounds +* handles media control with mp3 files + +// end::DesignPlayerComponent_david[] [[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.jxmusic.commons` package. == Implementation This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== Undo/Redo feature +// tag::ImplementationPlayerComponent_david[] +=== Player Component ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +The Player component is a new component in addition to the existing 4 other components of AddressBook. It handles all audio related functionalities for some of the Command classes to use. Player mainly interacts with the JavaFX media library that is included in Java, so no third party library is involved. -* `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. +The job of the Player is basically forwarding requests of media playback controls to the Playable object. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +===== Singleton Player +Player is implemented as a singleton as only one instance of it is required at any time. While singleton brings about undesired implications such as tighter coupling and lower testability, we ensured that Player is only used by the Command classes and no other parts of the code touches Player. As for testability, we discover that JUnit is not compatible for JavaFX media (details in <>). On the plus side, singleton pattern makes adding dependency very easy which is very helpful as adding dependency into the Logic component of Address Book was tedious. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +.Player component complete class diagram +image::PlayerCompleteClassDiagram.png[width="600"] -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. +[[PlayableStatus]] +===== PlayableStatus +`PlayableStatus` to represents the state of the player, effectively acts as a layer on top of JavaFX’s `MediaPlayer.Status`. -image::UndoRedoStartingStateListDiagram.png[width="800"] +1. `UNINITIALIZED` - The initial state when no track or playlist has been played +2. `PLAYING` - User enters either the `play p/` or `play t/` command +3. `PAUSED` - User enters the `pause` command +4. `STOPPED` - User enters the `stop` command +5. `ERROR` - Any other states of the `MediaPlayer.Status` which JxMusic is not concerned with -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. +.`PlayableStatus` state chart diagram +image::PlayableStatusStateChartDiagram.png[width="300"] -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] +[[Skipped-Tests]] +===== Skipped Tests -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`. +JUnit and JavaFX MediaPlayer does not work well together. Running any test that constructs a MediaPlayer object (ie `new MediaPlayer`) will throw `IllegalStateException: Toolkit not initialized`. In order to resolve the exception, it requires calling `Platform.startup()` before `new MediaPlayer` is called. Even so, the tests will not work on Travis nor Appveyor, throwing `MediaException: Cannot create player!` thus failing the builds. It is suspected to be due to incompatibility or lack of support for JavaFX Media on their test servers since JavaFX Media has its own link:https://www.oracle.com/technetwork/java/javafx/downloads/supportedconfigurations-1506746.html[dependencies (at the bottom of link)]. -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +Therefore, any test that depends on MediaPlayer are skipped by using `Assume.assumeNoException(mediaException)`. -[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`. +[[Notable-Issues-JavaFXMedia]] +===== Notable Issues with JavaFX Media -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. +1. JavaFX Media does not work for mp3 files that has photoshopped album art. + +* This issue has been reported as a link:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8210828[confirmed Java bug]. +2. Playing a track on MacOS requires setting the MediaPlayer current time to 0 before calling `play()` as it jumps to the end of media for no reason. Whereas on Windows, calling `MediaPlayer.play` works. +* This issue is addressed at link:{repoUrl}/issues/35[#35]. -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +==== Design Considerations +===== Aspect: Choice of media player library +* **Alternative 1 (current choice):** Use JavaFX Media API +** Pros: +*** No additional dependency (link:https://www.oracle.com/technetwork/java/javafx/downloads/supportedconfigurations-1506746.html[at least on common operating systems]) +** Cons: +*** See <> + +* **Alternative 2:** Use 3rd party mp3 player library such as JLayer +** Pros: +*** Possibly less issues than JavaFX Media. +** Cons: +*** Additional dependency +*** Existing mp3 libraries for java are old and badly documented. + +// end::ImplementationPlayerComponent_david[] + +// tag::ReadLibrary[] +=== ReadLibrary +==== Current Implementation +The `readLibrary` is a part of `JsonLibraryStorage`. It reads the Json file as well as the library folder when initialising the `library` class. It calls the following operations: -[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. +* `JsonFileStorage.loadDataFromFile(filepath)` - deserialize the Json file and create `library` class. +* `Trackscanner.scan(libraryDir)` - scan through the library folder and fill all the valid mp3 files into a set of tracks. -The following sequence diagram shows how the undo operation works: +No matter whether the Json file exists, `trackscanner` will run first to make sure the `library` class contains the tracks set. Then if the file path exists, it will deserialize the json file to initialize library’s playlist information. The deserializer and serializer are programmed with `Gson` library. + -image::UndoRedoSequenceDiagram.png[width="800"] +The `trackscanner` navigates to the library folder and extracts the mp3 files and add them to the set of tracks. However there are cases that the user does not have a library folder in the same directory as the app. To ensure the tracks contained inside the playlist to be valid and navigable, we put a library folder containing default tracks inside the resources folder. +Hence, the program first extracts the library folder from the jar file and copy it to the directory. There are two methods that the app copies library to the directory: -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. +* Case 1: If the MainApp is run using jar file, `trackscanner` will internally run the unzip command to unzip the jar file. (Warning: This only works if the jar file is opened using Terminal); +* Case 2: If the MainApp is run using IDE, `trackscanner` will copy the library folder from resources to the directory; -[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. +.Activity of trackscanner +image::TrackScannerActivityDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: Choice of media player library +* **Alternative 1 (current choice):** Read the Json file from data folder and scan the tracks from library folder at the same time. +** Pros: +*** Enables the library to initialise with all the available tracks in library folder +** Cons: +*** The track treeset in the library class will not update again after initialisation. + +* **Alternative 2:** Do not scan the tracks in `readLibrary`, scan the library each time the user search for tracks in the library folder. +** Pros: +*** Lead to the files’ latest status inside the library folder + +==== Future Enhancements + +When the user double clicks the jar file it cannot run because the unzipping of jar file is done using `process.exec`, which requires the user to run the jar file using terminal. This needs to be addressed in future development. +// end::ReadLibrary[] + +// tag::Tracksearchandlist[] +=== Track search and Track list +==== Current Implementation +The `tracklist` and `tracksearch` commands are implemented to view and filter tracks in the library. + +* `Command.TrackListCommand` - list all the tracks inside the library +* `Command.TrackSearchCommand` - search the desired tracks from library whose name contains any of the argument keywords. + +.Sequence of track list command +image::TrackListCommandSequenceDiagram.png[width="800"] + +.Activity of track search command +image::TrackSearchCommandSequenceDiagram.png[width="800"] + + +==== Design Considerations +===== Aspect: Searching multiple keywords. +* **Alternative 1 (current choice):** Combines each search results by each keywords separated by space. +** Pros: +*** Find tracks that match with any of the keywords +** Cons: +*** Spaces are not included in the keywords. +* **Alternative 2:** consider words as one part of the desired track and show results of the search. +** Pros: +*** Show more precise results + +===== Aspect: Tracks listed display +* **Alternative 1 (current choice):** List the tracks in a new panel separate from playlist panel. +** Pros: +*** Tracks are more clearly shown. +* **Alternative 2:** Show the tracks in the message box +** Pros: +*** Easier to implement. +** Cons: +*** Takes up the message box and hence the success message or failing message cannot be seen. + +==== Future Enhancements +Future development can combine the two alternatives so that the user can search with keywords including spaces when they intend to. +// end::Tracksearchandlist[] + +// tag::Trackaddandremove[] +=== Track Adding and Removing Feature +Simple-to-use track management commands that add tracks to a specified playlist. + +==== Current Implementation +When managing a playlist, you can customise it by adding and removing tracks from it. + +.Sequence diagram: track add command +image::TrackAddCommandSequenceDiagram.png[width="720"] + +.Activity diagram: track add command +image::TrackAddCommandActivityDiagram.png[width="720"] + +.Sequence diagram: track delete command +image::TrackDeleteCommandSequenceDiagram.png[width="720"] + +==== Design Considerations +To add new tracks to a playlist can be a very mechanical task, hence the ease of allowing for adding multiple entries helps to ease the need for repetition. It also makes sense for the inclusion of adding tracks by referring to its index on the panel. + +Hence, there are two ways that tracks can be added. +Firstly, with track name. +Secondly, with its index from filteredTrackList panel. -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. +The omission of repeating the index prefix is also added to reduce typing required to perform deletion. Having to type the prefix with every index means including up to 200% more typing per index to add. -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] +Deleting tracks does not support deletion in multiples, and it omits the need for index prefix. -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. +While tracks can be added by name, it makes sense to allow the user to make case-insensitive references to tracks. This helps to reduce the need to follow case convention for track names. -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] +==== Future Enhancements +Deleting multiple tracks could be implemented as more advanced features are added. + +Adding multiple tracks by substrings can be implemented to facilitate quick modification of playlists. +// end::Trackaddandremove[] + +=== Playback Feature +Four functionalities are implemented to achieve the playback task in JxMusic Player. `play` enables users to play musics. `pause` allows the break off of playing and `stop` terminates playing. `seek` functionality achieves the requirement of seeking the playing to a certain time point. To aid the use of `seek`, `duration` command is implemented to let users get the duration of track. + +// tag::ImplementationPlaybackFeaturePlay_david[] +==== Play +PlayCommand caters for commands having the word `play` in it, in particular, `play`, `play p/` and `play t/` commands. It consists of 5 modes: + +1. Continue from pause - `play` +2. Default playlist - `play p/` +3. Specific playlist - `play p/` +4. Default track - `play t/` +5. Specific track - `play t/` + +These modes are determined by the different PlayCommand constructors called by the PlayCommandParser after it parses the user input, as shown in the activity diagram below: + +.Play command activity diagram +image::PlayCommandActivityDiagram.png[width="600"] + +Playing a playlist constructs a PlayablePlaylist which is essentially playing a list of PlayableTrack. The currently playing track is pointed by the `currentIndex`. + +image::PlayablePlaylistObjectDiagram1.png[width="600"] + +When a track finishes playing, the next track is played. + +image::PlayablePlaylistObjectDiagram2.png[width="600"] + +Future enhancement: implement `prev()` for playing the previous track. + +image::PlayablePlaylistObjectDiagram3.png[width="600"] +// end::ImplementationPlaybackFeaturePlay_david[] + +===== Design Considerations +===== Aspect: Initialization stage of PlayablePlaylist +* **Alternative 1 (current choice):** Constructs a list of PlayableTrack +** Pros: +*** Better performance when switching tracks +*** Easier implementation +** Cons: +*** Does not cater for playlist changes when it is playing (ie adding/deleting a track while the playlist is playing) +* **Alternative 2:** Constructs PlayableTrack only when it is to be played +** Pros: +*** Caters for playlist changes +** Cons: +*** Slightly worse performance +*** More complex implementation + +// tag::playbackFeature_yingnan[] +==== Pause +`PauseCommand` is implemented to realize the pausing a playing entity. +The playing could be resumed after pause and it will play from the time point at which the playing entity is paused. +Ultimately, `pause()` method in `javafx.scene.media.MediaPlayer` is being called to achieve the performance. + +==== Stop +`StopCommand` is implemented to achieve the termination of playing of a playing entity. +The playing could not be resumed after `stop`, but the entity being played is remembered and `play` command +after `stop` will result in the playing of the entity from the beginning. +Ultimately, `stop()` method in `javafx.scene.media.MediaPlayer` is being called to achieve the performance. + +.Sequence of the `stop` command +image::StopSequenceDiagram.png[width="800"] + +==== Seek +===== Current Implementation +The `seek` functionality enables user to seek the play of a track to a certain time point. +This feature is achieved by the implementation of `SeekCommand` in logic class. +To perform user’s instruction, `SeekCommandParser` is also being implemented. Ultimately, `seek(Duration seekTime)` +method in `javafx.scene.media.MediaPlayer` is being called to achieve the performance. + +===== Design Consideration +===== Aspect: How seek behaves when the time point is beyond start/stop(aka. total) time +* **Alternative 1 (current choice):** throws exception with error message + +.Activity of `seek` +image::SeekMethodActivityDiagram.png[width="800"] +** Pro: +*** User will get notification message if their intention could not be achieved +** Con: +*** Exception will be thrown and the duration information should be made clear to users. + +* **Alternative 2:** follows the specification of `seek(Duration seekTime)` in `javafx.scene.media.MediaPlayer` + +In the documentation of `javafx.scene.media.MediaPlayer.seek(Duration seekTime)`, it specifies the following execution results: + +If seekTime > stop time, seek to stop time. + +If seekTime < start time, seek to start time. + +** Pro: +*** No extra implementation of the retrieval of duration information needs to be done. +** Con: +*** User will not get notification message if their intention of seeking to a point out of playing time fails. + +==== Duration +===== Current Implementation +`DurationCommand` is implemented to enable users to know the duration of current playing track/paused/stopped track. +A message contains the duration of the track will displayed in `ResultDisplay` of the `MainWindow`. + +===== Design Consideration +===== Aspect: How user get to know the duration of a track +* **Alternative 1 (current choice):** implements a command to retrieve the information of duration +** Pro: +*** User will have the control of the display of the duration information. +** Con: +*** Compare with Alternative 2, it is less user friendly since extra effort is needed for users +to get the information of duration. + +* **Alternative 2:** displays the duration of tracks in `trackCard` + +.Proposed UI design with duration shown +image::UIwithDuration.png[width="800"] + +This choice is what we chose at first, but subsequently we find out the some methods could not be tested due to the +backwardness of javafx (Travis and AppVeyor throw `com.sun.media.jfxmedia.mediaexception: could not create player!` +exception while local tests pass) + +** Pro: +*** It is more convenient and intuitive since no extra effort need to be made to get to know the duration information. +** Con: +*** `javafx` feature might be an obstacle when testing. + +// end::playbackFeature_yingnan[] + +// tag::playlistNew_hayden[] +=== Playlist Management feature +==== Current Implementation + +Playlist new - Creates a new playlist which is added to the library + +Playlist del - Deletes the playlist indexed displayed on the playlist panel from the library + +Playlist search - Searches through the library and displays the playlist/s that match the desired String/ sub-String + +Playlist list - Lists all playlists within the library + +The PlaylistNewCommand creates a new Playlist that is added to the Library object if it does not exists. It’s command phrase pattern is `playlist new p/playlist [t/track]...` where `playlist` denotes the name of the playlist and `track` denotes the name of the tracks. +Additionally, it implements the following operations: + +Pattern: +`playlist new p/playlist [t/track]...` +Example: +`playlist new p/Favourites t/Somesong t/Othersong` + +If no tracks are specified, an empty playlist will be created. Otherwise, the list of tracks will be automatically added into the playlist. + +The PlaylistNewCommandParser handles parsing of the user input, specifically for the mandatory playlist name as well as the optional track list. + +If there exists a playlist in the library with the same name as the new playlist, it will be rejected from being added into the library as playlist is identified by its name. + +The following sequence diagram shows how a new playlist is added: + +image::playlistNewCommandSequenceDiagram.png[width="800"] The following activity diagram summarizes what happens when a user executes a new command: -image::UndoRedoActivityDiagram.png[width="650"] +image::PlaylistNewActivityDiagram.png[width="800"] ==== Design Considerations -===== Aspect: How undo & redo executes +===== Aspect: How tracks are added to a new playlist + +* **Alternative 1 (current choice):** When adding tracks to a new playlist - Identify the tracks by full track name. +** Pros: Easy to implement and intuitive for the user. +** Cons: Tracks with long name could take a long time to type. +* **Alternative 2:** Identify the track via index within the track List panel. +** Pros: Can be much faster to add tracks +** Cons: If panel List is large, it could take a long time to find track -* **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. +===== Aspect: Data structure to support adding a Playlist + +* **Alternative 1 (current choice):** New playlists are added to the bottom of the Library. +** Pros: Keeps the most recent / relevant playlists at the highest playlist index number. +** Cons: Hard to find playlist when Library contains a large amount of playlists. +* **Alternative 2:** Playlists are displayed in alphabetical order +** Pros: Easier to find desired playlist within large list. +** Cons: Harder to find recently added playlists +* **Alternative 3:** New playlists display at the top of the playlist panel. +** Pros: Keeps the most recent / relevant playlists at the top and easily accessible. +** Cons: Has larger compute time cost when list is large +// end::playlistNew_hayden[] -===== Aspect: Data structure to support the undo/redo commands -* **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[] // tag::dataencryption[] === [Proposed] Data Encryption @@ -489,14 +773,14 @@ We have two types of tests: . *GUI Tests* - These are tests involving the GUI. They include, .. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. +.. _Unit tests_ that test the individual components. These are in `seedu.jxmusic.ui` package. . *Non-GUI Tests* - These are tests not involving the GUI. They include, .. _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `seedu.jxmusic.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.jxmusic.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.jxmusic.logic.LogicManagerTest` === Troubleshooting Testing @@ -537,430 +821,638 @@ A project often depends on third-party libraries. For example, Address Book depe a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started +== Product Scope -Suggested path for new programmers: +*Target user profile*: -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +* has a need to listen to music on computers +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +*Value proposition*: listen to music and manage playlists faster than a typical mouse/GUI driven app -[[GetStartedProgramming-EachComponent]] -=== Improving each component +[appendix] -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +//tag::userStories_hayden[] +== User Stories -[discrete] -==== `Logic` component +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +|Priority |As a ... |I want to ... |So that I can.... +|`* * *` |user |Add a track | Save them to my library +|`* * *` |user |Delete a track |Refine my library +|`* * *` |user with multiple playlists |Manage playlists |Maintain different playlists +|`* * *` |user with multiple tracks |Manage tracks in the playlist | Customise playlists +|`* * *` |user |play tracks |Listen to them +|`* * *` |user |Pause a track |Stop when I need to +|`* * *` |user |Continue from pause |Continue from set position with current track +|`* * *` |user |Seek a track |Skip parts of a track +|`* * *` |user |Skip a track |Iterate through my playlist and play a track I want to hear +|`* *` |user |Control volume |Listen comfortably +|`* *` |user |Search for a track |Find and a particular track with ease +|`* *` |user |Repeat a playlist |Continue listening when playlist has finished +|`* *` |user |Shuffle a playlist |Listen to playlists in different order +|`*` |user |See usage instructions |Refer to instructions when I need help +|======================================================================= +//end::userStories_hayden[] -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +[appendix] +// tag::useCase_yingnan[] +== Use Cases + +(For all use cases below, the *System* is the `JxMusic` and the *Actor* is the `user`, unless specified otherwise) [discrete] -==== `Model` component +=== Use case: List all playlists -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +*MSS* -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** +1. User requests to list playlists. +2. System displays names of all playlists in library. ++ +Use case ends. [discrete] -==== `Ui` component +=== Use case: Search for a playlist -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. - -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +*MSS* -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** +1. User enters the command `search`. +2. System displays "Enter playlist name:". +3. User enters a sequence of characters. +4. System displays all playlists that include the sequence of characters, in lexicographical order. + -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +Use case ends. -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). +*Extensions* + +[none] +* 4a. There is no list that matches the name. + -**Before** +[none] +** 4a1. System displays "not found" message. + -image::getting-started-ui-result-before.png[width="200"] +Use case ends. + +// end::useCase_yingnan[] + +[discrete] +=== Use case: Create playlist + +*MSS* + +1. User requests to create a new playlist. +2. System creates a new playlist and saves it to library. + -**After** +Use case ends. + +*Extensions* + +[none] +* 2a. The name of the playlist has existed. + -image::getting-started-ui-result-after.png[width="200"] +[none] +** 2a1. System displays an error message. + -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** +Use case ends. -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +[discrete] +=== Use case: Delete a playlist + +*MSS* + +1. User requests to delete a playlist. +2. System deletes the playlist. + -**Before** +Use case ends. + +*Extensions* + +[none] +* 2a. Playlist is not found. + -image::getting-started-ui-status-before.png[width="500"] +[none] +** 2a1. System displays an error message. + -**After** +Use case ends. + +* 2b. Playlist is currently being played. + -image::getting-started-ui-status-after.png[width="500"] +[none] +** 2a1. System stops the playing and deletes the playlist. + -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +Use case ends. [discrete] -==== `Storage` component +=== Use case: Add a track into playlist -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +*MSS* -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +1. User requests to add a track into a playlist. +2. System searches for the track. +3. System searches for the playlist. +4. System adds the track to the playlist. +5. System displays successful message. + -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +Use case ends. -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +*Extensions* -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +[none] +* 2a. The track does not exist. ++ +[none] +** 2a1. System displays an error message. ++ +Use case ends. -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +* 3a. The playlist does not exist. ++ +[none] +** 3a1. System displays an error message. ++ +Use case ends. -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +[discrete] +=== Use case: Delete a track from playlist -Examples: +*MSS* -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +1. User requests to delete a track from a playlist. +2. System deletes the track. ++ +Use case ends. -==== Step-by-step Instructions +*Extensions* -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +[none] +* 2a. The track does not exist. ++ +[none] +** 2a1. System displays an error message. ++ +Use case ends. -**Main:** +[discrete] +=== Use case: Play a track -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +*MSS* -**Tests:** +1. User requests to play. +2. System plays the current track. ++ +Use case ends. -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +*Extensions* -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +[none] +* 2a. No track is in current playlist. ++ +[none] +** 2a1. System goes back to the status before step 1. ++ +Use case ends. -**Main:** +[discrete] +=== Use case: Play a playlist -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +*MSS* -**Tests:** +1. User requests to play a playlist. +2. System searches for the playlist. +3. System plays the tracks of the playlist. ++ +Use case ends. -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +*Extensions* -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +[none] +* 2a. The playlist is not found. ++ +[none] +** 2a1. System displays an error message. ++ +Use case ends. -**Main:** +* 3a. The playlist is empty. ++ +Use case ends. -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +[discrete] +=== Use case: Pause the playing of track and resume the play -**Tests:** +*MSS* -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +1. User requests to pause the playing of track. +2. System pauses the playing. +3. User requests to resume the playing of track. +4. System plays the track from the point it was paused. ++ +Use case ends. -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +[discrete] +=== Use case: Stop the playing of track -**Main:** +*MSS* -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +1. User requests to stop the playing of track +2. System exits the playing of track. ++ +Use case ends. -**Tests:** +[discrete] +=== Use case: Seek time point in a track -. Add test for `Remark`, to test the `Remark#equals()` method. +*MSS* -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +1. User requests to seek the play timeline to a certain point. +2. System jumps to the point and plays from the point. ++ +Use case ends. -**Main:** +*Extensions* -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +[none] +* 2a. The time point is outside the range of timeline. ++ +[none] +** 2a1. System displays an error message and pauses current playing. ++ +Use case ends. -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +[discrete] +=== Use case: Step forward/backward in a track -**Main:** +*MSS* -. Add a new Xml field for `Remark`. +1. User requests to step forward/backward a range of time in the playing of current track. +2. System jumps to the point and plays from the point. ++ +Use case ends. -**Tests:** +*Extensions* -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +[none] +* 2a. The time point is outside the range of timeline. ++ +[none] +** 2a1. System displays an error message and pauses current playing. ++ +Use case ends. -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +[discrete] +=== Use case: Replay a track -**Tests:** +*MSS* -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +1. User requests to replay the track. +2. System plays the track from the beginning. ++ +Use case ends. -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +[discrete] +=== Use case: Navigate playlist -**Main:** +*MSS* -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +1. User requests to skip the next/previous track. +2. System goes to the next/previous track and plays the track. ++ +Use case ends. -**Tests:** +*Extensions* -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +[none] +* 2a. The next/previous track does not exist. ++ +[none] +** 2a1. If "repeat playlist" function is on, system goes to the beginning/ending track of the playlist and plays the track. ++ +Use case ends. ++ +2a2. If "repeat playlist" is off, system pauses current playing. ++ +Use case ends. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. -**Main:** +[discrete] +=== Use case: Switch playing modes -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +*MSS* -**Tests:** +1. User requests to repeat track. +2. System changes to "repeat track" mode and current track will be played repeatedly. +3. User requests to repeat playlist. +4. System changes to "repeat playlist" mode and the current playlist will start over after playing all tracks. +5. User enters the command `repeat off`. +6. System changes to "repeat off" mode and the play will stop after playing all the tracks. ++ +Use case ends. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[discrete] +=== Use case: Shuffle playlist -==== Full Solution +*MSS* -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +1. User requests to shuffle the playlist. +2. System randomly reorders the sequence of tracks inside the playlist. ++ +Use case ends. [appendix] -== Product Scope - -*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 +== Non Functional Requirements -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +. Should work on any <> as long as it has Java `9` or higher installed. +. Should only support .mp3 file. +. Should be able to hold up to 1000 playlists and 1000 tracks in total 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. [appendix] -== User Stories - -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +== Glossary -[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 +[[mainstream-os]] Mainstream OS:: +Windows, Linux, Unix, macOS +Track:: music +Playlist:: list of tracks +Library:: list of playlists and also name of folder which contains all tracks .mp3 files and library.json -|`* * *` |user |add a new person | +[appendix] +// tag::InstructionsForManualTesting_david[] +== Instructions for Manual Testing -|`* * *` |user |delete a person |remove entries that I no longer need +Given below are instructions to test the app manually. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +Positive [green]#test cases# (for normal usage scenario) are in green and negative [red]#test cases# (for usage scenarios trying to break the app) are in red. -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +=== Launch -_{More to be added}_ +. Initial launch -[appendix] -== Use Cases +.. Download the jar file and copy into an empty folder +.. Open the terminal/command prompt/powershell +.. Enter command `java -jar jxmusic.jar` + + Expected: a “library” folder to be generated next to the jar file and shows GUI with sample playlists (as shown below) +... Fallback 1: enter command `unzip jxmusic.jar "library/*"` then double click the jar file +... Fallback 2: download and unzip link:{repoUrl}/releases/download/v1.3/library.zip[library.zip] in the folder containing the jar file, then double click the jar file -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +image::LibraryFolderNextToJar.png[width="300"] -[discrete] -=== Use case: Delete person +image::Ui.png[width="800"] -*MSS* +[NOTE] +Once library folder exists and it contains the sample mp3 files, subsequent launches can be done by double clicking the jar file. -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 -+ -Use case ends. +. Add mp3 files into the library +.. [green]#Test case:# Copy and paste any of the mp3 file in the library folder, then run the jar file. + +Expected: The new track file name appears in the track panel. -*Extensions* +. Add invalid mp3 file into the library +.. [red]#Test case:# Save a text file as "invalid.mp3" in the library folder, then run the jar file. + +Expected: "invalid" does not show up in track panel. -[none] -* 2a. The list is empty. -+ -Use case ends. +=== Playing tracks -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. +You may refer <> for easier understanding of the possible state transitions. -_{More to be added}_ +. Play the tracks in a specific playlist +.. [green]#Test case:# `play p/sound effects` + +Expected: Tracks in "sound effects" playlist start playing sequentially. +[TIP] +The tracks of "sound effects" playlist ("Marbles", "SOS Morse Code", "Service Bell Help") are short (few seconds in duration) sample tracks provided for testing this case. -[appendix] -== Non Functional Requirements +. Play the tracks in the first playlist of the library +.. [green]#Test case:# `play p/` + +Expected: Tracks in the first playlist sorted by its name in the library starts playing. +[TIP] +The command works even when no playlist is displayed in the panel. The first playlist in the library is the first one in the playlist panel when `playlist list` is entered. -. Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -. 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. +. Play a specific track +.. [green]#Test case:# `play t/Marbles` + +Expected: "Marbles" start playing. -_{More to be added}_ +. Play a default track +.. [green]#Test case:# `play t/` + +Expected: The first track sorted by its name in the library starts playing. +[TIP] +Similarly to `play p/`, the command works even when no track is displayed in the panel. The first track in the library is the first one in the track panel when `track list` is entered. -[appendix] -== Glossary +. Play another playlist/track while a playlist/track is playing +.. [green]#Test case:# `play t/Marbles`, `play t/acyort` + +Expected: "Marbles" stops playing and "acyort" starts playing. -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +. Play an empty playlist +.. **Prerequisites:** Create an empty playlist with the `playlist new` command +.. [red]#Test case:# `playlist new p/empty`, `play p/` + +Expected: Displays "playlist has no track" error message. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +=== Pausing -[appendix] -== Product Survey +. Pause a playing playlist/track +.. **Prerequisites:** Play a playlist/track with the `play p/[playlist]` or `play t/[track]` command +.. [green]#Test case:# `play p/`, `pause` + +Expected: The playing track is paused. -*Product Name* +. Pause a stopped playlist/track +.. **Prerequisites:** Stop a playlist/track with the `stop` command +.. [red]#Test case:# `play p/`, `stop`, `pause` + +Expected: Displays "no playing track to pause" error message. -Author: ... +. Pause without playing +.. **Prerequisites:** No play command has been entered. Restart app for testing. +.. [red]#Test case:# `pause` + +Expected: Displays "no playing track to pause" error message. -Pros: +=== Unpausing -* ... -* ... +. Unpause a paused playlist/track +.. **Prerequisites:** Pause a playing playlist/track with the `pause` command +.. [green]#Test case:# `play p/`, `pause`, `play` + +Expected: The paused playlist/track resumes playing from where it was paused. -Cons: +. Unpause a stopped playlist/track +.. **Prerequisites:** Stop a playing/paused track with the `stop` command +.. [green]#Test case:# `play p/`, `stop`, `play` + +Expected: The stopped playlist restarts playing from the first track. +.. [green]#Test case:# `play t/`, `stop`, `play` + +Expected: The stopped track restarts playing from the beginning. -* ... -* ... +. Unpause while playing +.. [red]#Test case:# `play p/`, `play` + +Expected: Stays playing. -[appendix] -== Instructions for Manual Testing +. Unpause without playing +.. **Prerequisites:** No play command has been entered. Restart app for testing. +.. [red]#Test case:# `play` + +Expected: Displays "no track paused/stopped" error message. -Given below are instructions to test the app manually. +=== Stopping -[NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +. Stop a playing/paused playlist/track +.. [green]#Test case:# `play p/`, `stop` + +Expected: The playing playlist/track stops playing. +.. [green]#Test case:# `play p/`, `pause`, `stop` + +Expected: The paused playlist/track stops playing. -=== Launch and Shutdown +. Stop while stopped +.. [red]#Test case:# `play p/`, `stop`, `stop` + +Expected: Stays stopped. -. Initial launch +. Stop without playing +.. **Prerequisites:** No play command has been entered. Restart app for testing. +.. [red]#Test case:# `stop` + +Expected: Displays "no track to stop" error message. -.. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +=== Viewing duration of track -. Saving window preferences +[NOTE] +Due to a limitation of the media player library, duration of a track can only be made known once it starts playing. -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. +. View duration of a playing/paused/stopped track +.. [green]#Test case:# `play p/`, `duration` + +Expected: Displays duration of the currently playing track. +.. [green]#Test case:# `play p/`, `pause`, `duration` + +Expected: Displays duration of the currently playing track. +.. [green]#Test case:# `play p/`, `stop`, `duration` + +Expected: Displays duration of the currently playing track. -_{ more test cases ... }_ +. View duration without playing +.. **Prerequisites:** No play command has been entered. Restart app for testing. +.. [red]#Test case:# `duration` + +Expected: Displays "no track playing/paused/stopped" error message. -=== Deleting a person +=== Seeking a track -. Deleting a person while all persons are listed +[TIP] +Use the `duration` command to view the total length of the playing track for testing the following test cases. -.. 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. +[NOTE] +Examples of valid TIME: + +`10` (10 sec) + +`1 59` (1 min 59 sec) + +`100` (100 sec = 1 min 40 sec) + +`1 100` (1 min + 100 sec = 2 min 40 sec) + +`1 99 99` (1 hr + 99 min + 99 sec = 2 hr 40 min 39 sec) + +We did not include hour long mp3 file. If you want to test that, you need to add the file into the library folder. + +. Seek within the duration of a track ("aliez" is 4m 26s long) +.. [green]#Test case:# `play t/aliez`, `seek d/100` + +Expected: Seek to 100th second mark and continues playing. +.. [green]#Test case:# `play t/aliez`, `seek d/4 26` + +Expected: Seek to last second mark and plays for 1 second. +.. [green]#Test case:# `play t/aliez`, `pause`, `seek d/100` + +Expected: Seek to 100th second mark and stays paused +.. [green]#Test case:# `play t/aliez`, `stop`, `seek d/100` +Expected: Displays "no playing track to seek" error message. + +. Seek over the duration of a track +.. [red]#Test case:# `play t/aliez`, `seek d/4 27` + +Expected: Displays "time is beyond track's duration" error message. + +. Seek with invalid time input +.. [red]#Test case:# `play t/aliez`, `seek d/-1` + +Expected: Displays "wrong time format" error message. +.. [red]#Test case:# `play t/aliez`, `seek d/-1 0` + +Expected: Displays "wrong time format" error message. +.. [red]#Test case:# `play t/aliez`, `seek d/0 0 0 0` + +Expected: Displays "wrong time format" error message. + +. Seek without playing +.. **Prerequisites:** No play command has been entered. Restart app for testing. +.. [red]#Test case:# `seek d/100` + +Expected: Displays "no playing track to seek" error message. + +=== Creating a new playlist + +. Create an empty playlist +.. [green]#Test case:# `playlist new p/new playlist` + +Expected: An empty playlist named “new playlist” is created + +. Create a playlist with tracks +.. [green]#Test case:# `playlist new p/new playlist 2 t/aliez` + +Expected: A playlist named "new playlist 2" is created, containing "aliez" track. +.. [red]#Test case:# `playlist new p/sound effects t/Marbles t/SOS Morse Code t/Service Bell Help` + +Expected: A playlist named “sound effects” is created, containing tracks “Marbles”, “SOS Morse Code” and “Service Bell Help” + +=== Searching playlists and tracks -_{ more test cases ... }_ +[NOTE] +The "search" commands (ie `playlist search` and `track search`) works the same way except one searches for playlists, the other for tracks. -=== Saving data +. Search with substring +.. [green]#Test case:# `playlist search al` + +Expected: Only playlists with names containing "al" are shown in the playlist panel +.. [green]#Test case:# `track search al` + +Expected: Only tracks with names containing "al" are shown in the track panel -. Dealing with missing/corrupted data files +. Search with multiple substrings +.. [green]#Test case:# `playlist search al i` + +Expected: Only playlists with names containing "al" **or** "i" are shown in the playlist panel +.. [green]#Test case:# `track search al i` + +Expected: Only tracks with names containing "al" **or** "i" are shown in the track panel -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +=== Listing playlists and tracks -_{ more test cases ... }_ +[NOTE] +Similarly to "search" commands, "list" commands (ie `playlist list` and `track list`) works the same way except one for playlists, the other for tracks. + +. List all playlists +.. [green]#Test case:# `playlist list` + +Expected: All playlists displayed with success message + +. List all tracks +.. [green]#Test case:# `track list` + +Expected: All tracks displayed with success message + +=== Adding tracks to playlist + +. Add tracks by track name +.. [green]#Test case:# `track add p/Favourites t/aliez` + +Expected: "Favourites" playlist to be added with "aliez" track. +.. [green]#Test case:# `track add p/Favourites t/aliez t/acyort` + +Expected: "Favourites" playlist to be added with "aliez" and "acyort" tracks. +.. [red]#Test case:# `track add p/non existing t/aliez` + +Expected: Displays "playlist does not exist" error message. +.. [red]#Test case:# `track add p/Favourites t/non existing` + +Expected: Displays "track file does not exist" error message. + +. Add tracks by track index +.. **Prerequisites:** List all tracks using the `track list` command. Multiple tracks in the list. +.. [green]#Test case:# `track add p/Favourites i/1` + +Expected: "Favourites" playlist to be added with first track in panel. +.. [green]#Test case:# `track add p/Favourites i/1 2` + +Expected: "Favourites" playlist to be added with first and second tracks in panel. +.. [red]#Test case:# `track add p/non existing i/1` + +Expected: Displays "playlist does not exist" error message. +.. [red]#Test case:# `track add p/Favourites i/999` + +Expected: Displays "indexes does not exist" error message. + +=== Deleting tracks in a playlist + +. Delete tracks by track index in playlist +.. [green]#Test case:# `track del p/Favourites i/1` + +Expected: First track of "Favourites" playlist is removed from playlist. +.. [red]#Test case:# `track del p/non existing i/1` + +Expected: Displays "playlist does not exist" error message. +.. [red]#Test case:# `track del p/Favourites i/999` + +Expected: Displays "playlist does not have index" error message. + +=== Deleting a playlist + +. Delete a playlist by index +.. **Prerequisites:** List all playlists using the `playlist list` command. Multiple playlists in the list. +.. [green]#Test case:# `playlist del 1` + +Expected: First playlist shown in panel is removed. +.. [red]#Test case:# `playlist del 999` + +Expected: Displays "index provided is invalid" error message. + +=== Thank you for testing our project! <3 +// end::InstructionsForManualTesting_david[] diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..0a012d9b8cc6 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += JxMusic - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,211 +12,496 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S1-T13-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `CS2103-AY1819S1-T13-3` Since: `Aug 2018` Licence: `MIT` +// tag::intro_hayden[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +The JxMusic is a music platform that lets you add to, organize and play your digital audio collection on your computer. The player 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). +//end::intro_hayden[] +// tag::quickStart_yingnan[] == Quick Start . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. Download the latest `jxmusic.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your JxMusic player. +. Open a terminal in the folder, enter the command `java -jar jxmusic.jar`. + - The command should generate a `library` folder next to the jar file containing several sample mp3 files. + - Otherwise, enter `unzip jxmusic.jar "library/*"` + - Alternatively, download the library.zip file and unzip it next to the jar. + - Ensure that you have the library folder with the sample mp3 files otherwise jxmusic won't start. + - Then run `java -jar jxmusic.jar` +. 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. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +. Some example commands you can try with build-in tracks and playlists: -* *`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 +* *`playlist list`* : lists all playlists +* **`playlist new`**`p/playlist1 t/Marbles` : creates a new playlist named `playlist1` with mp3 file `Marbles` to the JxMusic Player. +* **`playlist play`**`p/playlist1` : plays the `playlist1` * *`exit`* : exits the app - . Refer to <> for details of each command. +. To add the new tracks to the library, add the mp3 files to the `library` folder. +. Restart the file so that your new tracks could be scanned and showed in UI. + +//end::quickStart_yingnan[] [[Features]] == Features +// tag::commandFormat[] + ==== *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 `playlist new p/PLAYLIST`, `PLAYLIST` is a parameter which can be used as `playlist new p/playlist1`. +* Items in square brackets are optional e.g `stepback [s/SECONDS]` can be used as `stepback` or as `stepback s/20`. ==== +// end::commandFormat[] + === Viewing help : `help` Format: `help` -=== Adding a person: `add` +// tag::PlayTPlayP_david[] +=== Playing a track : `play t/` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Plays a track. If there is a playlist or track playing, it will be stopped and the track will be played. + +Format: `play t/[TRACK]` -[TIP] -A person can have any number of tags (including 0) +**** +* `TRACK` is an optional parameter, referring to the name of an existing track in the library folder. +* If `TRACK` is not specified, the first track in the library folder sorted by file name will be played. +**** + +Examples: + +* `play t/` + +Plays the first track in the library folder sorted by file name if there is any. +* `play t/Some Song` + +Plays the track named "Some Song" if it exists in the library folder. + +=== Playing a playlist : `play p/` + +Plays a playlist. Similarly to playing a track, if there is a playlist or track playing, it will be stopped and the playlist will be played. + +Format: `play p/[PLAYLIST]` + +**** +* `PLAYLIST` is an optional parameter, referring to the name of an existing playlist in the library folder. +* If `PLAYLIST` is not specified, the first playlist in the library sorted by name will be played. +**** + +Examples: + +* `play p/` + +Plays the first playlist in the library folder if there is any. +* `play p/Favourites` + +Plays the playlist named "Favourites" if it exists in the library folder. +// end::PlayTPlayP_david[] + +// tag::pause_hayden[] + +=== Pausing a playing track : `pause` + +Pauses a playing track. + +Format: `pause` + +Examples: + +* `play t/Some Song` + +`pause` + +The track is paused. + +// end::pause_hayden[] + +// tag::Play_david[] +=== Continuing a paused track : `play` + +Continues a paused track. + +Format: `play` + +Examples: + +* `play t/Some Song` + +`pause` + +`play` + +"Some Song" will continue playing from where it is paused. +// end::Play_david[] + +// tag::command_yingnan[] +=== Stopping a play : `stop` + +Stops the track from playing. + +Format: `stop` 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` +* `play t/Some Song` + +`stop` + +`play` + +"Some song" stops playing and replays from the beginning. + +=== Getting the duration of current playing/paused/stopped track: `duration` -=== Listing all persons : `list` +Displays the duration of current playing/paused/stopped track. + +Format: `duration` -Shows a list of all persons in the address book. + -Format: `list` +* `play t/Some Song` + +`duration` + +The duration of the "Some Song" is displayed on `ResultDisplay` pane. -=== Editing a person : `edit` +=== Seeking time point : `seek` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Steps to the specified time point of the current track. + +Format: `seek d/TIME` **** -* 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. +* `TIME` is in the format of `[[h ]m ]s` each of which represents a unit of time that will be summed up to get the +time point. Only unsigend integers are allowed as time inputs. +* Examples of valid `TIME`: +** `10` (10 sec) +** `1 59` (1 min 59 sec) +** `100` (100 sec = 1 min 40 sec) +** `1 100` (1 min + 100 sec = 2 min 40 sec) +** `1 99 99` (1 hr + 99 min + 99 sec = 2 hr 40 min 39 sec) **** 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. +* `play t/Some Song` + +`seek d/1 10` + +The track seeks to the 1 min 10 sec point and starts from there. +* `play t/Some Song` + +`seek d/100` + +The track seeks to the 1 min 40 sec point and starts from there. +* `play t/Some Song` + +`seek d/200000` + +If `TIME` is longer than the song duration, the seek command fails. + +// end::command_yingnan[] + +=== Listing all playlists : `playlist list` + +Shows the list of all playlists in the library. + +Format: `playlist list` + +// tag::playlistSearch_hayden[] -=== Locating persons by name: `find` +=== Searching for a playlist : `playlist search` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Searches for playlists by names. + +Format: `playlist search QUERY` **** -* 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` +* `QUERY` is a string to be searched from the names of the playlists, which doesn’t need to be an exact match. **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `playlist search Fav` + +Searches for playlists that has Fav in their names. +// end::playlistSearch_hayden[] -=== Deleting a person : `delete` +// tag::PlaylistNew_david[] +=== Creating a playlist : `playlist new` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Creates a new playlist with specific tracks and saves it into the library. + +Format: `playlist new p/PLAYLIST [t/TRACK]...` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... +* `PLAYLIST` refers to the playlist’s name. +* `TRACK` refers to the track’s name. **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `playlist new p/Favourites t/Some Song t/Some Song 2` + +Creates a new playlist with the name Favourites and adds the tracks named Some Song and Some Song 2. -=== Selecting a person : `select` +// end::PlaylistNew_david[] -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +// tag::playlistDel_hayden[] + +=== Deleting a playlist : `playlist del` + +Removes an existing playlist from the library. + +Format: `playlist del INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* `1, 2, 3, ...` +* `INDEX` refers to the playlist’s index in the playlist panel. **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `playlist del 1` + +Deletes the first playlist in the panel if it exists. +// end::playlistDel_hayden[] -=== Listing entered commands : `history` +// tag::tracklist_gj[] +=== Listing all tracks : `track list` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Shows a list of all tracks in the library. + +Format: `track list` -[NOTE] -==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +// end::tracklist_gj[] -// tag::undoredo[] -=== Undoing previous command : `undo` +// tag::tracksearch_gj[] +=== Searching for a track : `track search` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Searches for tracks by names. + +Format: `track search QUERY` -[NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +**** +* `QUERY` is a string to be searched from the names of the tracks, which doesn’t need to be an exact match. +**** + +Examples: + +* `track search aliez` + +Searches for tracks that has aliez in their names. +// end::tracksearch_gj[] + +// tag::trackcommands[] +=== Adding a track into a playlist : `track add` + +Adds a track into a playlist. Track can be chosen by using its name or its index in the track list panel. + +Format: `track add p/PLAYLIST [t/TRACK]...` +Format: `track add p/PLAYLIST [i/INDEX]...` + +**** +* `PLAYLIST` refers to an existing playlist’s name. +* `TRACK` refers to the name of an existing track in the library folder. +* `INDEX` refers to the index of a track in the track list panel. +**** + +Examples: + +* `track add p/Favourites t/Some Song t/Some Song2` + +Adds the track named "Some Song" and "Some Song2" to the "Favourites" playlist. + +* `track add p/Favourites i/1 2` + +Adds the first and second track in the track list panel to the "Favourites" playlist. + +=== Deleting a track from a playlist : `track del` + +Removes a track from a playlist. Track can is chosen by using its appeared sequence in playlist card. + +Format: `track del p/PLAYLIST INDEX` + +[TIP] +Track is selected by its index **not** its name. + +**** +* `PLAYLIST` refers to an existing playlist’s name. +* `INDEX` refers to the index of the track in the `PLAYLIST`. +**** + +Examples: + +* `track del p/Favourites i/3` + +Deletes the 3rd track in "Favourites" playlist. +// end::trackcommands[] + +// tag::v2.0_gj[] +=== Stepping a track : `step` [coming in v2.0] + +Steps forward by default 10 seconds or specified time in seconds. + +Format: `step [s/SECONDS]` + +**** +* `SECONDS` is the number of seconds to step forward. +* If `SECONDS` is negative, the command behaves similar to `stepback`. +**** + +Examples: + +* `play t/Some Song` + +`step` + +The track moves forward to 10 seconds later of the track and keeps playing. +* `play t/Some Song` + +`step s/100` + +Stepping forward 100 seconds. +* `play t/Some Song` + +`stop s/-100` + +Using negative value for `SECONDS` will apply the same behaviour as `stepback`. The track steps backward 100 seconds. + +=== Stepping back : `stepback` [coming in v2.0] + +Steps back by default 10 seconds or specified time in seconds. + +Format: `stepback [s/SECONDS]` + +**** +* `SECONDS` is the number of seconds to step backward. +* If `SECONDS` is negative, the command behaves similar to `step`. +**** + +Examples: + +* `play t/Some Song` + +`stepback` + +The track moves backward 10 seconds earlier of the track and keeps playing. If the track has just played for less than 10 seconds, the track replays. +* `play t/Some Song` + +`stepback s/100` + +Stepping back 100 seconds. +* `play t/Some Song` + +`stepback s/-100` + +Using negative value for `SECONDS` will apply the same behaviour as `step`. The track steps forward 100 seconds. + +=== Replay a track : `replay` [coming in v2.0] + +Replays a track before the track ends to start from the beginning. + +Format: `replay` + +Examples: + +* `play t/Some Song` + +`seek t/100` + +`replay` + +The track plays from the beginning. + +=== Skipping to the next track : `next` [coming in v2.0] + +Plays the next track. + +Format: `next` + +Examples: + +* `play p/Favourites` + +`next` + +If the last track is being played, the playlist ends and stops. +* `play t/Some Song` + +`repeat track` + +`next` + +“Some Song” will replay. +* `play p/Favourites` + +`repeat playlist` + +`next` + +Go to the next track. Since `repeat playlist` is on, if it is the last track of the playlist playing, the first track of the playlist will play. +* `play p/Favourites` + +`shuffle` + +`repeat playlist` + +`next` + +Go to the next track. Since `shuffle` and `repeat playlist` are on, if it is the last track playing, the next shuffled repeat will play. + +=== Skipping to the previous track : `prev` [coming in v2.0] + +Plays the previous track. + +Format: `prev` Examples: -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + +* `play p/Favourites` + +`prev` + +Plays the previous track. If the current track is first track in playlist, replays the same track since there is no previous track. +* `play t/Some Song` + +`repeat track` + +`prev` + +“Some Song” will replay. +* `play p/Favourites` + +`repeat playlist` + +`prev` + +Go to the previous track. Since `repeat playlist` is on, if it is the first track of the playlist playing, the last track of the playlist will play. +* `play p/Favourites` + +`shuffle` + +`repeat playlist` + +`prev` + +Go to the previous track. Since `shuffle` and `repeat playlist` are on, if it is the first track playing and there was no previous track, the same track repeats. + +=== Repeatedly playing a track : `repeat track` [coming in v2.0] + +Switches the repeat mode to repeatedly play a single track. The command works even without any track playing. Upon running this command, any subsequent track will be played on repeat. + +Format: `repeat track` -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. +Examples: -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +* `play p/Favourites` + +`repeat playlist` + +`repeat track` + +Switches to repeat track mode. The currently playing track will be on repeat. +* `repeat track` + +`play t/Some Song` + +“Some Song” will play on repeat. +* `repeat track` + +`play p/Favourites` + +The first track of the “Favourites” playlist will play on repeat. -=== Redoing the previously undone command : `redo` +=== Repeatedly playing a playlist : `repeat playlist` [coming in v2.0] -Reverses the most recent `undo` command. + -Format: `redo` +Switches the repeat mode to repeatedly play a single playlist. The command works even without any playlist playing. Upon running this command, any subsequent playlist will be played on repeat. + +Format: `repeat playlist` Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `play p/Favourites` + +`repeat track` + +`repeat playlist` + +Switches to repeat playlist mode. “Favourites” will play on repeat. +* `repeat playlist` + +`play p/Favourites` + +“Favourites” will play on repeat. +* `repeat playlist` + +`play t/Some Song` + +“Some Song” will play on repeat. + +=== Turning off repeat mode : `repeat off` [coming in v2.0] -* `delete 1` + -`redo` + -The `redo` command fails as there are no `undo` commands executed previously. +Turns off the repeat mode + +Format: `repeat off` -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + -// end::undoredo[] +Examples: + +* `play p/Favourites` + +`repeat playlist` + +`repeat off` + +Switches off repeating mode. + +=== Shuffling a playlist : `shuffle` [coming in v2.0] + +Switches on shuffle mode to play tracks in random order. + +Format: `shuffle` + +**** +* When a playlist is in playback (either playing or paused), `shuffle` will put the rest of the playlist into random order. +* `shuffle` will have no effect when track repeating mode is on. +**** + +Examples: + +* `shuffle` + +`play p/Favourites` + +“Favourites” will play in random order. +* `play p/Favourites` + +`repeat playlist` + +`shuffle` + +Subsequent tracks will be played in random order. When all the tracks in the playlist has been played, the next repeat will be shuffled. +* `play p/Favourites` + +`repeat track` + +`shuffle` + +The track being repeated will continue to play until `repeat off` only then subsequent tracks will play in random order from the remaining unplayed tracks in the playlist. + +=== Turning off shuffle mode : `shuffle off` [coming in v2.0] + +Turns off the shuffle mode + +Format: `shuffle off` + +Examples: + +* `play p/Favourites` + +`shuffle` + +`shuffle off` + +Switches off shuffling mode. + +// end::v2.0_gj[] === Clearing all entries : `clear` -Clears all entries from the address book. + +Clears all entries from the library. + Format: `clear` === Exiting the program : `exit` @@ -224,37 +509,61 @@ Format: `clear` Exits the program. + Format: `exit` -=== Saving the data - -Address book data are saved in the hard disk automatically after any command that changes the data. + -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 `library` folder it creates with the file that contains the data of your previous `library` folder. +// tag::commandSummary_hansel[] == 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` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` + +=== Commands for controlling playback +* *Play track* : `play t/[TRACK]` +* *Play playlist* : `play p/[PLAYLIST]` +* *Pause track* : `pause` +* *Continue track* : `play` +* *Stop playing* : `stop` +* *Seek time point* : `seek d/TIME` + +eg. `seek d/1 10` +* *Step* : `step [s/SECONDS]` + +eg. `step s/100` +* *Stepback* : `stepback [s/SECONDS]` + +eg. `stepback s/100` +* *Replay* : `replay` +// end::commandSummary_hansel[] + +// tag::commandSummary_yingnan[] +=== Commands for aiding the use of playback commands +* *Get duration* : `duration` + +// end::commandSummary_yingnan[] + +// tag::commandSummary2_hansel[] +=== Commands for controlling sequence of playback +* *Skip to next track* : `next` +* *Skip to previous track* : `prev` +* *Repeat track* : `repeat track` +* *Repeat playlist* : `repeat playlist` +* *Turn off repeat mode* : `repeat off` +* *Shuffle playlist* : `shuffle` +* *Turn off shuffle mode* : `shuffle off` + +=== Commands for managing playlists in library +* *List all playlists* : `playlist list` +* *Search for playlist* : `playlist search QUERY` + +eg. `playlist search Fav` +* *Create playlist* : `playlist new p/PLAYLIST [t/TRACK]...` + +eg. `playlist new p/Favourites t/Some Song t/Some Song 2` +* *Delete playlist* : `playlist del INDEX` + +eg. `playlist del INDEX` + +=== Commands for managing tracks in playlist +* *Add track to playlist* : `track add p/PLAYLIST t/TRACK` + +eg. `track add p/Favourites t/Some Song` +* *Delete track from playlist* : `track del p/PLAYLIST i/INDEX` + +eg. `track del p/PLAYLIST i/1` +* *List all tracks* : `track list` +* *Search for tracks* : `track search QUERY` + +eg. `track search Fav` +// end::commandSummary2_hansel[] diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index d1be2f3b7c3a..585931d46c08 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -83,9 +83,9 @@ The set of code style rules implemented can be found in `config/checkstyle/check * **`allTests`** + Runs all tests. * **`guiTests`** + -Runs all tests in the `seedu.address.ui` and `systemtests` package +Runs all tests in the `seedu.jxmusic.ui` and `systemtests` package * **`nonGuiTests`** + -Runs all non-GUI tests in the `seedu.address` +Runs all non-GUI tests in the `seedu.jxmusic` package * **`headless`** + Sets the test mode as _headless_. The mode is effective for that Gradle run only so it should be combined with other test tasks. diff --git a/docs/diagrams/ArchitectureDiagram.pptx b/docs/diagrams/ArchitectureDiagram.pptx index b0e5a9d0ff55..591402646e29 100644 Binary files a/docs/diagrams/ArchitectureDiagram.pptx and b/docs/diagrams/ArchitectureDiagram.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..d1ebb6e31cb3 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..529f28609377 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..ad9b234f91df 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/PlayActivityDiagram.pptx b/docs/diagrams/PlayActivityDiagram.pptx new file mode 100644 index 000000000000..1054e61833af Binary files /dev/null and b/docs/diagrams/PlayActivityDiagram.pptx differ diff --git a/docs/diagrams/PlayerAndPlayCommandDrawioDiagrams.xml b/docs/diagrams/PlayerAndPlayCommandDrawioDiagrams.xml new file mode 100644 index 000000000000..4f06319fb2bb --- /dev/null +++ b/docs/diagrams/PlayerAndPlayCommandDrawioDiagrams.xml @@ -0,0 +1 @@ +5Vttc+I4DP41zOzdDAx5h49QaJeZtttZunPb+2bAhFydmAtOC/frz05siO1AgRJeyn7oJoqsyJLySJZNxboJ53cxmE4e8AiiilkfzStWp2KaDcelfxlhkRGcOif4cTDKSMaK0A/+g5xY59QkGMGZxEgwRiSYysQhjiI4JBINxDF+l9nGGMlvnQIfaoT+ECCd+lcwIhNONdzm6sF3GPgT/uqGyec3AMNXP8ZJxN9XMa1x+i97HAIhi090NgEj/J4jWd2KdRNjTLKrcH4DETOtMFs27nbN06XeMYzINgNsLxvxBlAChcouomPbU6YeWXCTuP8mTKd2CGI/iCpWiz6tT+f0LyWmM2P0KsHT7Jmde0bgnFQBCnw+bkiVg/FKJr3y+f/pm4McAYRUYBvpdz0mYwyGcEmWh0gSqQEC9S2DWKMIwhMCi1RBMXigslLaVKVNYmYxEcticsb6ee5lYQTHJGdiXTbjndIJfPsjY3vD1Nkb1C5ZC2ZLFMxIgTa6B46r34xGa5GVTq3XFCQzeI6KzSB8/dZJYkACHJ2hfj4kfQJIMhPGY8EHBgjWMvK2epqShuYbjElAk0Mrg7BOCnJtDmidTLM2plxjlGL5OKAYa7XHOCI8tRkmv78FYYBYUvwO0RtkUhlwkBAxpuW78/jNIZ3pAOc5EsfzO4hDSOIFZeFPLYfnFp57XX77nktkTU6b5HKYYXMi4MnTX4peJRB6wXNIcT5xDc16cERTK7/FMZlgH0cAdVfUdpovIZNQl60B5wH5nbt+YSw106G3/0BCFty6ICGYeWAp/B4zD2XDIjqD3+k4zxH3L/xVa609w0k85PqLWobQQIREprG5bfRJDBH9WN7ksqLIwHzoEw6oKktf2g3Zl6aj+ChTio9auakVx2CRY5syhtn69zi2/B7LUMoGld/9HL9pb+a3m9YmfnqRzXAVk0ubbxemplb2BNGMgIiVE0r8supFDssY0izPYIWHEbcu5XbaFaeTwwZe6VhtFULCYDRKYx+BAUTtZdV4gxGO0/eKurEoSt11oLCsiLl2UlVZBBb0a7IN2dTZ3WcDV/F3QxaAx+MZJIpLd3aipTlRR58v7b1qvWZ7Yk3CTV21DuLAqmEqYs0yXGhrDtsxGXi7JIP98b5xaLzfOqE6Fxrk9gGDnMa4J0fjYVCqaktS3TIi3NX8N0xiZie0YNVpEPnX507Hc2V3Ggdxp+koUo0SHOroDj19x0TpXDyACPgX28DIZCeCUKWcCPtsPumo++x6NY/kgubGV6k9XmzSdWq2SC3y2VddkG6/Iq0fYEUqUvdZfa9n0eFMEfsyIWKXZs8Jep4DjBEE59ghO9/W4ppe7Hk1OL8QKrtNBZW9AlT2nAJUtg7RJyxCZbVxGI1abC+P3g0QHr5WWPdObBYabnZ7mxpx3VqxXnMqudXiJst9tAQUO2UfLgHz9hOZ7pOFrZpBbVNxQaa81gbU+3DGB4LW9BP3qJM9vTtz+ryr5T+xO3WReZBVxnwx2YtG7LNnrKnrjgueVZ74Uksuc7Fi1/O04bb576Kx3rGUz76oAnfLqsA9+xKQ4DkGNMVcKgyEcBQAfmwh5XzIEU6ABjTe4SMIYcbWJ3Ha5zq+HiT1asqjOvg8nXkVeNRwnVPikaOZr8xNh9UOdCW//ZwJ2aocFaVUvhwVoFr2DrRjya6ybGe/0tOr1zcLOmDpqbdoL2LPRATmte+ZePqhRF7mXp8XS9oqsRWhZeyUeI3P4uyeJ31knDUvAmdVeHT2xdmG52wWdECcbeo4W6v9eREfqeg/XTnUivNlORdO8wui+3QNf23+LAl0lVV41bDK8KipeVRzYCmd1dxJS1M6aZkO2/EQ6N54vfVJnRLatLYjQ++ypb4rhmuCDEXQ4TC8UdSmPXK4eEcKF/u8wsU1la7cvuHi2XKisEsMl3Ps5W21m96NkhBmG3uV8vbT9b3ny+kj/nrsPfaee6373t/dzun3hZ/uWy+9xztdkafWr36Rgv3nH09PRQ+6P3/++PkF22hNe02+OcoWbqPoINz6zIGnMFJywyqHMKPNKEoRwR3hCAqaYLIFIZdqFNeo/htgQnC4bYIQySCfIBrbrv+W56YllxzmMHZDWRya6k91ts0UTWUj2bIOtTikt6tfrWbsq18GW93/AQ==3VhbU+IwGP01fZTJpddHFHSZcZURnV33LbaxdLY0bBoU/PWbtik0DazKRXbggUlO8n1Nzzn9mtTCF5P5FSfT8XcW0dRCIJpbuGch5ARA/hfAogKwHVRAzJOoguAKGCVvVIEqLp4lEc21iYKxVCRTHQxZltFQaBjhnL3q055Zql91SmJqAKOQpCb6I4nEuEJ9B6zwbzSJx/WVIVAjTyT8HXM2y9T1LISfy181PCF1LjU/H5OIvTYg3LfwBWdMVK3J/IKmBbU1bVXc5YbR5bo5zcRHAlAV8ELSmbr1/t3d7Z1anFjUhAg6l/nOx2KSSgDKJqd58kaeyglA9qcsyURJu3NuOT2JkJlgeSVtEUDSJM5kO5RLo1wCL5SLRHLeVQOCTSWaT0mYZPF90emd2RJ5ZplQFoGu7Ksly2g633jbcEmm9ChlEyr4Qk5RARgr/pU/oa36ryu1XQWNG0LXshHlr3iZecWxbCia11OODcqH193Hwc2Vhdy0IPmJa9y7f2aFG0oazio+u3ICdKfzkox6XLZisaTndLWD2HlXO+9A2tmmdt2HUb936py7OuUIBV/2uDgG5aP72+Hw9DlHMpFGOv46n7sG6Q83g5vB/aB7Pfh1+tTDukgr6l3XYH75xt839Z5BvcE2zaJusckp2EpJniehzj2dJ+JnwXvHdlX3sRjq1INDyhO5sILmUhxJC1+oCN+tgSLmDHRQ3W9HVeuikbGVatEs185mPKSatQThMRWNN6IpRoNtZw3ZNcZpSkTyoi9inQLqCsPCiY3HzNO3ArbfErFavIpqbqVaiTBqJcKtRNUtG4lKQyxv+0MeCXb3SC5XI8wZJXyZpKaVsJSp6SXgehvctJ0vjmoC6Lb2g96WJgi89TV7/x6oj0pfYYJGeQj8VnmA9qb6sPKO16pDB/GOXlOco9rJAx0XNH5oP+6CNurgoPHzdbeBw7kN7tltGctoy2qf0f9oyrYOjsjeUsrl87sp0R61Mw/5n9YuIvmYRqrzv4uEfL0KQwduJxJG7yTao0jmOWc3kRpFGwSoWbTlPhDsvKk7lrYYBLokAdpSW2x3HOghG2IvwBC0vyq4dgdg2/aR5/guAt7BhDfPWvsT3m5v5iFwdt/ON9+z6KhmaBVR2D6AfdgMtvNPM3joIGaQ3dWn3mr66nM67v8FzVZLj5swEP41HCvxCDQ55rHd9lBppazUswMTsGowMiaQ/fUdx+bhQLRIXWk3h8j+5uGZb2ZsnGCft8+ClNlvngBzfDdpneDg+P46jPBfAVcNhK4BUkETDXkDcKRvYEDXoDVNoLIUJedM0tIGY14UEEsLI0LwxlY7c2afWpIUJsAxJmyK/qGJzLq03AH/CTTNupM910hOJP6bCl4X5jzHD863nxbnpPNl9KuMJLwZQcGTE+wF51Kv8nYPTFHb0abtfjyQ9nELKOQSg1AbXAiroYs4Ymi6q3GRqkUpXSfYvjByJScGrwIz7HTQb69m8pHXjsNM5gxXXi8bR2UCvYCQ0I4gE+Uz8BykuKJK23WQtjAN5fkbvW+G8niB0clGpVkZjJiOSHvPAyu4MMTMkxQtIcn7AiT57iey9H0JS/4XYCn4zF5az7B0l+3t+gClj4O3azIq4ViSWEkbvG0RG/FxpoztOeMC9wUv4GMoWt0z5E4YitwZhsL1/zO0eZ8hKJKtuuVxFzNSVTS2SXnIACTWvT/Nf5RgOJefwQQwIunFfi3mkjYnvHCKkfT0ep7Nb/8cdC4qXosYjNX4+n7Hkbe5cySJSEFOHN1q0Ke9qCzdSaO6xLVQDP/Cbm1RcgNVV+iJPomH84xdKO2KCajom7oUTNeXKuJbDuHOCQ+IkFrySn8oKAPCaFqo+mMAgM2/U81N8QXfGoHkalIqHBxapK9qc/i2+pjpiB5Ub3x/zE3HfZUXTAduh48BXbXhgyt4+gc=5VhNj9owEP01kdpDpTghFI7Abrc9VFqJlXo2yZBYNTZyHD7213dMnO+gpSzLLi0HZD/bk5k385wBx5+tdg+KrpOfMgLueG60c/w7x/NGwRC/DbDPgcC1QKxYlEOkAubsGSzoWjRjEaSNjVpKrtm6CYZSCAh1A6NKyW1z21Ly5lPXNIYOMA8p76K/WKSTIiy3wr8Di5PiycS1Kwsa/o6VzIR9nuP5y8MnX17RwpbdnyY0ktsa5N87/kxJqfPRajcDbqgtaMvPfTuyWvqtQOhTDnj5gQ3lGRQeDzkenWY4iM1grV3HnzxyuqcLDk8KIyz2oN1ym41H7wsOE73iOCLlWt0r6+gGlIZdDbJePoBcgVZ73LIrKig/YQuKeON8vq3SQ3y7J6mlZmAxaisiLi1XrODAEtNPkn8KSeQDkOS578jS4BSWvA/Akv+etRT0sNSK9nB9gNmPwptuE6ZhvqahWd3ibYtYjY8l43wmuVQ4F1LAZSgatBlyOwwN3R6GgtHrGRq+zBCIaGJueZyFnKYpC5ukHGUAosa9342/FmDQF5/FFHCq2ab5tugL2j7hUTL0pKSXkCa/5eugMJHKTIVgT9Wv7xcMkXHLkKYqBt0xdMhBGfZJafnaSUuYKUPwDyzWHa4cQFMUuaAX6qicsQh1M2EKUvZs7gRb9Gvj8CGEYOoEd4jQTMs07xPMAcpZLEz60QHA2p+a2mb4Ap/YBS2NUFLUDRPxk5ncfRlcRhzDI8mrXx994mgn+RxxjF4vDqOBud0ulU5kLAXl9xU6xcRuDvfPLWhp2L7NA/88LbUNeaPTtIRk031tm63d4w6P+h0+6teo36+qYnIPzhX2uFNS2wSEyTk2ftg7M8HSxEzxtY1acrxZV+ICFf3p83Wlz2Gpryz8cSsTbo/yBz2l7l9A+eRWW/WyKbhGe0Vutle/Lk0326xfl6ab7Na9Qff3zJt16+Q/aNexNWgQ7BcE/22L0TFEWoYu166TE/t18u/366WM2+l7g4Ydp9Wfd3naqj9I/fs/3VhLj9owEP41kdpDpTghKRwXlm57qLQSK/VskiGxauLIcXjsr++YOO8goi1l6XJA9ufxZOabRwYsd7E9PEmaxj9FCNxy7PBguY+W40w9H781cCwAzzZAJFlYQKQGVuwVDGgbNGchZC1BJQRXLG2DgUgSCFQLo1KKfVtsI3j7qSmNoAesAsr76C8Wqrh0y67x78CiuHwysc3Jmga/IynyxDzPctzN6VMcb2mpy8hnMQ3FvgG5S8tdSCFUsdoeFsA1tSVtxb1vZ04ruyUkaswFp7iwozyH0mKf49V5jotIL1JlW+7DM6dHuubwItHDUgb1VmLGH3UsOYzVluOKVGdNq4yhO5AKDg3IWPkEYgtKHlHkUGZQccMkFHFmxX5fh4e4RiZuhGZiMGoyIqo016zgwhAzTJI7hiRyByQ59juyNBnDknMHLLnvmUveAEsdb0/tA7Q8Ft58HzMFq5QG+nSP3RaxBh8bxvlCcCFxn4gErkPRpMuQ3WPItwcY8qZ/z5B/mSFIwgfd5XEXcJplLGiTcpYBCFt9v+9/w0FvyD+DSeBUsV37bTHktHnCs2BoSUUvIW1+Xa+jIhO5DMDcarbvS4q6tigqI1A9RacYVG6PCsvXXliCXGqCf2CyHvDkBOqkKAp6Lc+WMyahagdMQsZedU8wSZ9qg08ueHPLe0SE5kpkxZygL1DOokSHHw0AzP25zm2GL/AHc6CELpQM64Yl0YvePH6ZXKc4/DPBa7aPoeJwrtA+Zr0opBJ2nz7flnUOG3Vjzmdt0qs33YWCvQbnZY39d0NS1Y5v8WIjZAxL9zgl3ZamURP3PY5Jt6VpaOa++znJmfQnyX82J5GhgfuDDUqO77UJ7vbzsYNSVxGZdRRdb1Ai/RF/cFIiH39Sqrjohu8WoxK5wg8JXQYrIy6kikUkEsqXNTrH0O5OPagup07tDHD4XuXkd3/5eu7byqmryJmOKyckmx4bYiZ7zxs8HTb4rF3TYbvqlCksGFnbuK3/iivE67873eUf diff --git a/docs/diagrams/PlayerComponentClassDiagram.pptx b/docs/diagrams/PlayerComponentClassDiagram.pptx new file mode 100644 index 000000000000..e08ef52939b5 Binary files /dev/null and b/docs/diagrams/PlayerComponentClassDiagram.pptx differ diff --git a/docs/diagrams/PlaylistNewActivityDiagram.pptx b/docs/diagrams/PlaylistNewActivityDiagram.pptx new file mode 100644 index 000000000000..20e329712e08 Binary files /dev/null and b/docs/diagrams/PlaylistNewActivityDiagram.pptx differ diff --git a/docs/diagrams/PlaylistNewSequenceDiagram.pptx b/docs/diagrams/PlaylistNewSequenceDiagram.pptx new file mode 100644 index 000000000000..d994c19d942f Binary files /dev/null and b/docs/diagrams/PlaylistNewSequenceDiagram.pptx differ diff --git a/docs/diagrams/SeekCommandActivityDiagram.pptx b/docs/diagrams/SeekCommandActivityDiagram.pptx new file mode 100644 index 000000000000..c92109fb9a37 Binary files /dev/null and b/docs/diagrams/SeekCommandActivityDiagram.pptx differ diff --git a/docs/diagrams/StopSequenceDiagram.pptx b/docs/diagrams/StopSequenceDiagram.pptx new file mode 100644 index 000000000000..d625ec3ba8a2 Binary files /dev/null and b/docs/diagrams/StopSequenceDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..5b5b08383756 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/TrackAddCommandActivityDiagram.pptx b/docs/diagrams/TrackAddCommandActivityDiagram.pptx new file mode 100644 index 000000000000..2368ea8d5e90 Binary files /dev/null and b/docs/diagrams/TrackAddCommandActivityDiagram.pptx differ diff --git a/docs/diagrams/TrackAddCommandSequenceDiagram.pptx b/docs/diagrams/TrackAddCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..a0a9b3d6d550 Binary files /dev/null and b/docs/diagrams/TrackAddCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/TrackDeleteCommandSequenceDiagram.pptx b/docs/diagrams/TrackDeleteCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..13fe1a4cd5f0 Binary files /dev/null and b/docs/diagrams/TrackDeleteCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/TrackListCommandSequenceDiagram.pptx b/docs/diagrams/TrackListCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..1c32c8c28f4b Binary files /dev/null and b/docs/diagrams/TrackListCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/TrackScannerActivityDiagram.pptx b/docs/diagrams/TrackScannerActivityDiagram.pptx new file mode 100644 index 000000000000..83e4b5cc79d4 Binary files /dev/null and b/docs/diagrams/TrackScannerActivityDiagram.pptx differ diff --git a/docs/diagrams/TrackSearchCommandSequenceDiagram.pptx b/docs/diagrams/TrackSearchCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..696884a484b0 Binary files /dev/null and b/docs/diagrams/TrackSearchCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoActivityDiagram.pptx b/docs/diagrams/UndoRedoActivityDiagram.pptx deleted file mode 100644 index 16fec930cf3f..000000000000 Binary files a/docs/diagrams/UndoRedoActivityDiagram.pptx and /dev/null differ diff --git a/docs/images/Architecture unused.png b/docs/images/Architecture unused.png new file mode 100644 index 000000000000..bdc789000f77 Binary files /dev/null and b/docs/images/Architecture unused.png differ diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index bdc789000f77..4dedce976af7 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png deleted file mode 100644 index 0462b9b7be6e..000000000000 Binary files a/docs/images/DeletePersonSdForLogic.png and /dev/null differ diff --git a/docs/images/DeletePlaylistSdForLogic.png b/docs/images/DeletePlaylistSdForLogic.png new file mode 100644 index 000000000000..e33e534498b6 Binary files /dev/null and b/docs/images/DeletePlaylistSdForLogic.png differ diff --git a/docs/images/LibraryFolderNextToJar.png b/docs/images/LibraryFolderNextToJar.png new file mode 100644 index 000000000000..75346edf53c4 Binary files /dev/null and b/docs/images/LibraryFolderNextToJar.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..97657cd20a9d 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LogicComponentSequenceDiagram.png b/docs/images/LogicComponentSequenceDiagram.png new file mode 100644 index 000000000000..6ecf9ce9982a Binary files /dev/null and b/docs/images/LogicComponentSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..526a2898665c 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PlayCommandActivityDiagram.png b/docs/images/PlayCommandActivityDiagram.png new file mode 100644 index 000000000000..541028502df8 Binary files /dev/null and b/docs/images/PlayCommandActivityDiagram.png differ diff --git a/docs/images/PlayablePlaylistObjectDiagram1.png b/docs/images/PlayablePlaylistObjectDiagram1.png new file mode 100644 index 000000000000..80ac51ea62de Binary files /dev/null and b/docs/images/PlayablePlaylistObjectDiagram1.png differ diff --git a/docs/images/PlayablePlaylistObjectDiagram2.png b/docs/images/PlayablePlaylistObjectDiagram2.png new file mode 100644 index 000000000000..db6deea815e7 Binary files /dev/null and b/docs/images/PlayablePlaylistObjectDiagram2.png differ diff --git a/docs/images/PlayablePlaylistObjectDiagram3.png b/docs/images/PlayablePlaylistObjectDiagram3.png new file mode 100644 index 000000000000..78118c846cb9 Binary files /dev/null and b/docs/images/PlayablePlaylistObjectDiagram3.png differ diff --git a/docs/images/PlayableStatusStateChartDiagram.png b/docs/images/PlayableStatusStateChartDiagram.png new file mode 100644 index 000000000000..a42b73bfd1cf Binary files /dev/null and b/docs/images/PlayableStatusStateChartDiagram.png differ diff --git a/docs/images/PlayerClassDiagram.png b/docs/images/PlayerClassDiagram.png new file mode 100644 index 000000000000..1bbc5ca011f8 Binary files /dev/null and b/docs/images/PlayerClassDiagram.png differ diff --git a/docs/images/PlayerCompleteClassDiagram.png b/docs/images/PlayerCompleteClassDiagram.png new file mode 100644 index 000000000000..03722ab5dd87 Binary files /dev/null and b/docs/images/PlayerCompleteClassDiagram.png differ diff --git a/docs/images/PlaylistNewActivityDiagram.png b/docs/images/PlaylistNewActivityDiagram.png new file mode 100644 index 000000000000..3e37b67df48d Binary files /dev/null and b/docs/images/PlaylistNewActivityDiagram.png differ diff --git a/docs/images/SDforDeletePlaylist.png b/docs/images/SDforDeletePlaylist.png new file mode 100644 index 000000000000..4a8d959349bd Binary files /dev/null and b/docs/images/SDforDeletePlaylist.png differ diff --git a/docs/images/SDforDeletePlaylistEventHandling.png b/docs/images/SDforDeletePlaylistEventHandling.png new file mode 100644 index 000000000000..553c76e06e0e Binary files /dev/null and b/docs/images/SDforDeletePlaylistEventHandling.png differ diff --git a/docs/images/SeekMethodActivityDiagram.png b/docs/images/SeekMethodActivityDiagram.png new file mode 100644 index 000000000000..ef4b0dc956e8 Binary files /dev/null and b/docs/images/SeekMethodActivityDiagram.png differ diff --git a/docs/images/StopSequenceDiagram.png b/docs/images/StopSequenceDiagram.png new file mode 100644 index 000000000000..578f7e956cd2 Binary files /dev/null and b/docs/images/StopSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..a88a6cf99e25 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/TrackAddCommandActivityDiagram.png b/docs/images/TrackAddCommandActivityDiagram.png new file mode 100644 index 000000000000..d85c1142dd82 Binary files /dev/null and b/docs/images/TrackAddCommandActivityDiagram.png differ diff --git a/docs/images/TrackAddCommandSequenceDiagram.png b/docs/images/TrackAddCommandSequenceDiagram.png new file mode 100644 index 000000000000..e88bfa6bceda Binary files /dev/null and b/docs/images/TrackAddCommandSequenceDiagram.png differ diff --git a/docs/images/TrackDeleteCommandSequenceDiagram.png b/docs/images/TrackDeleteCommandSequenceDiagram.png new file mode 100644 index 000000000000..4111d9a90296 Binary files /dev/null and b/docs/images/TrackDeleteCommandSequenceDiagram.png differ diff --git a/docs/images/TrackListCommandSequenceDiagram.png b/docs/images/TrackListCommandSequenceDiagram.png new file mode 100644 index 000000000000..7f9ec5175191 Binary files /dev/null and b/docs/images/TrackListCommandSequenceDiagram.png differ diff --git a/docs/images/TrackScannerActivityDiagram.png b/docs/images/TrackScannerActivityDiagram.png new file mode 100644 index 000000000000..1b2201992283 Binary files /dev/null and b/docs/images/TrackScannerActivityDiagram.png differ diff --git a/docs/images/TrackSearchCommandSequenceDiagram.png b/docs/images/TrackSearchCommandSequenceDiagram.png new file mode 100644 index 000000000000..d3a5610a6a8c Binary files /dev/null and b/docs/images/TrackSearchCommandSequenceDiagram.png differ diff --git a/docs/images/UIwithDuration.png b/docs/images/UIwithDuration.png new file mode 100755 index 000000000000..64d62c8cd3ea Binary files /dev/null and b/docs/images/UIwithDuration.png differ diff --git a/docs/images/Ui mockup.png b/docs/images/Ui mockup.png new file mode 100644 index 000000000000..4e8c328a9d14 Binary files /dev/null and b/docs/images/Ui mockup.png differ diff --git a/docs/images/Ui unused.png b/docs/images/Ui unused.png new file mode 100644 index 000000000000..5ec9c527b49c Binary files /dev/null and b/docs/images/Ui unused.png differ diff --git a/docs/images/Ui unused2.png b/docs/images/Ui unused2.png new file mode 100644 index 000000000000..952967ab2603 Binary files /dev/null and b/docs/images/Ui unused2.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..1d6fab32eadf 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 369469ef176e..0f7ff38ff971 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png deleted file mode 100644 index 55e4138cc64f..000000000000 Binary files a/docs/images/UndoRedoActivityDiagram.png and /dev/null differ diff --git a/docs/images/gongjie663746.png b/docs/images/gongjie663746.png new file mode 100644 index 000000000000..deb76dcef281 Binary files /dev/null and b/docs/images/gongjie663746.png differ diff --git a/docs/images/handshou.png b/docs/images/handshou.png new file mode 100644 index 000000000000..933b3fe4baca Binary files /dev/null and b/docs/images/handshou.png differ diff --git a/docs/images/haydenphillips.png b/docs/images/haydenphillips.png new file mode 100644 index 000000000000..3873229e4b50 Binary files /dev/null and b/docs/images/haydenphillips.png differ diff --git a/docs/images/hidingmode.png b/docs/images/hidingmode.png new file mode 100644 index 000000000000..1bffe9dc0936 Binary files /dev/null and b/docs/images/hidingmode.png differ diff --git a/docs/images/nancyquris.png b/docs/images/nancyquris.png new file mode 100644 index 000000000000..0c7e5d7f64ff Binary files /dev/null and b/docs/images/nancyquris.png differ diff --git a/docs/images/playlistNewCommandSequenceDiagram.png b/docs/images/playlistNewCommandSequenceDiagram.png new file mode 100644 index 000000000000..026fcb3aafec Binary files /dev/null and b/docs/images/playlistNewCommandSequenceDiagram.png differ diff --git a/docs/team/gongjie663746.adoc b/docs/team/gongjie663746.adoc new file mode 100644 index 000000000000..cd6ffc35b4ac --- /dev/null +++ b/docs/team/gongjie663746.adoc @@ -0,0 +1,50 @@ += gongjie663746 - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: JxMusic + +--- + +== Overview + +JxMusic is for those who prefer to use a desktop app for listening to music. More importantly, JxMusic 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). + +== Summary of contributions + +* https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=gongjie663746[*Code contributed*] +* *Major enhancement*: *Storage* component of Jxmusic +** What it does: Enable the app to save the Library’s Playlist information into JSON file, read from library to initialize library class, and scan all valid mp3 files in the /library folder. +** Justification: It is important in this application because it allows the user to have their playlists information stored. Furthermore, it loads the library folder’s mp3 files and display them on the track panel. +** Highlights: This enhancement takes the edge cases into considerations when the library folder is empty or does not exist, it can extract a default library from resource folder of the application. +* *Other Contributions*: +** Minor enhancements: Added Track List Command and Track Search Command, which allow users to navigate the all the tracks available in the library folder, and search tracks using keywords. +** Contributions to project management: +*** self-assigning issues. https://github.com/CS2103-AY1819S1-T13-3/main/issues/40[#40], https://github.com/CS2103-AY1819S1-T13-3/main/issues/37[#37] +*** Contribute to the brainstorming. https://github.com/CS2103-AY1819S1-T13-3/main/issues/24[#24] +** Evidence of helping others: +*** Spotted bugs and edge cases on the track and playlist search command. +*** Found bugs on the Group T12-4's software. https://github.com/CS2103-AY1819S1-T12-4/main/issues/153[#153] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=tracklist_gj] + +include::../UserGuide.adoc[tag=v2.0_gj] + +== 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=ReadLibrary] + +include::../DeveloperGuide.adoc[tag=Tracksearchandlist] + diff --git a/docs/team/handshou.adoc b/docs/team/handshou.adoc new file mode 100644 index 000000000000..46d05119c58c --- /dev/null +++ b/docs/team/handshou.adoc @@ -0,0 +1,69 @@ += Hansel Chia - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: JxMusic + +--- + +== Overview + +JxMusic is a desktop music player application developed as a school project for the learning of Software +Engineering principles. The project is built upon https://se-edu.github.io/addressbook-level4/[AddressBook(Level 4)]. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions +* https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=handshou[*Code contributed*] +* https://github.com/CS2103-AY1819S1-T13-3/main/commits?author=handshou[*Commits contributed*] + +* *Major enhancement*: added *the ability to manage playlist by adding and deleting tracks*, including writing tests +** What it does: allows the user to add or remove tracks. Adding tracks is done in two ways. Firstly, by using index of the track in the panel. Otherwise, by using the track's name. Multiple tracks can be added in one line of command, by track index or track name. Deleting tracks are done by referencing tracks in the playlist card. +** Justification: This feature improves the usability of playlists because a user can initiate multiple tracks into a playlist with one line of command. Tracks can be be deleted too. +** Highlights: The implementation of adding and deleting tracks extend beyond commands, into model, playlist and track. To reduce coupling, implemented methods in Playlist and ModelHelper. + +** Credits: _AddressBook's_ implementation of *rest operator* in https://github.com/se-edu/addressbook-level4/blob/dbb73f0157f52c79d474d6df643e58703b8dcc62/src/main/java/seedu/address/logic/parser/AddCommandParser.java#L56[arePrefixesPresent()]. Applied the idea for multiple parameters to overloading TrackAddCommand() constructor to accept multiple Tracks. + +* *Minor enhancement*: added *playlist* methods, enhanced *track* and *model* methods to support major enhancements. Small changes with helper methods in *CommandTestUtil*, *ParserUtil* and *ModelHelper*. Notable mention: +** *[https://github.com/CS2103-AY1819S1-T13-3/main/blame/master/src/main/java/seedu/jxmusic/model/Playlist.java[Playlist]]* boolean:isEmpty, Playlist:copy, boolean:deleteTrack, boolean:hasTrack, Index:getTrackIndex +** *[https://github.com/CS2103-AY1819S1-T13-3/main/blame/master/src/main/java/seedu/jxmusic/model/Track.java[Track]]* boolean:equals (critical for TrackAdd and TrackDel), int:hashCode + +* *Other contributions*: +** Project management: +*** Managed some issues: https://github.com/CS2103-AY1819S1-T13-3/main/issues/69[#69], +https://github.com/CS2103-AY1819S1-T13-3/main/issues/71[#71] +*** Managed some pull requests: +https://github.com/CS2103-AY1819S1-T13-3/main/pull/60[#60], +https://github.com/CS2103-AY1819S1-T13-3/main/pull/70[#70], +https://github.com/CS2103-AY1819S1-T13-3/main/pull/104[#104], +https://github.com/CS2103-AY1819S1-T13-3/main/pull/107[#107] + +** Community: +*** PRs reviewed: +https://github.com/CS2103-AY1819S1-T13-3/main/pull/61[#61], +https://github.com/CS2103-AY1819S1-T13-3/main/pull/55[#55], +https://github.com/CS2103-AY1819S1-T13-3/main/pull/100[#100] + +*** Reported bugs for other teams in the class: https://github.com/CS2103-AY1819S1-W12-4/main/issues/129[1], https://github.com/CS2103-AY1819S1-W12-4/main/issues/120[2] + +** Tools: +*** Integrated a new Github plugin (CircleCI) to the team repo + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=trackcommands] +include::../UserGuide.adoc[tag=commandSummary_hansel] +include::../UserGuide.adoc[tag=commandSummary2_hansel] + +== 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=Trackaddandremove] diff --git a/docs/team/haydenphillips.adoc b/docs/team/haydenphillips.adoc new file mode 100644 index 000000000000..2da1c86cc103 --- /dev/null +++ b/docs/team/haydenphillips.adoc @@ -0,0 +1,70 @@ += HaydenPhillips - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: JxMusic + +--- + +== Overview + +JxMusic is a desktop music player application developed as a school project for the learning of Software +Engineering principles. The project is built upon https://se-edu.github.io/addressbook-level4/[AddressBook(Level 4)]. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions: + +* *Major enhancement*: +Playlist Management +*** What it does: Allows the ability to add to, delete from, and search for playlists within the music library. +*** Justification: Allows the user to organise their music collection to suit their listening preferences. Users can sort their music by creating playlist based on genre, likeability, age etc. +The user can easily search through large collections of music via the search command. +When the playlist is no longer used or needed, one can simply delete. +*** Highlights: +Playlist names can allow not just single words, but phases. +Searching for a playlist has been made easier by utilising the ability to find matching sub-strings of the playlist names. +Can create a new playlist while simultaneously adding tracks to the playlist + +* *Minor enhancement*: Added the ability to pause a track that is currently playing + +* *Code contributed*: +*https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=haydenphillips&sort=displayName&since=2018-09-12&until=2018-11-08&timeframe=day&reverse=false&repoSort=true[*Code contributed*] + +== Other contributions: + +Project management: + +** Issue Tracker management: +**** Logic Component: https://github.com/CS2103-AY1819S1-T13-3/main/issues/34[#34], https://github.com/CS2103-AY1819S1-T13-3/main/issues/38[#38] +**** Commands implemented: https://github.com/CS2103-AY1819S1-T13-3/main/issues/22[#22], https://github.com/CS2103-AY1819S1-T13-3/main/issues/26[#26] +** Ensure there are no coding standard violations +** Implemented corresponding test cases to cover newly-implemented Playlist management feature. + +Enhancements to existing features: + +** Refactored code throughout the Logic component + +Community: + +** PRs reviewed (with non-trivial review comments) +** Reported bugs and suggestions for other teams in the class + +== Contributions to the User Guide +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=intro_hayden] +include::../UserGuide.adoc[tag=playlistSearch_hayden] +include::../UserGuide.adoc[tag=playlistDel_hayden] +include::../UserGuide.adoc[tag=pause_hayden] + +== 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=playlistNew_hayden] +include::../DeveloperGuide.adoc[tag=userStories_hayden] diff --git a/docs/team/hidingmode.adoc b/docs/team/hidingmode.adoc new file mode 100644 index 000000000000..a10d508cc502 --- /dev/null +++ b/docs/team/hidingmode.adoc @@ -0,0 +1,91 @@ += David Choo - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: JxMusic + +--- + +== Overview + +JxMusic is a desktop music player application developed as a school project for the learning of Software +Engineering principles. The project is built upon https://se-edu.github.io/addressbook-level4/[AddressBook(Level 4)]. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=hidingmode[*Code contributed*] + +* *Major enhancement*: Implemented Player component +** What it does: Handles all audio related functionalities by interfacing with JavaFX Media. +** Justification: This is the core feature of JxMusic as it handles audio related interactions in a music player application. +** Highlights: This enhancement supports new media control commands. Existing mp3 player libraries for Java were briefly evaluated. Before settling on JavaFX Media, I created a https://gist.github.com/hidingmode/7b138634854569400fed9fbd6ec4e0b0[prototype player] to experiment on its API. However, the implementation of Player experienced some setbacks due to unforeseen issues with JavaFX Media. +*** Discovery of https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8210828[confirmed JavaFX Media bug] +*** Resolution of https://github.com/CS2103-AY1819S1-T13-3/main/issues/35[another suspected JavaFX Media bug] +*** Incompatibility of https://github.com/CS2103-AY1819S1-T13-3/main/pull/101/commits/a4c5170c53c234f023e48b3e0ade3654457fd225#diff-197aef6c54da4c4e85898e9156e245b4R37[JavaFX Media and JUnit, and by extension, Travis and AppVeyor]. +*** Asynchronous nature of JavaFX Media + +* *Major enhancement*: Coordinated refactoring process of entire code base +** What it does: Transforms most of the original address book 4 codes to fit as a music player. +** Justification: As the original code base was an address book application, a major overhaul was made to transform it into a music player application. +** Highlights: Written elaborative and detailed issues and PRs. +https://github.com/CS2103-AY1819S1-T13-3/main/issues/34[Guiding principle] was to transform `AddressBook` to `Library`, `Person` to `Playlist`, `Tag` to `Track`. https://github.com/CS2103-AY1819S1-T13-3/main/pull/42[Source refactored] in 2 days and https://github.com/CS2103-AY1819S1-T13-3/main/pull/49[tests refactored] in 3 days. + +* *Major enhancement*: Implemented `play`, `playlist new`, `duration` commands. +** What it does: +*** https://github.com/CS2103-AY1819S1-T13-3/main/pull/63[`play`]: Plays a playlist/track or resume from a pause +*** https://github.com/CS2103-AY1819S1-T13-3/main/commit/ff264c5af1fe731f7bc4c9b641bcd650db6be9fa#diff-23a2194e30915cb772a3d1546d69fc5d[`playlist new`]: Creates a new playlist +*** https://github.com/CS2103-AY1819S1-T13-3/main/commit/bd0e7ebed3283be366708556debad5b3bd9a5708#diff-76e971f55875fb7bcd7f30543550f829[`duration`]: Shows the duration of a playing track +** Justification: These are just some of the many commands the team worked on as a whole. +** Highlights: +`play` command caters for 5 modes of usage - `play p/`, `play p/`, `play t/`, `play t/`, and `play` + + +* *Other contributions*: +** *Minor enhancement*: https://github.com/CS2103-AY1819S1-T13-3/main/pull/49/commits/3ab8ec79d21476eac49d5e91aed4c668c6120ff0#diff-d6dedaa869e5f06f11b19f555ba08a07R33[Modified the parser regex] to cater for command phrases instead of just single command word. +** *Minor enhancement*: Implemented simple https://github.com/CS2103-AY1819S1-T13-3/main/commit/17453bd6a806696c3101d55663ac7395bae31db9#diff-d7278ba9866fb6c2c946f04fee81fbd7[validation checks for tracks using mp3 file header bytes]. +** Test: Written https://github.com/CS2103-AY1819S1-T13-3/main/commit/82c1cdab0b7cad0f62474f3da3f811df83c35f20#diff-197aef6c54da4c4e85898e9156e245b4[test for `play`] command + +* *Project management:* +** Set up https://travis-ci.org/CS2103-AY1819S1-T13-3/main/[Travis] and https://ci.appveyor.com/project/docsautopublisher/main[AppVeyor] +** Managed https://github.com/CS2103-AY1819S1-T13-3/main/releases[releases] `v1.1` - `v1.4` (4 releases) on GitHub +** Used issue tracker as backlog by https://github.com/CS2103-AY1819S1-T13-3/main/issues?utf8=%E2%9C%93&q=is%3Aissue+author%3Ahidingmode[listing user stories] +** Coordinated release branching strategy https://github.com/CS2103-AY1819S1-T13-3/main/pull/52[v1.2], https://github.com/CS2103-AY1819S1-T13-3/main/pull/62[v1.3], https://github.com/CS2103-AY1819S1-T13-3/main/pull/104[v1.4]. + +* *Evidence of technical leadership:* Occasional participation on the module forum +*** https://github.com/nus-cs2103-AY1819S1/forum/issues/40#issuecomment-424299702[feedback to AB4] +*** https://github.com/nus-cs2103-AY1819S1/forum/issues/15[module website] +*** https://github.com/TEAMMATES/teammates/issues/9115[TEAMMATES website] +*** https://github.com/nus-cs2103-AY1819S1/forum/issues/119#issuecomment-432974823[reply to a similar issue I encountered] +*** https://github.com/nus-cs2103-AY1819S1/forum/issues/17[sharing of an issue I thought others might face] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=PlayTPlayP_david] + +include::../UserGuide.adoc[tag=Play_david] + +include::../UserGuide.adoc[tag=PlaylistNew_david] + +== 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=ArchitectureDiagram_david] + +include::../DeveloperGuide.adoc[tag=DesignPlayerComponent_david] + +include::../DeveloperGuide.adoc[tag=ImplementationPlayerComponent_david] + +include::../DeveloperGuide.adoc[tag=ImplementationPlaybackFeaturePlay_david] + +*I also wrote the entire "Instructions for manual testing" section in developer guide which is too long to be included here.* diff --git a/docs/team/nancyquris.adoc b/docs/team/nancyquris.adoc new file mode 100644 index 000000000000..8b764375e1db --- /dev/null +++ b/docs/team/nancyquris.adoc @@ -0,0 +1,76 @@ += Liu Yingnan - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: JxMusic + +--- + +== Overview + +JxMusic is a desktop music player application developed as a school project for the learning of Software +Engineering principles. The project is built upon https://se-edu.github.io/addressbook-level4/[AddressBook(Level 4)]. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. +It is written in Java, and has about 10 kLoC. + +== Summary of contributions +* https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=nancyquris[*Code contributed*] +* *Major enhancement*: +** Designed and implemented the UI component based on AddressBook(Level 4) +*** What it does: It creates a GUI for the product in which the entry for users to interact with the product consists. +*** Justification: This feature functions importantly since the user-product interaction relies on it. +It also provides users with the first impression and perception of this product. +*** Highlights: JavaFX was used to aid the implementation. The collocation of .java and .fxml file +provides a good structure for the implementation of UI component. + +** Implemented `stop`, `seek` and `playlist list` commands +*** What it does: `stop` and `seek` command enables users to stop or seek the playing of tracks. `playlist list` command +lists all playlists in current library. +*** Justification: `stop` and `seek` commands suppelment the playback feature of this product. `playlist list` command +enhances the using of playlist management feature. +*** Highlights: `seek` command takes in special consideration of edge case handling. + +* *Other contributions*: +** Minor Enhancement: implemented corresponding test cases to cover newly-implemented codes + +** Project management: +*** Tools: configured RepoSense and Codacy. +*** Issue Tracker Management: Issues relevant to individual responsibility were assigned and handled. +**** UI component: https://github.com/CS2103-AY1819S1-T13-3/main/issues/34[#34], +https://github.com/CS2103-AY1819S1-T13-3/main/issues/39[#39] +**** Commands implemnented: https://github.com/CS2103-AY1819S1-T13-3/main/issues/22[#22], +https://github.com/CS2103-AY1819S1-T13-3/main/issues/28[#28], +https://github.com/CS2103-AY1819S1-T13-3/main/issues/29[#29] +*** Configured and published https://cs2103-ay1819s1-t13-3.github.io/main/[project website] + +** Evidence of helping others: testing of W10-4's product: https://github.com/CS2103-AY1819S1-W10-4/main/issues/185[#185] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +The launch of jar file and example commands in QuickStart were later revised by https://github.com/hidingmode[David Choo]. + +The skeleton of `stop` and `seek` command was mainly written by https://github.com/hidingmode[David Choo]. + +include::../UserGuide.adoc[tag=quickStart_yingnan] + +include::../UserGuide.adoc[tag=commandFormat] + +include::../UserGuide.adoc[tag=command_yingnan] + +include::../UserGuide.adoc[tag=commandSummary_yingnan] + +== 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=eventDrivenandUI] + +include::../DeveloperGuide.adoc[tag=playbackFeature_yingnan] + +include::../DeveloperGuide.adoc[tag=useCase_yingnan] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..16e95ea41a70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Mon Oct 01 19:14:31 SGT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/library/Haikei Goodbye Sayonara.mp3 b/library/Haikei Goodbye Sayonara.mp3 new file mode 100644 index 000000000000..e8e5231ac6fb Binary files /dev/null and b/library/Haikei Goodbye Sayonara.mp3 differ diff --git a/library/Ihojin no Yaiba.mp3 b/library/Ihojin no Yaiba.mp3 new file mode 100644 index 000000000000..822e43fc6bf5 Binary files /dev/null and b/library/Ihojin no Yaiba.mp3 differ diff --git a/library/Marbles.mp3 b/library/Marbles.mp3 new file mode 100644 index 000000000000..2c916bc79010 Binary files /dev/null and b/library/Marbles.mp3 differ diff --git a/library/SOS Morse Code.mp3 b/library/SOS Morse Code.mp3 new file mode 100644 index 000000000000..6e13b8403693 Binary files /dev/null and b/library/SOS Morse Code.mp3 differ diff --git a/library/Service Bell Help.mp3 b/library/Service Bell Help.mp3 new file mode 100644 index 000000000000..75e3541c1832 Binary files /dev/null and b/library/Service Bell Help.mp3 differ diff --git a/library/acyort.mp3 b/library/acyort.mp3 new file mode 100644 index 000000000000..58c02964814b Binary files /dev/null and b/library/acyort.mp3 differ diff --git a/library/aliez.mp3 b/library/aliez.mp3 new file mode 100644 index 000000000000..a315bc0311ca Binary files /dev/null and b/library/aliez.mp3 differ diff --git a/library/scarborough fair.mp3 b/library/scarborough fair.mp3 new file mode 100644 index 000000000000..de98fda9fa70 Binary files /dev/null and b/library/scarborough fair.mp3 differ diff --git a/library/unsupported.mp3 b/library/unsupported.mp3 new file mode 100644 index 000000000000..78981922613b --- /dev/null +++ b/library/unsupported.mp3 @@ -0,0 +1 @@ +a 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 1deb3a1e4695..000000000000 --- 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/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index c5c8b9ce90ed..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final Person newSelection; - - public PersonPanelSelectionChangedEvent(Person newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - public Person getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/address/commons/util/XmlUtil.java deleted file mode 100644 index a78cd15b7f0c..000000000000 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.commons.util; - -import static java.util.Objects.requireNonNull; - -import java.io.FileNotFoundException; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; - -/** - * Helps with reading from and writing to XML files. - */ -public class XmlUtil { - - /** - * Returns the xml data in the file as an object of the specified type. - * - * @param file Points to a valid xml file containing data that match the {@code classToConvert}. - * Cannot be null. - * @param classToConvert The class corresponding to the xml data. - * Cannot be null. - * @throws FileNotFoundException Thrown if the file is missing. - * @throws JAXBException Thrown if the file is empty or does not have the correct format. - */ - @SuppressWarnings("unchecked") - public static T getDataFromFile(Path file, Class classToConvert) - throws FileNotFoundException, JAXBException { - - requireNonNull(file); - requireNonNull(classToConvert); - - if (!FileUtil.isFileExists(file)) { - throw new FileNotFoundException("File not found : " + file.toAbsolutePath()); - } - - JAXBContext context = JAXBContext.newInstance(classToConvert); - Unmarshaller um = context.createUnmarshaller(); - - return ((T) um.unmarshal(file.toFile())); - } - - /** - * Saves the data in the file in xml format. - * - * @param file Points to a valid xml file containing data that match the {@code classToConvert}. - * Cannot be null. - * @throws FileNotFoundException Thrown if the file is missing. - * @throws JAXBException Thrown if there is an error during converting the data - * into xml and writing to the file. - */ - public static void saveDataToFile(Path file, T data) throws FileNotFoundException, JAXBException { - - requireNonNull(file); - requireNonNull(data); - - if (!Files.exists(file)) { - throw new FileNotFoundException("File not found : " + file.toAbsolutePath()); - } - - JAXBContext context = JAXBContext.newInstance(data.getClass()); - Marshaller m = context.createMarshaller(); - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - - m.marshal(data, file.toFile()); - } - -} 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 8b34b862039a..000000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,27 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; - -/** - * 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 an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ - ListElementPointer getHistorySnapshot(); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index 9aff86fc33dc..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,52 +0,0 @@ -package seedu.address.logic; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.ComponentManager; -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.person.Person; - -/** - * The main LogicManager of the app. - */ -public class LogicManager extends ComponentManager implements Logic { - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final CommandHistory history; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model) { - this.model = model; - history = new CommandHistory(); - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - try { - Command command = addressBookParser.parseCommand(commandText); - return command.execute(model, history); - } finally { - history.add(commandText); - } - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public ListElementPointer getHistorySnapshot() { - return new ListElementPointer(history.getHistory()); - } -} 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 d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +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.CommandHistory; -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, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - 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 1f85bcfe85a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.logic.CommandHistory; -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, CommandHistory history) { - requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index dc782d8e230f..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +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.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.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, CommandHistory history) 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.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - 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 beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -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, CommandHistory history) { - 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/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java deleted file mode 100644 index f1541fb57f20..000000000000 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.Collections; -import java.util.List; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; - -/** - * Lists all the commands entered by user from the start of app launch. - */ -public class HistoryCommand extends Command { - - public static final String COMMAND_WORD = "history"; - public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; - public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(history); - List previousCommands = history.getHistory(); - - if (previousCommands.isEmpty()) { - return new CommandResult(MESSAGE_NO_HISTORY); - } - - Collections.reverse(previousCommands); - return new CommandResult(String.format(MESSAGE_SUCCESS, String.join("\n", previousCommands))); - } - -} 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 6d44824c7d1b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,25 +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.logic.CommandHistory; -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, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java deleted file mode 100644 index 227771a4eef6..000000000000 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ /dev/null @@ -1,31 +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.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previously undone state. - */ -public class RedoCommand extends Command { - - public static final String COMMAND_WORD = "redo"; - public static final String MESSAGE_SUCCESS = "Redo success!"; - public static final String MESSAGE_FAILURE = "No more commands to redo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canRedoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index f5e8c1a8722e..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - List filteredPersonList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java deleted file mode 100644 index 40441264f346..000000000000 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ /dev/null @@ -1,31 +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.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previous state. - */ -public class UndoCommand extends Command { - - public static final String COMMAND_WORD = "undo"; - public static final String MESSAGE_SUCCESS = "Undo success!"; - public static final String MESSAGE_FAILURE = "No more commands to undo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canUndoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.undoAddressBook(); - 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 3b8bfa035e83..000000000000 --- 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 b7d57f5db86a..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,92 +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.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -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 SelectCommand.COMMAND_WORD: - return new SelectCommandParser().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 HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); - - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); - - 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 75b1a9bf1190..000000000000 --- 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 4d1f4bb0e4ec..000000000000 --- 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 an 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 845644b7dea1..000000000000 --- 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/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index 76daf40807e2..000000000000 --- 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_NAME_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_PHONE_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_ADDRESS_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_EMAIL_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_TAG_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/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java deleted file mode 100644 index 565b7f04bfe1..000000000000 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ /dev/null @@ -1,28 +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.SelectCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new SelectCommand object - */ -public class SelectCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the SelectCommand - * and returns an SelectCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public SelectCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe); - } - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 7f85c8b9258b..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void updatePerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index ac4521f33199..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.model; - -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -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; - - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** 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 updatePerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); - - /** - * Returns true if the model has previous address book states to restore. - */ - boolean canUndoAddressBook(); - - /** - * Returns true if the model has undone address book states to restore. - */ - boolean canRedoAddressBook(); - - /** - * Restores the model's address book to its previous state. - */ - void undoAddressBook(); - - /** - * Restores the model's address book to its previously undone state. - */ - void redoAddressBook(); - - /** - * Saves the current address book state for undo/redo. - */ - void commitAddressBook(); -} 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 a664602ef5b1..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,150 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager extends ComponentManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final VersionedAddressBook versionedAddressBook; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; - } - - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); - } - - @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); - } - - @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); - } - - //=========== 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 FXCollections.unmodifiableObservableList(filteredPersons); - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - //=========== Undo/Redo ================================================================================= - - @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); - } - - @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); - } - - @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); - } - - @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); - } - - @Override - public void commitAddressBook() { - versionedAddressBook.commit(); - } - - @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 versionedAddressBook.equals(other.versionedAddressBook) - && 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 6ddc2cd9a290..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java deleted file mode 100644 index 227a335045d7..000000000000 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code AddressBook} that keeps track of its own history. - */ -public class VersionedAddressBook extends AddressBook { - - private final List addressBookStateList; - private int currentStatePointer; - - public VersionedAddressBook(ReadOnlyAddressBook initialState) { - super(initialState); - - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); - currentStatePointer = 0; - } - - /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. - * Undone states are removed from the state list. - */ - public void commit() { - removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); - currentStatePointer++; - } - - private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); - } - - /** - * Restores the address book to its previous state. - */ - public void undo() { - if (!canUndo()) { - throw new NoUndoableStateException(); - } - currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Restores the address book to its previously undone state. - */ - public void redo() { - if (!canRedo()) { - throw new NoRedoableStateException(); - } - currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Returns true if {@code undo()} has address book states to undo. - */ - public boolean canUndo() { - return currentStatePointer > 0; - } - - /** - * Returns true if {@code redo()} has address book states to redo. - */ - public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { - return false; - } - - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; - - // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.currentStatePointer; - } - - /** - * Thrown when trying to {@code undo()} but can't. - */ - public static class NoUndoableStateException extends RuntimeException { - private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); - } - } - - /** - * Thrown when trying to {@code redo()} but can't. - */ - public static class NoRedoableStateException extends RuntimeException { - private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); - } - } -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 38a7629e9a2d..000000000000 --- 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_EMAIL_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "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 EMAIL_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_EMAIL_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_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 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index a22e51653835..000000000000 --- 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_PHONE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_PHONE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_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 5856aa42e6b5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,135 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return FXCollections.unmodifiableObservableList(internalList); - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca73..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index 8cdff2773ac9..000000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_TAG_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(TAG_VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} 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 1806da4facfa..000000000000 --- 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 4599182b3f92..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index 28791127999b..000000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -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(UserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. - * Raises {@link DataSavingExceptionEvent} if there was an error during saving. - */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); -} 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 b0df908a76a7..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,93 +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 com.google.common.eventbus.Subscribe; - -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager extends ComponentManager 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(UserPrefs 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); - } - - - @Override - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); - try { - saveAddressBook(event.data); - } catch (IOException e) { - raise(new DataSavingExceptionEvent(e)); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index c03785e5700f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = source.getTags().stream() - .map(XmlAdaptedTag::new) - .collect(Collectors.toList()); - } - - /** - * Converts this jaxb-friendly adapted person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java deleted file mode 100644 index d3e2d8be9c4f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.storage; - -import javax.xml.bind.annotation.XmlValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * JAXB-friendly adapted version of the Tag. - */ -public class XmlAdaptedTag { - - @XmlValue - private String tagName; - - /** - * Constructs an XmlAdaptedTag. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedTag() {} - - /** - * Constructs a {@code XmlAdaptedTag} with the given {@code tagName}. - */ - public XmlAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given Tag into this class for JAXB use. - * - * @param source future changes to this will not affect the created - */ - public XmlAdaptedTag(Tag source) { - tagName = source.tagName; - } - - /** - * Converts this jaxb-friendly adapted tag object into the model's Tag object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); - } - return new Tag(tagName); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedTag)) { - return false; - } - - return tagName.equals(((XmlAdaptedTag) other).tagName); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index ecf0e7ec23a8..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -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.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private Path filePath; - - public XmlAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - 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, - FileNotFoundException { - requireNonNull(filePath); - - if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); - return Optional.empty(); - } - - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); - try { - return Optional.of(xmlAddressBook.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); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java deleted file mode 100644 index d8f65dc036ab..000000000000 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.storage; - -import java.io.FileNotFoundException; -import java.nio.file.Path; - -import javax.xml.bind.JAXBException; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.XmlUtil; - -/** - * Stores addressbook data in an XML file - */ -public class XmlFileStorage { - /** - * Saves the given addressbook data to the specified file. - */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) - throws FileNotFoundException { - try { - XmlUtil.saveDataToFile(file, addressBook); - } catch (JAXBException e) { - throw new AssertionError("Unexpected exception " + e.getMessage(), e); - } - } - - /** - * Returns address book in the file or an empty address book - */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, - FileNotFoundException { - try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); - } catch (JAXBException e) { - throw new DataConversionException(e); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b85fa4a8f07e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - @XmlElement - private List persons; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - return persons.equals(((XmlSerializableAddressBook) other).persons); - } -} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index b43de90a2b9f..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); - } -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 80080adb4305..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/jxmusic/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/jxmusic/AppParameters.java index ab552c398f3d..f441544345d0 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/jxmusic/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.jxmusic; 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.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/jxmusic/MainApp.java similarity index 69% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/jxmusic/MainApp.java index ecdd043a4f81..da50a14411f1 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/jxmusic/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.jxmusic; import java.io.IOException; import java.nio.file.Path; @@ -10,37 +10,37 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -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.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.jxmusic.commons.core.Config; +import seedu.jxmusic.commons.core.EventsCenter; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.core.Version; +import seedu.jxmusic.commons.events.ui.ExitAppRequestEvent; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.commons.util.ConfigUtil; +import seedu.jxmusic.commons.util.StringUtil; +import seedu.jxmusic.logic.Logic; +import seedu.jxmusic.logic.LogicManager; +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.ModelManager; +import seedu.jxmusic.model.ReadOnlyLibrary; +import seedu.jxmusic.model.UserPrefs; +import seedu.jxmusic.model.util.SampleDataUtil; +import seedu.jxmusic.storage.JsonLibraryStorage; +import seedu.jxmusic.storage.JsonUserPrefsStorage; +import seedu.jxmusic.storage.LibraryStorage; +import seedu.jxmusic.storage.Storage; +import seedu.jxmusic.storage.StorageManager; +import seedu.jxmusic.storage.UserPrefsStorage; +import seedu.jxmusic.ui.Ui; +import seedu.jxmusic.ui.UiManager; /** * The main entry point to the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +54,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing JxMusic ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +62,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + LibraryStorage libraryStorage = new JsonLibraryStorage(userPrefs.getLibraryFilePath()); + storage = new StorageManager(libraryStorage, userPrefsStorage); initLogging(config); @@ -77,25 +77,24 @@ public void init() throws Exception { } /** - * 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 jxmusic book and {@code userPrefs}.
+ * The data from the sample jxmusic book will be used instead if {@code storage}'s jxmusic book is not found, + * or an empty jxmusic book will be used instead if errors occur when reading {@code storage}'s jxmusic book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + ReadOnlyLibrary initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + initialData = storage.readLibrary(); + if (initialData.getPlaylistList().size() == 0) { + logger.info("Data file not found. Will be starting with a sample Library"); + initialData = SampleDataUtil.populateSamplePlaylists(initialData); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } 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 Library"); + initialData = new Library(); } 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 Library"); + initialData = new Library(); } return new ModelManager(initialData, userPrefs); @@ -159,7 +158,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 Library"); initializedPrefs = new UserPrefs(); } @@ -179,13 +178,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting JxMusic " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping JxMusic ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/jxmusic/commons/core/ComponentManager.java similarity index 87% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/seedu/jxmusic/commons/core/ComponentManager.java index 05a400773ae8..fa1fa4b8d857 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/jxmusic/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package seedu.jxmusic.commons.core; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Base class for *Manager classes diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/jxmusic/commons/core/Config.java similarity index 95% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/jxmusic/commons/core/Config.java index e978d621e086..eb3bd7831ace 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/jxmusic/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.jxmusic.commons.core; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "JxMusic"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/seedu/jxmusic/commons/core/EventsCenter.java similarity index 92% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/seedu/jxmusic/commons/core/EventsCenter.java index 799b976f7eb7..c119111688bd 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/seedu/jxmusic/commons/core/EventsCenter.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; +package seedu.jxmusic.commons.core; import java.util.logging.Logger; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Manages the event dispatching of the app. diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/jxmusic/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/jxmusic/commons/core/GuiSettings.java index ed578f1a9bb6..080d2c5e255b 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/jxmusic/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.jxmusic.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/jxmusic/commons/core/LogsCenter.java similarity index 96% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/jxmusic/commons/core/LogsCenter.java index 5316a1d87d3e..569495ea952b 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/jxmusic/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.jxmusic.commons.core; import java.io.IOException; import java.util.Arrays; @@ -8,7 +8,7 @@ import java.util.logging.Logger; import java.util.logging.SimpleFormatter; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Configures and manages loggers and handlers, including their logging level @@ -20,7 +20,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 = "jxmusic.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/jxmusic/commons/core/Messages.java b/src/main/java/seedu/jxmusic/commons/core/Messages.java new file mode 100644 index 000000000000..bc33564484e3 --- /dev/null +++ b/src/main/java/seedu/jxmusic/commons/core/Messages.java @@ -0,0 +1,14 @@ +package seedu.jxmusic.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_PLAYLIST_DISPLAYED_INDEX = "The playlist index provided is invalid"; + public static final String MESSAGE_PLAYLISTS_LISTED_OVERVIEW = "%1$d playlists listed!"; + public static final String MESSAGE_TRACKS_LISTED_OVERVIEW = "%1$d tracks listed!"; + +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/jxmusic/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/jxmusic/commons/core/Version.java index e117f91b3b2e..b4a27b885116 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/jxmusic/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.jxmusic.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/jxmusic/commons/core/index/Index.java similarity index 92% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/jxmusic/commons/core/index/Index.java index 19536439c099..9102e88b6a79 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/jxmusic/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.jxmusic.commons.core.index; /** * Represents a zero-based or one-based index. @@ -51,4 +51,9 @@ public boolean equals(Object other) { || (other instanceof Index // instanceof handles nulls && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check } + + @Override + public String toString() { + return "" + getOneBased(); + } } diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/seedu/jxmusic/commons/events/BaseEvent.java similarity index 91% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/seedu/jxmusic/commons/events/BaseEvent.java index 85e71cbb6b62..37f0afcd6be5 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/BaseEvent.java @@ -1,4 +1,4 @@ -package seedu.address.commons.events; +package seedu.jxmusic.commons.events; /** * The base class for all event classes. diff --git a/src/main/java/seedu/jxmusic/commons/events/model/LibraryChangedEvent.java b/src/main/java/seedu/jxmusic/commons/events/model/LibraryChangedEvent.java new file mode 100644 index 000000000000..a621d8e666df --- /dev/null +++ b/src/main/java/seedu/jxmusic/commons/events/model/LibraryChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.jxmusic.commons.events.model; + +import seedu.jxmusic.commons.events.BaseEvent; +import seedu.jxmusic.model.ReadOnlyLibrary; + +/** Indicates the Library in the model has changed*/ +public class LibraryChangedEvent extends BaseEvent { + + public final ReadOnlyLibrary data; + + public LibraryChangedEvent(ReadOnlyLibrary data) { + this.data = data; + } + + @Override + public String toString() { + return "number of playlists " + data.getPlaylistList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/seedu/jxmusic/commons/events/storage/DataSavingExceptionEvent.java similarity index 78% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/seedu/jxmusic/commons/events/storage/DataSavingExceptionEvent.java index 7096107d8adf..3f7098b11233 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package seedu.jxmusic.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Indicates an exception during a file saving diff --git a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/ExitAppRequestEvent.java similarity index 69% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/seedu/jxmusic/commons/events/ui/ExitAppRequestEvent.java index a280bc7b5569..8bbe6ce5c3a1 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.jxmusic.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Indicates a request for App termination diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/JumpToListRequestEvent.java similarity index 73% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/seedu/jxmusic/commons/events/ui/JumpToListRequestEvent.java index a890f8b47350..11cafb77fcc2 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/ui/JumpToListRequestEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package seedu.jxmusic.commons.events.ui; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.commons.events.BaseEvent; /** * Indicates a request to jump to the list of persons diff --git a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/NewResultAvailableEvent.java similarity index 79% rename from src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java rename to src/main/java/seedu/jxmusic/commons/events/ui/NewResultAvailableEvent.java index a5e8b2e13883..095bd85ca9c6 100644 --- a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/ui/NewResultAvailableEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.jxmusic.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * Indicates that a new result is available. diff --git a/src/main/java/seedu/jxmusic/commons/events/ui/PlaylistPanelSelectionChangedEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/PlaylistPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..418c45f30d6c --- /dev/null +++ b/src/main/java/seedu/jxmusic/commons/events/ui/PlaylistPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.jxmusic.commons.events.ui; + +import seedu.jxmusic.commons.events.BaseEvent; +import seedu.jxmusic.model.Playlist; + +/** + * Represents a selection change in the Playlist List Panel + */ +public class PlaylistPanelSelectionChangedEvent extends BaseEvent { + + + private final Playlist newSelection; + + public PlaylistPanelSelectionChangedEvent(Playlist newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Playlist getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/ShowHelpRequestEvent.java similarity index 70% rename from src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java rename to src/main/java/seedu/jxmusic/commons/events/ui/ShowHelpRequestEvent.java index 87d5c70a1b79..105681c51c9f 100644 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ b/src/main/java/seedu/jxmusic/commons/events/ui/ShowHelpRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.jxmusic.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.commons.events.BaseEvent; /** * An event requesting to view the help page. diff --git a/src/main/java/seedu/jxmusic/commons/events/ui/TrackListPanelSelectionChangedEvent.java b/src/main/java/seedu/jxmusic/commons/events/ui/TrackListPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..c6643b09d889 --- /dev/null +++ b/src/main/java/seedu/jxmusic/commons/events/ui/TrackListPanelSelectionChangedEvent.java @@ -0,0 +1,24 @@ +package seedu.jxmusic.commons.events.ui; + +import seedu.jxmusic.commons.events.BaseEvent; +import seedu.jxmusic.model.Track; + +/** + * Represents a selection change in the Track List Panel + */ +public class TrackListPanelSelectionChangedEvent extends BaseEvent { + private final Track newSelection; + + public TrackListPanelSelectionChangedEvent(Track newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Track getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/jxmusic/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/jxmusic/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..9fa107e60306 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/jxmusic/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.jxmusic.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/jxmusic/commons/exceptions/IllegalValueException.java similarity index 93% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/jxmusic/commons/exceptions/IllegalValueException.java index 19124db485c9..cddc92b81b57 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/jxmusic/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.jxmusic.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/jxmusic/commons/util/AppUtil.java similarity index 94% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/jxmusic/commons/util/AppUtil.java index da90201dfd64..e58e816391c6 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.jxmusic.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.jxmusic.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/jxmusic/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/jxmusic/commons/util/CollectionUtil.java index eafe4dfd6818..fb8ea901bafe 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.jxmusic.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/jxmusic/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/jxmusic/commons/util/ConfigUtil.java index f7f8a2bd44c0..715e21ffe252 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.jxmusic.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.jxmusic.commons.core.Config; +import seedu.jxmusic.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/jxmusic/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/jxmusic/commons/util/FileUtil.java index b1e2767cdd92..d72c98e58d24 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.jxmusic.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/jxmusic/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/jxmusic/commons/util/JsonUtil.java index 8ef609f055df..1077bfb4c395 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.jxmusic.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.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.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/jxmusic/commons/util/StringUtil.java similarity index 70% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/jxmusic/commons/util/StringUtil.java index 61cc8c9a1cb8..86f331bb2450 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/jxmusic/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.jxmusic.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.jxmusic.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; @@ -38,6 +38,27 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code substring}. + * Ignores case, a full word match is not required. + *
examples:
+     *       containsWordIgnoreCase("ABc def", "abc") == true
+     *       containsWordIgnoreCase("ABc def", "DEF") == true
+     *       containsWordIgnoreCase("ABc def", "AB") == true
+     *       
+ * @param sentence cannot be null + * @param substring cannot be null, cannot be empty + */ + public static boolean containsSubstringIgnoreCase(String sentence, String substring) { + requireNonNull(sentence); + requireNonNull(substring); + + String trimmedSubstring = substring.trim(); + checkArgument(!trimmedSubstring.isEmpty(), "Substring parameter cannot be empty or spaces"); + + return sentence.toLowerCase().contains(trimmedSubstring.toLowerCase()); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/CommandHistory.java b/src/main/java/seedu/jxmusic/logic/CommandHistory.java similarity index 97% rename from src/main/java/seedu/address/logic/CommandHistory.java rename to src/main/java/seedu/jxmusic/logic/CommandHistory.java index 39bca9b8df57..05d05e0fcbce 100644 --- a/src/main/java/seedu/address/logic/CommandHistory.java +++ b/src/main/java/seedu/jxmusic/logic/CommandHistory.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.jxmusic.logic; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/logic/ListElementPointer.java b/src/main/java/seedu/jxmusic/logic/ListElementPointer.java similarity index 99% rename from src/main/java/seedu/address/logic/ListElementPointer.java rename to src/main/java/seedu/jxmusic/logic/ListElementPointer.java index ca4085d98a11..5bebf984aa24 100644 --- a/src/main/java/seedu/address/logic/ListElementPointer.java +++ b/src/main/java/seedu/jxmusic/logic/ListElementPointer.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.jxmusic.logic; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/seedu/jxmusic/logic/Logic.java b/src/main/java/seedu/jxmusic/logic/Logic.java new file mode 100644 index 000000000000..9a4dd0945ac5 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/Logic.java @@ -0,0 +1,38 @@ +package seedu.jxmusic.logic; + +import javafx.collections.ObservableList; +import seedu.jxmusic.logic.commands.CommandResult; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * 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 an unmodifiable view of the filtered list of playlists. + */ + ObservableList getFilteredPlaylistList(); + + /** + * Returns an unmodifiable view of the filtered list of tracks. + */ + ObservableList getFilteredTrackList(); + + /** + * Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object + */ + ListElementPointer getHistorySnapshot(); +} diff --git a/src/main/java/seedu/jxmusic/logic/LogicManager.java b/src/main/java/seedu/jxmusic/logic/LogicManager.java new file mode 100644 index 000000000000..71355e8ba7eb --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/LogicManager.java @@ -0,0 +1,58 @@ +package seedu.jxmusic.logic; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.jxmusic.commons.core.ComponentManager; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.logic.commands.Command; +import seedu.jxmusic.logic.commands.CommandResult; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.logic.parser.LibraryParser; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final CommandHistory history; + private final LibraryParser libraryParser; + + public LogicManager(Model model) { + this.model = model; + history = new CommandHistory(); + libraryParser = new LibraryParser(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + try { + Command command = libraryParser.parseCommand(commandText); + return command.execute(model); + } finally { + history.add(commandText); + } + } + + @Override + public ObservableList getFilteredPlaylistList() { + return model.getFilteredPlaylistList(); + } + + @Override + public ObservableList getFilteredTrackList() { + return model.getFilteredTrackList(); + } + + @Override + public ListElementPointer getHistorySnapshot() { + return new ListElementPointer(history.getHistory()); + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/ClearCommand.java b/src/main/java/seedu/jxmusic/logic/commands/ClearCommand.java new file mode 100644 index 000000000000..2f47e0960967 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/ClearCommand.java @@ -0,0 +1,23 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Model; + +/** + * Clears the library. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_PHRASE = "clear"; + public static final String MESSAGE_SUCCESS = "Library has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.resetData(new Library()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/jxmusic/logic/commands/Command.java similarity index 54% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/jxmusic/logic/commands/Command.java index 34e99d786ec6..bd7a6b4ac44b 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/jxmusic/logic/commands/Command.java @@ -1,22 +1,24 @@ -package seedu.address.logic.commands; +package seedu.jxmusic.logic.commands; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.player.Player; +import seedu.jxmusic.player.PlayerManager; /** * Represents a command with hidden internal logic and the ability to be executed. */ public abstract class Command { + protected Player player = PlayerManager.getInstance(); + /** * Executes the command and returns the result message. * * @param model {@code Model} which the command should operate on. - * @param history {@code CommandHistory} which the command should operate on. * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; + public abstract CommandResult execute(Model model) throws CommandException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/jxmusic/logic/commands/CommandResult.java similarity index 88% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/jxmusic/logic/commands/CommandResult.java index abdc267a2c44..869881787d5a 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/jxmusic/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.jxmusic.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/jxmusic/logic/commands/DurationCommand.java b/src/main/java/seedu/jxmusic/logic/commands/DurationCommand.java new file mode 100644 index 000000000000..f6ae4fccd4f0 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/DurationCommand.java @@ -0,0 +1,35 @@ +package seedu.jxmusic.logic.commands; + +import javafx.util.Duration; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; + +/** + * Plays a track/playlist or continue playing from a pause/stop + */ +public class DurationCommand extends Command { + + public static final String COMMAND_PHRASE = "duration"; + + public static final String MESSAGE_SUCCESS = "Duration of current track: %1$s"; + public static final String MESSAGE_FAILURE = "There is no track playing/paused/stopped"; + + @Override + public CommandResult execute(Model model) throws CommandException { + switch (player.getStatus()) { + case PLAYING: + case PAUSED: + case STOPPED: + Duration fileDuration = player.getDuration(); + int totalSeconds = (int) fileDuration.toSeconds(); + int seconds = totalSeconds % 60; + int hours = totalSeconds / 3600; + int minutes = (totalSeconds - seconds - hours * 3600) / 60; + String durationString = String.format("%02dh %02dm %02ds", hours, minutes, seconds); + return new CommandResult(String.format(MESSAGE_SUCCESS, durationString)); + default: + return new CommandResult(MESSAGE_FAILURE); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/jxmusic/logic/commands/ExitCommand.java similarity index 51% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/jxmusic/logic/commands/ExitCommand.java index e848fa918964..1471c65394bd 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/jxmusic/logic/commands/ExitCommand.java @@ -1,9 +1,8 @@ -package seedu.address.logic.commands; +package seedu.jxmusic.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; +import seedu.jxmusic.commons.core.EventsCenter; +import seedu.jxmusic.commons.events.ui.ExitAppRequestEvent; +import seedu.jxmusic.model.Model; /** * Terminates the program. @@ -12,10 +11,10 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting JxMusic as requested ..."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model) { EventsCenter.getInstance().post(new ExitAppRequestEvent()); return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/jxmusic/logic/commands/HelpCommand.java similarity index 64% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/jxmusic/logic/commands/HelpCommand.java index 66305e95d8f2..d216a5bab859 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/jxmusic/logic/commands/HelpCommand.java @@ -1,9 +1,8 @@ -package seedu.address.logic.commands; +package seedu.jxmusic.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; +import seedu.jxmusic.commons.core.EventsCenter; +import seedu.jxmusic.commons.events.ui.ShowHelpRequestEvent; +import seedu.jxmusic.model.Model; /** * Format full help instructions for every command for display. @@ -18,7 +17,7 @@ public class HelpCommand extends Command { public static final String SHOWING_HELP_MESSAGE = "Opened help window."; @Override - public CommandResult execute(Model model, CommandHistory history) { + public CommandResult execute(Model model) { EventsCenter.getInstance().post(new ShowHelpRequestEvent()); return new CommandResult(SHOWING_HELP_MESSAGE); } diff --git a/src/main/java/seedu/jxmusic/logic/commands/PauseCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PauseCommand.java new file mode 100644 index 000000000000..98ea179b6f54 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PauseCommand.java @@ -0,0 +1,28 @@ +package seedu.jxmusic.logic.commands; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.player.Playable; + +/** + * Pauses track that is currently playing + */ +public class PauseCommand extends Command { + + public static final String COMMAND_WORD = "pause"; + + public static final String MESSAGE_SUCCESS = "Pause a track"; + + public static final String MESSAGE_NOT_PLAYING = "No playing track to pause"; + + + @Override + public CommandResult execute(Model model) throws CommandException { + if (player.getStatus() == Playable.Status.PLAYING) { + player.pause(); + } else { + throw new CommandException(MESSAGE_NOT_PLAYING); + } + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/PlayCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PlayCommand.java new file mode 100644 index 000000000000..c37fb01199ee --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PlayCommand.java @@ -0,0 +1,139 @@ +package seedu.jxmusic.logic.commands; + +import java.util.List; +import java.util.Optional; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; +import seedu.jxmusic.player.Playable; + +/** + * Plays a track/playlist or continue playing from a pause/stop + */ +public class PlayCommand extends Command { + + public static final String COMMAND_PHRASE = "play"; + + public static final String MESSAGE_SUCCESS = "Plays successfully"; + + public static final String MESSAGE_NOT_PAUSED = "No track paused/stopped"; + public static final String MESSAGE_NO_TRACK = "There is no track"; + public static final String MESSAGE_NO_PLAYLIST = "There is no playlist"; + public static final String MESSAGE_TRACK_NOT_FOUND = "Track not found"; + public static final String MESSAGE_PLAYLIST_NOT_FOUND = "Playlist not found"; + public static final String MESSAGE_PLAYLIST_EMPTY = "The playlist has no track"; + + public static final String MESSAGE_FAILURE = "Failed to play"; + + private final PlayMode mode; + private Playlist argPlaylist; + private Track argTrack; + + public PlayCommand() { + mode = PlayMode.CONTINUE_FROM_PAUSE; + } + + public PlayCommand(Playlist playlist) { + argPlaylist = playlist; + if (argPlaylist == null) { + mode = PlayMode.DEFAULT_PLAYLIST; + } else { + mode = PlayMode.SPECIFIC_PLAYLIST; + } + } + + public PlayCommand(Track track) { + argTrack = track; + if (argTrack == null) { + mode = PlayMode.DEFAULT_TRACK; + } else { + mode = PlayMode.SPECIFIC_TRACK; + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + switch (mode) { + case CONTINUE_FROM_PAUSE: + if (player.getStatus() == Playable.Status.UNINITIALIZED) { + throw new CommandException(MESSAGE_NOT_PAUSED); + } + player.play(); + break; + case DEFAULT_PLAYLIST: + if (player.getStatus() != Playable.Status.UNINITIALIZED) { + player.stop(); + } + if (model.getLibrary().getPlaylistList().isEmpty()) { + throw new CommandException(MESSAGE_NO_PLAYLIST); + } + Playlist firstPlaylist = model.getLibrary().getPlaylistList().get(0); + if (firstPlaylist.getTracks().isEmpty()) { + throw new CommandException(MESSAGE_PLAYLIST_EMPTY); + } + player.play(firstPlaylist); + break; + case SPECIFIC_PLAYLIST: + if (player.getStatus() != Playable.Status.UNINITIALIZED) { + player.stop(); + } + if (model.getLibrary().getPlaylistList().isEmpty()) { + throw new CommandException(MESSAGE_NO_PLAYLIST); + } + List filteredPlaylists = model.getLibrary().getPlaylistList() + .filtered(playlist -> playlist.isSamePlaylist(argPlaylist)); + if (filteredPlaylists.isEmpty()) { + throw new CommandException(MESSAGE_PLAYLIST_NOT_FOUND); + } + Playlist specifiedPlaylist = filteredPlaylists.get(0); + if (specifiedPlaylist.getTracks().isEmpty()) { + throw new CommandException(MESSAGE_PLAYLIST_EMPTY); + } + player.play(specifiedPlaylist); + break; + case DEFAULT_TRACK: + if (player.getStatus() != Playable.Status.UNINITIALIZED) { + player.stop(); + } + if (model.getLibrary().getTracks().isEmpty()) { + throw new CommandException(MESSAGE_NO_TRACK); + } + Track firstTrack = model.getLibrary().getTracks().stream() + .findFirst() + .get(); + player.play(firstTrack); + break; + case SPECIFIC_TRACK: + if (player.getStatus() != Playable.Status.UNINITIALIZED) { + player.stop(); + } + if (model.getLibrary().getTracks().isEmpty()) { + throw new CommandException(MESSAGE_NO_TRACK); + } + Optional filteredTrack = model.getLibrary().getTracks().stream() + .filter(track -> track.equals(argTrack)) + .findFirst(); + if (!filteredTrack.isPresent()) { + throw new CommandException(MESSAGE_TRACK_NOT_FOUND); + } + Track specifiedTrack = filteredTrack.get(); + player.play(specifiedTrack); + break; + default: + } + return new CommandResult(MESSAGE_SUCCESS); + } + + /** + * Specifies the purpose of the PlayCommand + */ + private enum PlayMode { + CONTINUE_FROM_PAUSE, + DEFAULT_PLAYLIST, + SPECIFIC_PLAYLIST, + DEFAULT_TRACK, + SPECIFIC_TRACK + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/PlaylistDelCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PlaylistDelCommand.java new file mode 100644 index 000000000000..c4f661e6c570 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PlaylistDelCommand.java @@ -0,0 +1,53 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.jxmusic.commons.core.Messages; +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; + +/** + * Deletes a playlist identified using it's displayed index from the jxmusic book. + */ +public class PlaylistDelCommand extends Command { + + public static final String COMMAND_PHRASE = "playlist del"; + + public static final String MESSAGE_USAGE = COMMAND_PHRASE + + ": Deletes the playlist identified by the index number used in the displayed playlist list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_PHRASE + "1"; + + public static final String MESSAGE_DELETE_PLAYLIST_SUCCESS = "Deleted Playlist: %1$s"; + + private final Index targetIndex; + + public PlaylistDelCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPlaylistList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PLAYLIST_DISPLAYED_INDEX); + } + + Playlist playlistToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deletePlaylist(playlistToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PLAYLIST_SUCCESS, playlistToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PlaylistDelCommand // instanceof handles nulls + && targetIndex.equals(((PlaylistDelCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/PlaylistListCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PlaylistListCommand.java new file mode 100644 index 000000000000..988c36e14ba8 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PlaylistListCommand.java @@ -0,0 +1,24 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.model.Model.PREDICATE_SHOW_ALL_PLAYLISTS; + +import seedu.jxmusic.model.Model; + +/** + * Lists all playlist inside library. + */ +public class PlaylistListCommand extends Command { + public static final String COMMAND_PHRASE = "playlist list"; + + public static final String MESSAGE_SUCCESS = "Listed all playlists"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPlaylistList(PREDICATE_SHOW_ALL_PLAYLISTS); + return new CommandResult(MESSAGE_SUCCESS); + + } + +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/PlaylistNewCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PlaylistNewCommand.java new file mode 100644 index 000000000000..59eaddee8588 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PlaylistNewCommand.java @@ -0,0 +1,59 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TRACK; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; + +/** + * Adds a playlist to the library. + */ +public class PlaylistNewCommand extends Command { + + public static final String COMMAND_PHRASE = "playlist new"; + + public static final String MESSAGE_USAGE = COMMAND_PHRASE + ": Creates a new empty playlist and saves it into the " + + "library." + + "Parameters: " + + PREFIX_PLAYLIST + "PLAYLIST " + + "[" + PREFIX_TRACK + "TRACK]...\n" + + "Example: " + COMMAND_PHRASE + " " + + PREFIX_PLAYLIST + "Favourites " + + PREFIX_TRACK + "Ihojin no Yaiba " + + PREFIX_TRACK + "acyort"; + + public static final String MESSAGE_SUCCESS = "New playlist added: %1$s"; + public static final String MESSAGE_DUPLICATE_PLAYLIST = "This playlist already exists in the library"; + + private final Playlist toAdd; + + /** + * Creates an PlaylistNewCommand to add the specified {@code Playlist} + */ + public PlaylistNewCommand(Playlist playlist) { + requireNonNull(playlist); + toAdd = playlist; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasPlaylist(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PLAYLIST); + } + + model.addPlaylist(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 PlaylistNewCommand // instanceof handles nulls + && toAdd.equals(((PlaylistNewCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/PlaylistSearchCommand.java b/src/main/java/seedu/jxmusic/logic/commands/PlaylistSearchCommand.java new file mode 100644 index 000000000000..c2833a2750a5 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/PlaylistSearchCommand.java @@ -0,0 +1,42 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.jxmusic.commons.core.Messages; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.NameContainsKeywordsPredicate; + +/** + * Searches and lists all playlist in jxmusic book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class PlaylistSearchCommand extends Command { + + public static final String COMMAND_PHRASE = "playlist search"; + + public static final String MESSAGE_USAGE = COMMAND_PHRASE + ": Searches all playlists 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_PHRASE + " Favourites\n"; + + private final NameContainsKeywordsPredicate predicate; + + public PlaylistSearchCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPlaylistList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PLAYLISTS_LISTED_OVERVIEW, model.getFilteredPlaylistList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PlaylistSearchCommand // instanceof handles nulls + && predicate.equals(((PlaylistSearchCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/SeekCommand.java b/src/main/java/seedu/jxmusic/logic/commands/SeekCommand.java new file mode 100644 index 000000000000..151107481b4b --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/SeekCommand.java @@ -0,0 +1,53 @@ +package seedu.jxmusic.logic.commands; + +import javafx.util.Duration; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.player.Playable; + +/** + * Seeks the player to a new playback time. + */ + +public class SeekCommand extends Command { + + public static final String COMMAND_PHRASE = "seek"; + + public static final String MESSAGE_USAGE = COMMAND_PHRASE + ": seek the play point to the specified time point.\n" + + "Parameters: d/TIME" + + "TIME is in the format of in the format of [[h ]m ]s each of which " + + "represents a unit of time that will be summed up to get the time point.\n"; + + public static final String MESSAGE_SUCCESS = "seek the player to time required"; + + public static final String MESSAGE_NOT_PLAYING = "No playing track to seek"; + + //time that is required to seek to + private Duration time; + + public SeekCommand(Duration seekTime) { + this.time = seekTime; + } + + public Duration getTime() { + return time; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + if (player.getStatus() == Playable.Status.PLAYING || player.getStatus() == Playable.Status.PAUSED) { + player.seek(time); + } else { + throw new CommandException(MESSAGE_NOT_PLAYING); + } + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SeekCommand // instanceof handles nulls + && time.equals(((SeekCommand) other).getTime())); + } +} + diff --git a/src/main/java/seedu/jxmusic/logic/commands/StopCommand.java b/src/main/java/seedu/jxmusic/logic/commands/StopCommand.java new file mode 100644 index 000000000000..adc5f32c5081 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/StopCommand.java @@ -0,0 +1,27 @@ +package seedu.jxmusic.logic.commands; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.player.Playable; + +/** + * Lists all persons in the jxmusic book to the user. + */ +public class StopCommand extends Command { + + // todo change to "play p/" when parser can use p/ for parameter + public static final String COMMAND_WORD = "stop"; + + public static final String MESSAGE_SUCCESS = "Stop the play of current track"; + + public static final String MESSAGE_NO_TRACK = "No track to stop"; + + @Override + public CommandResult execute(Model model) throws CommandException { + if (player.getStatus() == Playable.Status.UNINITIALIZED) { + throw new CommandException(MESSAGE_NO_TRACK); + } + player.stop(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/TrackAddCommand.java b/src/main/java/seedu/jxmusic/logic/commands/TrackAddCommand.java new file mode 100644 index 000000000000..8e934392dd0e --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/TrackAddCommand.java @@ -0,0 +1,210 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TRACK; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Add a track to target playlist. + */ +public class TrackAddCommand extends Command { + public static final String COMMAND_PHRASE = "track add"; + public static final String MESSAGE_SUCCESS = "Tracks added to playlist: %1$s -> %2$s"; + public static final String MESSAGE_USAGE_TRACK = COMMAND_PHRASE + + ": Adds a new track(s) " + + "by the name of the track and playlist.\n" + + "Playlist will be modified with new track.\n" + + "Parameters: [" + PREFIX_PLAYLIST + "PLAYLIST] " + + "[" + PREFIX_TRACK + "TRACK]...\n" + + "Example: " + COMMAND_PHRASE + " " + + PREFIX_PLAYLIST + "rockPlaylist" + " " + + PREFIX_TRACK + "rockNRoll"; + public static final String MESSAGE_USAGE_INDEX = COMMAND_PHRASE + + ": Adds a new track(s) " + + "by the index of the track and name of the playlist.\n" + + "Playlist will be modified with new track at track index.\n" + + "Parameters: [" + PREFIX_PLAYLIST + "PLAYLIST] " + + "[" + PREFIX_INDEX + "INDEX]...\n" + + "Example: " + COMMAND_PHRASE + " " + + PREFIX_PLAYLIST + "rockPlaylist" + " " + + PREFIX_INDEX + "1"; + public static final String MESSAGE_TOO_MANY_PREFIX = "Too many prefixes, either use " + + PREFIX_TRACK + " or " + + PREFIX_INDEX; + public static final String MESSAGE_TRACK_DOES_NOT_EXIST = "These tracks do not exist: %3$s"; + public static final String MESSAGE_INDEX_DOES_NOT_EXIST = "These indexes do not exist: %3$s"; + public static final String MESSAGE_PLAYLIST_DOES_NOT_EXIST = "Playlist does not exist: %1$s"; + + private List argTracksToAdd; + private Playlist argPlaylist; + private List argIndexesToAdd; + private List tracksToAdd = new ArrayList(); + private List tracksNotAdded = new ArrayList(); + private ArrayList indexesNotAdded = new ArrayList<>(); + private InputType type; + + public TrackAddCommand(Playlist targetPlaylist, InputType type, Track... trackToAdd) { + requireNonNull(trackToAdd); + requireNonNull(targetPlaylist); + this.argPlaylist = targetPlaylist; + this.type = type; + this.argTracksToAdd = new ArrayList(); + for (Track track : trackToAdd) { + this.argTracksToAdd.add(track); + } + } + + public TrackAddCommand(Playlist targetPlaylist, InputType type, List tracksToAdd) { + requireNonNull(tracksToAdd); + requireNonNull(targetPlaylist); + this.argPlaylist = targetPlaylist; + this.type = type; + if (type == InputType.TRACK) { + this.argTracksToAdd = tracksToAdd; + } + if (type == InputType.INDEX) { + this.argIndexesToAdd = tracksToAdd; + } + } + + private boolean isExistingIndex(Index i, int trackListSize) { + return (i.getOneBased() <= trackListSize); + } + + private List getTracksFromIndexes(Model model, ArrayList indexes) { + List trackList = new ArrayList<>(); + final ObservableList viewedTracks = model.getFilteredTrackList(); + System.out.println(viewedTracks); + int trackListSize = viewedTracks.size(); + for (Index i : indexes) { + if (isExistingIndex(i, trackListSize)) { + Track track = viewedTracks.get(i.getZeroBased()); + trackList.add(track); + } + } + return trackList; + } + + private ArrayList getInvalidIndexes(int trackListSize, ArrayList indexes) { + ArrayList invalidIndexes = new ArrayList<>(); + for (Index i : indexes) { + if (!isExistingIndex(i, trackListSize)) { + invalidIndexes.add(i); + } + } + return invalidIndexes; + } + + private boolean isExistingPlaylistIn(Model model, Playlist argPlaylist) { + return !model.hasPlaylist(argPlaylist); + } + + /** + * Takes in a set of tracks in library, and populates the given playlist + * @param libraryTracks {@code ObservableSet} libraryTracks to fetch tracks from library + * @param populatedPlaylist The {@code Playlist} to be populated + */ + private void populatePlaylistWith(ObservableSet libraryTracks, Playlist populatedPlaylist) { + // check if tracks exist and add tracks to playlist + for (Track trackToAdd : argTracksToAdd) { + Optional listOfTracks = libraryTracks.stream() + .filter(track -> track.equals(trackToAdd)) + .findFirst(); + boolean areExistingTracks = listOfTracks.isPresent(); + if (areExistingTracks) { + tracksToAdd.add(listOfTracks.get()); + } else { + // to display as tracks that cannot be added + tracksNotAdded.add(trackToAdd); + } + } + for (Track trackToAdd : tracksToAdd) { + Track actualTrack = libraryTracks.stream() + .filter(track -> track.equals(trackToAdd)) + .findFirst() + .get(); + populatedPlaylist.addTrack(actualTrack); + } + } + + private Playlist findPlaylistFrom(ObservableList libraryPlaylists) { + return libraryPlaylists.filtered(playlist + -> playlist.isSamePlaylist(argPlaylist)) + .get(0); + } + + @Override + public CommandResult execute(Model model) { + int viewedTracksSize = model.getFilteredTrackList().size(); + ObservableSet libraryTracks = model.getLibrary().getTracks(); + ObservableList libraryPlaylists = model.getLibrary().getPlaylistList(); + Playlist updatedPlaylist; + Playlist actualPlaylist; + + if (isExistingPlaylistIn(model, argPlaylist)) { + return new CommandResult(String.format(MESSAGE_PLAYLIST_DOES_NOT_EXIST, argPlaylist.getName())); + } + + actualPlaylist = findPlaylistFrom(libraryPlaylists); + updatedPlaylist = actualPlaylist.copy(); + + // checks if index is being used instead of track + if (type == InputType.INDEX) { + ArrayList indexesToAdd = new ArrayList(argIndexesToAdd); + argTracksToAdd = getTracksFromIndexes(model, indexesToAdd); + indexesNotAdded = getInvalidIndexes(viewedTracksSize, indexesToAdd); + } + populatePlaylistWith(libraryTracks, updatedPlaylist); + model.updatePlaylist(actualPlaylist, updatedPlaylist); + + return displayMessage(actualPlaylist); + } + + /** + * + * @param actualPlaylist actualPlaylist from library + * @return CommandResult with message to be displayed + */ + private CommandResult displayMessage(Playlist actualPlaylist) { + // check if indexes do not exist + if (indexesNotAdded.isEmpty() && tracksNotAdded.isEmpty()) { + // for testing sake + // TODO: change implementation so that single tracks do not appear with [array bracket] in msg + if (tracksToAdd.size() == 1) { + return new CommandResult(String.format(MESSAGE_SUCCESS, tracksToAdd.get(0), actualPlaylist.getName())); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, tracksToAdd, actualPlaylist.getName())); + } + + // check for indexes and tracks added to be displayed + if (!indexesNotAdded.isEmpty() && !tracksToAdd.isEmpty()) { + return new CommandResult(String.format(MESSAGE_SUCCESS + "\n" + MESSAGE_INDEX_DOES_NOT_EXIST, + tracksToAdd, actualPlaylist.getName(), indexesNotAdded)); + } + if (!indexesNotAdded.isEmpty() && tracksToAdd.isEmpty()) { + return new CommandResult(String.format(MESSAGE_INDEX_DOES_NOT_EXIST, "", "", indexesNotAdded)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS + "\n" + MESSAGE_TRACK_DOES_NOT_EXIST, + tracksToAdd, actualPlaylist.getName(), tracksNotAdded)); + } + + /** + * Allows selection of add track by Index, or add track by Track Name + */ + public enum InputType { + INDEX, TRACK; + } + +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/TrackDeleteCommand.java b/src/main/java/seedu/jxmusic/logic/commands/TrackDeleteCommand.java new file mode 100644 index 000000000000..11d120b915d8 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/TrackDeleteCommand.java @@ -0,0 +1,77 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; + +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Delete a track from target playlist. + */ +public class TrackDeleteCommand extends Command { + public static final String COMMAND_PHRASE = "track del"; + public static final String MESSAGE_SUCCESS = "Track %1$s deleted from playlist %2$s"; + public static final String MESSAGE_USAGE = COMMAND_PHRASE + ": Deletes a track " + + "by the index of track in the playlist.\n" + + "Parameters: [" + PREFIX_PLAYLIST + "PLAYLIST] [" + PREFIX_INDEX + "INDEX]...\n" + + "Example: " + COMMAND_PHRASE + " " + + PREFIX_PLAYLIST + "rockNRollPlaylist " + + PREFIX_INDEX + "1"; + public static final String MESSAGE_INDEX_DOES_NOT_EXIST = "This playlist does not have index: %1$s"; + public static final String MESSAGE_PLAYLIST_IS_EMPTY = "This playlist is empty"; + public static final String MESSAGE_PLAYLIST_DOES_NOT_EXIST = "This playlist %1$s does not exist"; + + private Index indexToDelete; + private Playlist targetPlaylist; + + public TrackDeleteCommand(Playlist targetPlaylist, Index indexToDelete) { + requireNonNull(indexToDelete); + requireNonNull(targetPlaylist); + this.indexToDelete = indexToDelete; + this.targetPlaylist = targetPlaylist; + } + + @Override + public CommandResult execute(Model model) { + Playlist updatedPlaylist; + Playlist actualPlaylist; + int trackNum = indexToDelete.getOneBased(); + int playlistSize = 0; + Track trackToDelete; + + // check if playlist exists + if (!model.hasPlaylist(targetPlaylist)) { + return new CommandResult(String.format(MESSAGE_PLAYLIST_DOES_NOT_EXIST, targetPlaylist.getName())); + } + + actualPlaylist = model.getLibrary().getPlaylistList() + .filtered(playlist -> playlist.isSamePlaylist(targetPlaylist)) + .get(0); + + // check if playlist is empty + if (actualPlaylist.isEmpty()) { + return new CommandResult(MESSAGE_PLAYLIST_IS_EMPTY); + } + + updatedPlaylist = actualPlaylist.copy(); + // check if track exists in existing playlist + playlistSize = updatedPlaylist.getSize(); + if (trackNum > playlistSize) { + return new CommandResult(String.format(MESSAGE_INDEX_DOES_NOT_EXIST, trackNum)); + } + + // get track from trackNum + trackToDelete = actualPlaylist.get(indexToDelete.getZeroBased()); + + if (updatedPlaylist.deleteTrack(trackToDelete)) { + model.updatePlaylist(actualPlaylist, updatedPlaylist); + return new CommandResult(String.format(MESSAGE_SUCCESS, trackToDelete, actualPlaylist.getName())); + } + return null; + } + +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/TrackListCommand.java b/src/main/java/seedu/jxmusic/logic/commands/TrackListCommand.java new file mode 100644 index 000000000000..dee1fbb5a403 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/TrackListCommand.java @@ -0,0 +1,21 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Model; + +/** + * Lists all the tracks inside the library to the user. + */ +public class TrackListCommand extends Command { + public static final String COMMAND_PHRASE = "track list"; + public static final String MESSAGE_SUCCESS = "Listed all tracks"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredTrackList(Model.PREDICATE_SHOW_ALL_TRACKS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/jxmusic/logic/commands/TrackSearchCommand.java b/src/main/java/seedu/jxmusic/logic/commands/TrackSearchCommand.java new file mode 100644 index 000000000000..31537b6f46be --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/commands/TrackSearchCommand.java @@ -0,0 +1,37 @@ +package seedu.jxmusic.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.jxmusic.commons.core.Messages; +import seedu.jxmusic.model.Model; +import seedu.jxmusic.model.TrackNameContainsKeywordsPredicate; + +/** + * Search the desired Track from the library whose name contains any of the argument keywords. + */ +public class TrackSearchCommand extends Command { + public static final String COMMAND_PHRASE = "track search"; + public static final String MESSAGE_USAGE = COMMAND_PHRASE + ": Searches for all tracks 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_PHRASE + " Marbles"; + private final TrackNameContainsKeywordsPredicate predicate; + public TrackSearchCommand(TrackNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTrackList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TRACKS_LISTED_OVERVIEW, model.getFilteredTrackList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TrackSearchCommand // instanceof handles nulls + && predicate.equals(((TrackSearchCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/jxmusic/logic/commands/exceptions/CommandException.java similarity index 82% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/jxmusic/logic/commands/exceptions/CommandException.java index a16bd14f2cde..420daf321d30 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/jxmusic/logic/commands/exceptions/CommandException.java @@ -1,4 +1,6 @@ -package seedu.address.logic.commands.exceptions; +package seedu.jxmusic.logic.commands.exceptions; + +import seedu.jxmusic.logic.commands.Command; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/jxmusic/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/jxmusic/logic/parser/ArgumentMultimap.java index 954c8e18f8ea..73a8511be198 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/jxmusic/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.jxmusic.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/jxmusic/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/jxmusic/logic/parser/ArgumentTokenizer.java index 5c9aebfa4888..d718d127a0b6 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/jxmusic/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.jxmusic.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/jxmusic/logic/parser/CliSyntax.java b/src/main/java/seedu/jxmusic/logic/parser/CliSyntax.java new file mode 100644 index 000000000000..4f8c93252386 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/CliSyntax.java @@ -0,0 +1,16 @@ +package seedu.jxmusic.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + // JxMusic prefixes + public static final Prefix PREFIX_INDEX = new Prefix("i/"); + public static final Prefix PREFIX_TRACK = new Prefix("t/"); + public static final Prefix PREFIX_PLAYLIST = new Prefix("p/"); + public static final Prefix PREFIX_TIME = new Prefix("d/"); + public static final Prefix PREFIX_SECONDS = new Prefix("s/"); + public static final Prefix PREFIX_QUERY = new Prefix("q/"); +} diff --git a/src/main/java/seedu/jxmusic/logic/parser/LibraryParser.java b/src/main/java/seedu/jxmusic/logic/parser/LibraryParser.java new file mode 100644 index 000000000000..b8a7fa255437 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/LibraryParser.java @@ -0,0 +1,109 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.jxmusic.logic.commands.ClearCommand; +import seedu.jxmusic.logic.commands.Command; +//import seedu.jxmusic.logic.commands.EditCommand; //todo +import seedu.jxmusic.logic.commands.DurationCommand; +import seedu.jxmusic.logic.commands.ExitCommand; +import seedu.jxmusic.logic.commands.HelpCommand; +import seedu.jxmusic.logic.commands.PauseCommand; +import seedu.jxmusic.logic.commands.PlayCommand; +import seedu.jxmusic.logic.commands.PlaylistDelCommand; +import seedu.jxmusic.logic.commands.PlaylistListCommand; +import seedu.jxmusic.logic.commands.PlaylistNewCommand; +import seedu.jxmusic.logic.commands.PlaylistSearchCommand; +import seedu.jxmusic.logic.commands.SeekCommand; +import seedu.jxmusic.logic.commands.StopCommand; +import seedu.jxmusic.logic.commands.TrackAddCommand; +import seedu.jxmusic.logic.commands.TrackDeleteCommand; +import seedu.jxmusic.logic.commands.TrackListCommand; +import seedu.jxmusic.logic.commands.TrackSearchCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class LibraryParser { + + /** + * Used for initial separation of command phrase and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = + Pattern.compile("(?(?:[a-zA-Z]+\\s+){0,1}[a-zA-Z]+(?!/))(?.*)"); + + /** + * 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("commandPhrase").trim(); + final String arguments = matcher.group("arguments"); // ArgumentTokenizer requires space prefixed string + switch (commandWord) { + + case PlayCommand.COMMAND_PHRASE: + return new PlayCommandParser().parse(arguments); + + case PauseCommand.COMMAND_WORD: + return new PauseCommand(); // todo parse (argument) + + case DurationCommand.COMMAND_PHRASE: + return new DurationCommand(); + + case StopCommand.COMMAND_WORD: + return new StopCommand(); + + case SeekCommand.COMMAND_PHRASE: + return new SeekCommandParser().parse(arguments); + + case PlaylistListCommand.COMMAND_PHRASE: + return new PlaylistListCommand(); + + case PlaylistNewCommand.COMMAND_PHRASE: + return new PlaylistNewCommandParser().parse(arguments); + + case TrackListCommand.COMMAND_PHRASE: + return new TrackListCommand(); + + case TrackAddCommand.COMMAND_PHRASE: + return new TrackAddCommandParser().parse(arguments); + + case TrackDeleteCommand.COMMAND_PHRASE: + return new TrackDeleteCommandParser().parse(arguments); + + case TrackSearchCommand.COMMAND_PHRASE: + return new TrackSearchCommandParser().parse(arguments); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case PlaylistDelCommand.COMMAND_PHRASE: + return new PlaylistDelCommandParser().parse(arguments); + + case ClearCommand.COMMAND_PHRASE: + return new ClearCommand(); + + case PlaylistSearchCommand.COMMAND_PHRASE: + return new PlaylistSearchCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/jxmusic/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/jxmusic/logic/parser/Parser.java index d6551ad8e3ff..fe3d00dbd2d4 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/jxmusic/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package seedu.jxmusic.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.jxmusic.logic.commands.Command; +import seedu.jxmusic.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/seedu/jxmusic/logic/parser/ParserUtil.java b/src/main/java/seedu/jxmusic/logic/parser/ParserUtil.java new file mode 100644 index 000000000000..525b511f5115 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/ParserUtil.java @@ -0,0 +1,158 @@ +package seedu.jxmusic.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Scanner; + +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.commons.util.StringUtil; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Name; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; +//import seedu.jxmusic.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."; + public static final String MESSAGE_INVALID_TIME_FORMAT = + "Wrong time format, at most 3 unsigned integers are allowed in input."; + + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). + */ + public static Index parseIndex(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + /** + * Parses {@code 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 ArrayList parseIndexes(Collection oneBasedIndexes) throws ParseException { + final ArrayList indexList = new ArrayList(); + for (String oneBasedIndex : oneBasedIndexes) { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + indexList.add(Index.fromOneBased(Integer.parseInt(trimmedIndex))); + } + return indexList; + } + + /** + * 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_NAME_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code String trackName} into a {@code Track}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code track} is invalid.
+ * Possible exception messages:
+ * Name.MESSAGE_NAME_CONSTRAINTS
+ * Track.MESSAGE_FILE_NOT_EXIST
+ * Track.MESSAGE_FILE_NOT_SUPPORTED + */ + public static Track parseTrack(String trackName) throws ParseException { + requireNonNull(trackName); + String trimmedTrackName = trackName.trim(); + try { + return new Track(new Name(trimmedTrackName)); + } catch (IllegalArgumentException ex) { + throw new ParseException(ex.getMessage()); + } + } + + /** + * Parses {@code Collection trackNames} into a {@code List}. + */ + public static List parseTracks(Collection trackNames) throws ParseException { + requireNonNull(trackNames); + final List trackList = new ArrayList<>(); + for (String trackName : trackNames) { + trackList.add(parseTrack(trackName)); + } + return trackList; + } + + /** + * Parses a {@code String playlistName} into a {@code Playlist}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code playlist} is invalid.
+ * Possible exception messages:
+ * Name.MESSAGE_NAME_CONSTRAINTS
+ * Playlist.MESSAGE_FILE_NOT_EXIST
+ * Playlist.MESSAGE_FILE_NOT_SUPPORTED + */ + public static Playlist parsePlaylist(String playlistName) throws ParseException { + requireNonNull(playlistName); + String trimmedPlaylistName = playlistName.trim(); + try { + return new Playlist(new Name(trimmedPlaylistName)); + } catch (IllegalArgumentException ex) { + throw new ParseException(ex.getMessage()); + } + } + + /** + * Parse a {@code String seekTime} into a {@code Duration}. + * Leading and trailing whitespace will be trimmed. + * + * @throws ParseException if the given {@code time} is invalid. + * Possible exception messages: + * Wrong time format, TIME is in the format of [[h ]m ]s each of which represents + * a unit of time that will be summed up to get the time point. + */ + public static double parseTime(String timeString) throws ParseException { + requireNonNull(timeString); + String trimmedTimeString = timeString.trim(); + double timeInSeconds = 0; + int countSoFar = 0; + Scanner s = new Scanner(trimmedTimeString); + + + try { + while (s.hasNext() && countSoFar < 3) { + String currentToken = s.next(); + int currentValue = Integer.parseUnsignedInt(currentToken); + timeInSeconds *= 60; + timeInSeconds += currentValue; + countSoFar++; + } + if (s.hasNextInt()) { + throw new ParseException(MESSAGE_INVALID_TIME_FORMAT); + } + return timeInSeconds * 1000; + } catch (Exception e) { + throw new ParseException(MESSAGE_INVALID_TIME_FORMAT); + } + + } +} diff --git a/src/main/java/seedu/jxmusic/logic/parser/PlayCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/PlayCommandParser.java new file mode 100644 index 000000000000..c46b98a0b859 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/PlayCommandParser.java @@ -0,0 +1,58 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TRACK; + +import java.util.stream.Stream; + +import seedu.jxmusic.logic.commands.PlayCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Name; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Parses input arguments and creates a new PlayCommand object + */ +public class PlayCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PlayCommand + * and returns an PlayCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PlayCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PLAYLIST, PREFIX_TRACK); + + // playlist prefix takes precedence over track prefix + if (arePrefixesPresent(argMultimap, PREFIX_PLAYLIST)) { + String playlistNameString = argMultimap.getValue(PREFIX_PLAYLIST).get(); + Playlist playlist = null; + if (!playlistNameString.isEmpty()) { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_PLAYLIST).get()); + playlist = new Playlist(name); + } + return new PlayCommand(playlist); + } + if (arePrefixesPresent(argMultimap, PREFIX_TRACK)) { + String trackNameString = argMultimap.getValue(PREFIX_TRACK).get(); + Track track = null; + if (!trackNameString.isEmpty()) { + track = ParserUtil.parseTrack(argMultimap.getValue(PREFIX_TRACK).get()); + } + return new PlayCommand(track); + } + + return new PlayCommand(); + } + + /** + * 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/jxmusic/logic/parser/PlaylistDelCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/PlaylistDelCommandParser.java new file mode 100644 index 000000000000..3e6729e4e3ea --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/PlaylistDelCommandParser.java @@ -0,0 +1,29 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.logic.commands.PlaylistDelCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new PlaylistDelCommand object + */ +public class PlaylistDelCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PlaylistDelCommand + * and returns an PlaylistDelCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PlaylistDelCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new PlaylistDelCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PlaylistDelCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/jxmusic/logic/parser/PlaylistNewCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/PlaylistNewCommandParser.java new file mode 100644 index 000000000000..617409a469ce --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/PlaylistNewCommandParser.java @@ -0,0 +1,51 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TRACK; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.jxmusic.logic.commands.PlaylistNewCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Name; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Parses input arguments and creates a new PlaylistNewCommand object + */ +public class PlaylistNewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PlaylistNewCommand + * and returns an PlaylistNewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PlaylistNewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PLAYLIST, PREFIX_TRACK); + + if (!arePrefixesPresent(argMultimap, PREFIX_PLAYLIST) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, PlaylistNewCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_PLAYLIST).get()); + List trackList = ParserUtil.parseTracks(argMultimap.getAllValues(PREFIX_TRACK)); + + Playlist playlist = new Playlist(name, trackList); + + return new PlaylistNewCommand(playlist); + } + + /** + * 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/jxmusic/logic/parser/PlaylistSearchCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/PlaylistSearchCommandParser.java new file mode 100644 index 000000000000..85f66d421e04 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/PlaylistSearchCommandParser.java @@ -0,0 +1,33 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.jxmusic.logic.commands.PlaylistSearchCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.NameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new PlaylistSearchCommand object + */ +public class PlaylistSearchCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PlaylistSearchCommand + * and returns a PlaylistSearchCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PlaylistSearchCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PlaylistSearchCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new PlaylistSearchCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/jxmusic/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/jxmusic/logic/parser/Prefix.java index c859d5fa5db1..1335f83daf83 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/jxmusic/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.jxmusic.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/seedu/jxmusic/logic/parser/SeekCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/SeekCommandParser.java new file mode 100644 index 000000000000..4530af9471fa --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/SeekCommandParser.java @@ -0,0 +1,46 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TIME; + +import java.util.stream.Stream; + +import javafx.util.Duration; + +import seedu.jxmusic.logic.commands.SeekCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SeekCommand object + */ +public class SeekCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the SeekCommand + * and returns an SeekCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SeekCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_TIME)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SeekCommand.MESSAGE_USAGE)); + } + String seekTimeString = argMultimap.getValue(PREFIX_TIME).get(); + Duration seekTime = null; + if (!seekTimeString.isEmpty()) { + double time = ParserUtil.parseTime(argMultimap.getValue(PREFIX_TIME).get()); + seekTime = new Duration(time); + } + return new SeekCommand(seekTime); + } + + /** + * 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/jxmusic/logic/parser/TrackAddCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/TrackAddCommandParser.java new file mode 100644 index 000000000000..970e56acd41e --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/TrackAddCommandParser.java @@ -0,0 +1,86 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_TRACK; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.logic.commands.TrackAddCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Parses input arguments and creates a new TrackAddCommand object + */ +public class TrackAddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TrackAddCommand + * and returns an TrackAddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TrackAddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PLAYLIST, PREFIX_TRACK, PREFIX_INDEX); + List tracksToAdd; + ArrayList indexesToAdd; + boolean isUsingIndex = false; + boolean isUsingTrackName = false; + + if (arePrefixesPresent(argMultimap, PREFIX_TRACK)) { + isUsingTrackName = true; + } + + if (arePrefixesPresent(argMultimap, PREFIX_INDEX)) { + isUsingIndex = true; + } + + if (isUsingTrackName && isUsingIndex) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TrackAddCommand.MESSAGE_TOO_MANY_PREFIX)); + } + + + if (!(arePrefixesPresent(argMultimap, PREFIX_PLAYLIST, PREFIX_TRACK) + && arePrefixesPresent(argMultimap, PREFIX_PLAYLIST, PREFIX_INDEX)) + || !argMultimap.getPreamble().isEmpty()) { + if (isUsingTrackName && !arePrefixesPresent(argMultimap, PREFIX_TRACK)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TrackAddCommand.MESSAGE_USAGE_TRACK)); + } + if (isUsingIndex && !arePrefixesPresent(argMultimap, PREFIX_INDEX)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TrackAddCommand.MESSAGE_USAGE_INDEX)); + } + } + + Playlist targetPlaylist = ParserUtil.parsePlaylist(argMultimap.getValue(PREFIX_PLAYLIST).get()); + + if (isUsingTrackName) { + tracksToAdd = ParserUtil.parseTracks(argMultimap.getAllValues(PREFIX_TRACK)); + return new TrackAddCommand(targetPlaylist, TrackAddCommand.InputType.TRACK, tracksToAdd); + } + if (isUsingIndex) { + List indexes = Arrays.asList(argMultimap.getValue(PREFIX_INDEX).get().split("\\s+")); + indexesToAdd = ParserUtil.parseIndexes(indexes); + return new TrackAddCommand(targetPlaylist, TrackAddCommand.InputType.INDEX, indexesToAdd); + } + return null; + } + + /** + * 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/jxmusic/logic/parser/TrackDeleteCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/TrackDeleteCommandParser.java new file mode 100644 index 000000000000..3a5ae4da1591 --- /dev/null +++ b/src/main/java/seedu/jxmusic/logic/parser/TrackDeleteCommandParser.java @@ -0,0 +1,52 @@ +package seedu.jxmusic.logic.parser; + +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.jxmusic.logic.parser.CliSyntax.PREFIX_PLAYLIST; + +import java.util.stream.Stream; + +import seedu.jxmusic.commons.core.index.Index; +import seedu.jxmusic.logic.commands.TrackDeleteCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.Playlist; + +/** + * Parses input arguments and creates a new TrackDeleteCommand object + */ +public class TrackDeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TrackDeleteCommand + * and returns an TrackDeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TrackDeleteCommand parse(String args) throws ParseException { + try { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_PLAYLIST, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_PLAYLIST, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + TrackDeleteCommand.MESSAGE_USAGE)); + } + + Playlist targetPlaylist = ParserUtil.parsePlaylist(argMultimap.getValue(PREFIX_PLAYLIST).get()); + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + return new TrackDeleteCommand(targetPlaylist, index); + } catch (ArrayIndexOutOfBoundsException | NullPointerException ex) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT + " args: " + args, + TrackDeleteCommand.MESSAGE_USAGE)); + } + } + + /** + * 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/FindCommandParser.java b/src/main/java/seedu/jxmusic/logic/parser/TrackSearchCommandParser.java similarity index 51% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/jxmusic/logic/parser/TrackSearchCommandParser.java index b186a967cb94..b19058cc5cc4 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/jxmusic/logic/parser/TrackSearchCommandParser.java @@ -1,33 +1,33 @@ -package seedu.address.logic.parser; +package seedu.jxmusic.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.jxmusic.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.jxmusic.logic.commands.TrackSearchCommand; +import seedu.jxmusic.logic.parser.exceptions.ParseException; +import seedu.jxmusic.model.TrackNameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class TrackSearchCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns an FindCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public FindCommand parse(String args) throws ParseException { + public TrackSearchCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TrackSearchCommand.MESSAGE_USAGE)); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new TrackSearchCommand(new TrackNameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/jxmusic/logic/parser/exceptions/ParseException.java similarity index 73% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/jxmusic/logic/parser/exceptions/ParseException.java index 158a1a54c1c5..94bc577c5003 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/jxmusic/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.jxmusic.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.jxmusic.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/jxmusic/model/Library.java b/src/main/java/seedu/jxmusic/model/Library.java new file mode 100644 index 000000000000..bcde851e0983 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/Library.java @@ -0,0 +1,147 @@ +package seedu.jxmusic.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.TreeSet; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; + +/** + * Wraps all data at the library level + * Duplicates are not allowed (by .isSamePlaylist comparison) + */ +public class Library implements ReadOnlyLibrary { + + public static final String LIBRARYDIR = "library/"; + private final UniquePlaylistList playlists; + private final ObservableSet tracks; + + /* + * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + playlists = new UniquePlaylistList(); + tracks = FXCollections.observableSet(new TreeSet<>()); + } + + public Library() {} + + /** + * Creates a Library using the Playlists in the {@code toBeCopied} + */ + public Library(ReadOnlyLibrary toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + + @Override + public ObservableList getPlaylistList() { + return playlists.asUnmodifiableObservableList(); + } + + @Override + public ObservableSet getTracks() { + return FXCollections.unmodifiableObservableSet(tracks); + } + + /** + * Replaces the contents of the playlist list with {@code playlists}. + * @param playlists must not contain duplicate playlists. + */ + public void setPlaylists(List playlists) { + this.playlists.setPlaylists(playlists); + } + + /** + * Replaces the contents of the track set with {@code tracks}. Set is used to ensure no duplicates. + * @param tracks the new track ObservableSet. Cannot be null but can be empty. + */ + public void setTracks(ObservableSet tracks) { + requireNonNull(tracks); + this.tracks.clear(); + this.tracks.addAll(tracks); + } + + /** + * Resets the existing data of this {@code AddressBook} with {@code newData}. + */ + public void resetData(ReadOnlyLibrary newData) { + requireNonNull(newData); + setPlaylists(newData.getPlaylistList()); + setTracks(newData.getTracks()); + } + + //// playlist-level operations + + /** + * Returns true if a playlist with the same identity as {@code playlist} exists in the library. + */ + public boolean hasPlaylist(Playlist playlist) { + requireNonNull(playlist); + return playlists.contains(playlist); + } + + /** + * Adds a playlist to the library. + * The playlist must not already exist in the library. + */ + public void addPlaylist(Playlist p) { + playlists.add(p); + } + /** + * Adds a track to the library. + * The track must not already exist in the library. + */ + public void addTrack(Track t) { + tracks.add(t); + } + + /** + * Replaces the given playlist {@code target} in the list with {@code editedPlaylist}. + * {@code target} must exist in the library. + * The playlist identity of {@code editedPlaylist} must not be the same as another existing playlist in the library. + */ + public void updatePlaylist(Playlist target, Playlist editedPlaylist) { + requireNonNull(editedPlaylist); + + playlists.replacePlaylist(target, editedPlaylist); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the library. + */ + public void removePlaylist(Playlist key) { + playlists.remove(key); + } + + //// util methods + + @Override + public String toString() { + return playlists.asUnmodifiableObservableList().size() + " playlists"; + // TODO: refine later + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Library // instanceof handles nulls + && playlists.equals(((Library) other).playlists)); + } + + @Override + public int hashCode() { + return playlists.hashCode(); + } +} diff --git a/src/main/java/seedu/jxmusic/model/Model.java b/src/main/java/seedu/jxmusic/model/Model.java new file mode 100644 index 000000000000..e9e3183c3c2b --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/Model.java @@ -0,0 +1,64 @@ +package seedu.jxmusic.model; + +import java.util.function.Predicate; + +import javafx.collections.ObservableList; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true **/ + Predicate PREDICATE_SHOW_ALL_PLAYLISTS = unused -> true; + + /** {@code Predicate} that always evaluate to true **/ + Predicate PREDICATE_SHOW_ALL_TRACKS = unused -> true; + + /** Clears existing backing model and replaces with the provided new data. */ + void resetData(ReadOnlyLibrary newData); + + /** Returns the Library */ + ReadOnlyLibrary getLibrary(); + + /** + * Returns true if a playlist with the same identity as {@code playlist} exists in the library. + */ + boolean hasPlaylist(Playlist playlist); + + /** + * Deletes the given playlist. + * The playlist must exist in the library. + */ + void deletePlaylist(Playlist target); + + /** + * Adds the given playlist. + * {@code playlist} must not already exist in the library. + */ + void addPlaylist(Playlist playlist); + + /** + * Replaces the given playlist {@code target} with {@code editedPlaylist}. + * {@code target} must exist in the jxmusic book. + * The playlist identity of {@code editedPlaylist} must not be the same as another existing playlist in the library. + */ + void updatePlaylist(Playlist target, Playlist editedPlaylist); + + /** Returns an unmodifiable view of the filtered playlist list */ + ObservableList getFilteredPlaylistList(); + + /** Returns an unmodifiable view of the filtered track list */ + ObservableList getFilteredTrackList(); + + /** + * Updates the filter of the filtered playlist list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPlaylistList(Predicate predicate); + + /** + * Updates the filter of the filtered track list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTrackList(Predicate predicate); +} diff --git a/src/main/java/seedu/jxmusic/model/ModelManager.java b/src/main/java/seedu/jxmusic/model/ModelManager.java new file mode 100644 index 000000000000..aa3102a97bc7 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/ModelManager.java @@ -0,0 +1,138 @@ +package seedu.jxmusic.model; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.jxmusic.commons.core.ComponentManager; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.model.LibraryChangedEvent; + +/** + * Represents the in-memory model of the library data. + */ +public class ModelManager extends ComponentManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final Library library; + private final FilteredList filteredPlaylists; + private final FilteredList filteredTrackList; + + /** + * Initializes a ModelManager with the given library and userPrefs. + */ + public ModelManager(ReadOnlyLibrary readOnlyLibrary, UserPrefs userPrefs) { + super(); + requireAllNonNull(readOnlyLibrary, userPrefs); + + logger.fine("Initializing with library: " + readOnlyLibrary + " and user prefs " + userPrefs); + + library = new Library(readOnlyLibrary); + filteredPlaylists = new FilteredList<>(library.getPlaylistList()); + ObservableList trackListFromSet = FXCollections.observableArrayList(library.getTracks()); + filteredTrackList = new FilteredList<>(trackListFromSet); + } + + public ModelManager() { + this(new Library(), new UserPrefs()); + } + + @Override + public void resetData(ReadOnlyLibrary newData) { + library.resetData(newData); + indicateModelChanged(); + } + + @Override + public ReadOnlyLibrary getLibrary() { + return library; + } + + /** Raises an event to indicate the model has changed */ + private void indicateModelChanged() { + raise(new LibraryChangedEvent(library)); + } + + @Override + public boolean hasPlaylist(Playlist playlist) { + requireNonNull(playlist); + return library.hasPlaylist(playlist); + } + + @Override + public void deletePlaylist(Playlist target) { + library.removePlaylist(target); + indicateModelChanged(); + } + + @Override + public void addPlaylist(Playlist playlist) { + library.addPlaylist(playlist); + updateFilteredPlaylistList(PREDICATE_SHOW_ALL_PLAYLISTS); + indicateModelChanged(); + } + + @Override + public void updatePlaylist(Playlist target, Playlist editedPlaylist) { + requireAllNonNull(target, editedPlaylist); + + library.updatePlaylist(target, editedPlaylist); + indicateModelChanged(); + } + + //=========== Filtered Playlist List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Playlist} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredPlaylistList() { + return FXCollections.unmodifiableObservableList(filteredPlaylists); + } + + /** + * Returns an unmodifiable view of the list of {@code tracks} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredTrackList() { + return FXCollections.unmodifiableObservableList(filteredTrackList); + } + + @Override + public void updateFilteredPlaylistList(Predicate predicate) { + requireNonNull(predicate); + filteredPlaylists.setPredicate(predicate); + } + + @Override + public void updateFilteredTrackList(Predicate predicate) { + requireNonNull(predicate); + filteredTrackList.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 library.equals(other.library) + && filteredPlaylists.equals(other.filteredPlaylists); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/jxmusic/model/Name.java similarity index 72% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/jxmusic/model/Name.java index 9982393dabb5..b2bf785c7b7a 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/jxmusic/model/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.jxmusic.model; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.jxmusic.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Playlist's name in the jxmusic book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -13,12 +13,12 @@ public class Name { "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the jxmusic must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - public final String fullName; + public final String nameString; /** * Constructs a {@code Name}. @@ -28,7 +28,7 @@ public class Name { public Name(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_NAME_CONSTRAINTS); - fullName = name; + nameString = name; } /** @@ -41,19 +41,19 @@ public static boolean isValidName(String test) { @Override public String toString() { - return fullName; + return nameString; } @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 + && nameString.toLowerCase().equals(((Name) other).nameString.toLowerCase())); // state check } @Override public int hashCode() { - return fullName.hashCode(); + return nameString.hashCode(); } } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/jxmusic/model/NameContainsKeywordsPredicate.java similarity index 66% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/jxmusic/model/NameContainsKeywordsPredicate.java index c9b5868427ca..1200185d5c53 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/jxmusic/model/NameContainsKeywordsPredicate.java @@ -1,14 +1,14 @@ -package seedu.address.model.person; +package seedu.jxmusic.model; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import seedu.jxmusic.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Playlist}'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(Playlist playlist) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsSubstringIgnoreCase(playlist.getName().nameString, keyword)); } @Override diff --git a/src/main/java/seedu/jxmusic/model/Playlist.java b/src/main/java/seedu/jxmusic/model/Playlist.java new file mode 100644 index 000000000000..8511c9e73355 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/Playlist.java @@ -0,0 +1,199 @@ +package seedu.jxmusic.model; + +import static seedu.jxmusic.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import seedu.jxmusic.commons.core.index.Index; + +/** + * Represents a Playlist in the jxmusic book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Playlist { + + // Identity fields + private final Name name; + + // Data fields + private final List tracks; + private int size; + + /** + * Only name field must be present. List of tracks to be added later. + */ + public Playlist(Name name) { + requireAllNonNull(name); + this.name = name; + this.tracks = new ArrayList<>(); + this.size = 0; + } + + /** + * Constructs a Playlist along with its list of tracks + * @param name name of the playlist, cannot be null + * @param tracks list of tracks of the playlist, cannot be null + */ + public Playlist(Name name, List tracks) { + requireAllNonNull(name); + this.name = name; + this.tracks = tracks; + this.size = this.tracks.size(); + } + + public Name getName() { + return name; + } + + /** + * Returns the list of tracks in the playlist + */ + public List getTracks() { + return Collections.unmodifiableList(tracks); + } + + /** + * Returns the track of index in the playlist + */ + public Track get(int index) throws ArrayIndexOutOfBoundsException { + return Collections.unmodifiableList(tracks).get(index); + } + + /** + * Adds a track into the playlist + * @param track to be added to the playlist, must not be null + */ + public void addTrack(Track track) { + if (track == null) { + throw new NullPointerException("Track must not be null"); + } + tracks.add(track); + setSize(getSize() + 1); + } + + /** + * Deletes a track from the playlist + * @param track to be deleted from the playlist, must not be null + */ + public boolean deleteTrack(Track track) { + if (track == null) { + throw new NullPointerException("Track must not be null"); + } + if (tracks.remove(track)) { + setSize(getSize() - 1); + return true; + } + return false; + } + + /** + * Returns true if both playlists have the same name + * This defines a weaker notion of equality between two playlists. + */ + public boolean isSamePlaylist(Playlist otherPlaylist) { + if (otherPlaylist == this) { + return true; + } + + return otherPlaylist != null + && otherPlaylist.getName().equals(getName()); + } + + /** + * Returns a copy of this playlist + */ + public Playlist copy() { + List tracks = this.getTracks(); + Name nameCopy = this.getName(); + Playlist copy = new Playlist(nameCopy); + for (Track track : tracks) { + copy.addTrack(track); + } + return copy; + } + + /** + * Checks if playlist has a specific track + * @param targetTrack Track to check for + * @return true if track is in playlist + */ + public boolean hasTrack(Track targetTrack) { + for (Track track : getTracks()) { + if (track.getFileName().equalsIgnoreCase(targetTrack.getFileName())) { + return true; + } + } + return false; + } + + public Index getTrackIndex(Track targetTrack) { + int index = 0; + for (Track track : getTracks()) { + if (track.equals(targetTrack)) { + return Index.fromZeroBased(index); + } + index++; + } + return Index.fromOneBased(0); + } + + private void setSize(int size) { + this.size = size; + } + + /** + * Returns number of tracks in this playlist + */ + public int getSize() { + return size; + } + + /** + * Returns true if there are no tracks in this playlist + */ + public boolean isEmpty() { + return (size == 0); + } + + /** + * Returns true if both playlists have the same name and tracks. + * This defines a stronger notion of equality between two playlists. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Playlist)) { + return false; + } + + Playlist otherPlaylist = (Playlist) other; + return otherPlaylist.getName().equals(getName()) + && otherPlaylist.getTracks().equals(getTracks()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, tracks); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append('\n') + .append("Tracks: \n"); + tracks.forEach(t -> { + builder.append(t) + .append('\n'); + }); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/jxmusic/model/ReadOnlyLibrary.java b/src/main/java/seedu/jxmusic/model/ReadOnlyLibrary.java new file mode 100644 index 000000000000..f9751481ed70 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/ReadOnlyLibrary.java @@ -0,0 +1,22 @@ +package seedu.jxmusic.model; + +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; + +/** + * Unmodifiable view of an jxmusic book + */ +public interface ReadOnlyLibrary { + + /** + * Returns an unmodifiable view of the playlists list. + * This list will not contain any duplicate playlists. + */ + ObservableList getPlaylistList(); + + /** + * Get the tracks in library + * @return the set of tracks + */ + ObservableSet getTracks(); +} diff --git a/src/main/java/seedu/jxmusic/model/Track.java b/src/main/java/seedu/jxmusic/model/Track.java new file mode 100644 index 000000000000..067341e2ff44 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/Track.java @@ -0,0 +1,157 @@ +package seedu.jxmusic.model; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import javafx.scene.media.Media; +import javafx.scene.media.MediaException; + +import seedu.jxmusic.commons.util.AppUtil; +import seedu.jxmusic.commons.util.CollectionUtil; + +/** + * Represents a track in JxMusic + * Guarantees: immutable; file is supported by javafx media as declared in {@link #isSupported(File)} + */ +public class Track implements Comparable { + public static final String MESSAGE_FILE_NOT_EXIST = + "Track file does not exist"; + public static final String MESSAGE_FILE_NOT_SUPPORTED = + "Track file not supported by javafx media"; + public static final String MP3_EXTENSION = ".mp3"; + private static final int[] ID3V2_HEADER = new int[]{0x49, 0x44, 0x33}; + private static final int[] MP3_HEADER = new int[]{0xff, 0xfb}; // for mp3 files without id3v2 header + private final File file; + // fileNameWithoutExtension not appended with MP3_EXTENSION + private final String fileNameWithoutExtension; + + /** + * Constructs a {@code Track}. + * By using a Name to restrict null or empty string + * + * @param trackFileName mp3 file name of the track, does not accept ".mp3" suffix + */ + public Track(Name trackFileName) { + CollectionUtil.requireAllNonNull(trackFileName); + String fileNameDotMp3; + if (trackFileName.toString().endsWith(MP3_EXTENSION)) { + fileNameDotMp3 = trackFileName.toString(); + fileNameWithoutExtension = removeMp3Extension(fileNameDotMp3); + } else { + fileNameWithoutExtension = trackFileName.toString(); + fileNameDotMp3 = fileNameWithoutExtension + MP3_EXTENSION; + } + File file = new File(Library.LIBRARYDIR + fileNameDotMp3); + AppUtil.checkArgument(file.exists(), MESSAGE_FILE_NOT_EXIST); + AppUtil.checkArgument(isSupported(file), MESSAGE_FILE_NOT_SUPPORTED); + this.file = file; + } + + public Track(File file) { + CollectionUtil.requireAllNonNull(file); + AppUtil.checkArgument(file.exists(), MESSAGE_FILE_NOT_EXIST); + AppUtil.checkArgument(isSupported(file), MESSAGE_FILE_NOT_SUPPORTED); + this.file = file; + String fileNameDotMp3 = file.getName(); + fileNameWithoutExtension = removeMp3Extension(fileNameDotMp3); + } + + public File getFile() { + return file; + } + + public String getFileName() { + return file.getName(); + } + + public String getFileNameWithoutExtension() { + return fileNameWithoutExtension; + } + + /** + * Checks for javafx media support and header bytes + * + * @param file the file to check + * @return true if file is supported by javafx media and has valid header bytes, false otherwise + */ + private static boolean isSupported(File file) { + // todo check by using PlayableTrack constructor in try block + AppUtil.checkArgument(file.exists(), MESSAGE_FILE_NOT_EXIST); + try { + Media media = new Media(file.toURI().toString()); + if (!hasMp3Header(file)) { + return false; + } + } catch (MediaException e) { + return false; + } + return true; + } + + /** + * Checks whether a file is truly mp3 file by looking at header bytes + * + * @param file the file to check + * @return true if file has id3v2 or mp3 header bytes + */ + private static boolean hasMp3Header(File file) { + byte[] first3Bytes = new byte[3]; + try { + FileInputStream fis = new FileInputStream(file); + fis.readNBytes(first3Bytes, 0, 3); + } catch (IOException e) { + return false; + } + boolean hasId3V2 = true; + for (int i = 0; i < ID3V2_HEADER.length; i++) { + if (first3Bytes[i] != (byte) ID3V2_HEADER[i]) { + hasId3V2 = false; + break; + } + } + if (hasId3V2) { + return true; + } + boolean isMp3WithoutId3V2 = true; + for (int i = 0; i < MP3_HEADER.length; i++) { + if (first3Bytes[i] != (byte) MP3_HEADER[i]) { + isMp3WithoutId3V2 = false; + break; + } + } + if (isMp3WithoutId3V2) { + return true; + } + return false; + } + + private String removeMp3Extension(String fileNameDotMp3) { + return fileNameDotMp3.substring(0, fileNameDotMp3.length() - MP3_EXTENSION.length()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Track // instanceof handles nulls + && fileNameWithoutExtension.equalsIgnoreCase(( + (Track) other).fileNameWithoutExtension)); // state check + } + + @Override + public int hashCode() { + return fileNameWithoutExtension.toLowerCase().hashCode(); + } + + @Override + public String toString() { + return fileNameWithoutExtension; + } + + @Override + public int compareTo(Object o) { + String otherName = ((Track) o).getFileNameWithoutExtension().toLowerCase(); + return this.getFileNameWithoutExtension().toLowerCase() + .compareTo(otherName); + } +} diff --git a/src/main/java/seedu/jxmusic/model/TrackNameContainsKeywordsPredicate.java b/src/main/java/seedu/jxmusic/model/TrackNameContainsKeywordsPredicate.java new file mode 100644 index 000000000000..9209398742c9 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/TrackNameContainsKeywordsPredicate.java @@ -0,0 +1,32 @@ +package seedu.jxmusic.model; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.jxmusic.commons.util.StringUtil; + +/** + * Tests that a {@code Track}'s {@code Name} matches any of the keywords given. + */ +public class TrackNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TrackNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Track track) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsSubstringIgnoreCase(track.getFileNameWithoutExtension(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TrackNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TrackNameContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/jxmusic/model/UniquePlaylistList.java b/src/main/java/seedu/jxmusic/model/UniquePlaylistList.java new file mode 100644 index 000000000000..f9897e718b6f --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/UniquePlaylistList.java @@ -0,0 +1,136 @@ +package seedu.jxmusic.model; + +import static java.util.Objects.requireNonNull; +import static seedu.jxmusic.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.jxmusic.model.exceptions.DuplicatePlaylistException; +import seedu.jxmusic.model.exceptions.PlaylistNotFoundException; + +/** + * A list of playlists that enforces uniqueness between its elements and does not allow nulls. + * A playlist is considered unique by comparing using {@code Playlist#isSamePlaylist(Playlist)}. + * As such, adding and updating of playlists uses Playlist#isSamePlaylist(Playlist) for equality so + * as to ensure that the playlist being added or updated is unique in terms of identity in the UniquePlaylistList. + * However, the removal of a playlist uses Playlist#equals(Object) so + * as to ensure that the playlist with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Playlist#isSamePlaylist(Playlist) + */ +public class UniquePlaylistList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent playlist as the given argument. + */ + public boolean contains(Playlist toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePlaylist); + } + + /** + * Adds a playlist to the list. + * The playlist must not already exist in the list. + */ + public void add(Playlist toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePlaylistException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the playlist {@code target} in the list with {@code editedPlaylist}. + * {@code target} must exist in the list. + * The playlist identity of {@code editedPlaylist} must not be the same as another existing playlist in the list. + */ + public void replacePlaylist(Playlist target, Playlist editedPlaylist) { + requireAllNonNull(target, editedPlaylist); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PlaylistNotFoundException(); + } + + if (!target.isSamePlaylist(editedPlaylist) && contains(editedPlaylist)) { + throw new DuplicatePlaylistException(); + } + + internalList.set(index, editedPlaylist); + } + + /** + * Removes the equivalent playlist from the list. + * The playlist must exist in the list. + */ + public void remove(Playlist toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PlaylistNotFoundException(); + } + } + + public void setPlaylists(UniquePlaylistList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code playlists}. + * {@code playlists} must not contain duplicate playlists. + */ + public void setPlaylists(List playlists) { + requireAllNonNull(playlists); + if (!playlistsAreUnique(playlists)) { + throw new DuplicatePlaylistException(); + } + + internalList.setAll(playlists); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePlaylistList // instanceof handles nulls + && internalList.equals(((UniquePlaylistList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code playlists} contains only unique playlists. + */ + private boolean playlistsAreUnique(List playlists) { + for (int i = 0; i < playlists.size() - 1; i++) { + for (int j = i + 1; j < playlists.size(); j++) { + if (playlists.get(i).isSamePlaylist(playlists.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/jxmusic/model/UserPrefs.java similarity index 68% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/jxmusic/model/UserPrefs.java index 980b2b388852..1391cb841ebf 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/jxmusic/model/UserPrefs.java @@ -1,10 +1,10 @@ -package seedu.address.model; +package seedu.jxmusic.model; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import seedu.jxmusic.commons.core.GuiSettings; /** * Represents User's preferences. @@ -12,7 +12,7 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path libraryFilePath = Paths.get("data" , "library.json"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -30,12 +30,12 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getLibraryFilePath() { + return libraryFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setLibraryFilePath(Path libraryFilePath) { + this.libraryFilePath = libraryFilePath; } @Override @@ -50,19 +50,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath); + && Objects.equals(libraryFilePath, o.libraryFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, libraryFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + libraryFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/jxmusic/model/exceptions/DuplicatePlaylistException.java b/src/main/java/seedu/jxmusic/model/exceptions/DuplicatePlaylistException.java new file mode 100644 index 000000000000..7a73aee4e509 --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/exceptions/DuplicatePlaylistException.java @@ -0,0 +1,11 @@ +package seedu.jxmusic.model.exceptions; + +/** + * Signals that the operation will result in duplicate Playlists + * Playlists are considered duplicates if they have the same identity + */ +public class DuplicatePlaylistException extends RuntimeException { + public DuplicatePlaylistException() { + super("Operation would result in duplicate playlists"); + } +} diff --git a/src/main/java/seedu/jxmusic/model/exceptions/PlaylistNotFoundException.java b/src/main/java/seedu/jxmusic/model/exceptions/PlaylistNotFoundException.java new file mode 100644 index 000000000000..35b142a0e0ba --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/exceptions/PlaylistNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.jxmusic.model.exceptions; + +/** + * Signals that the operation is unable to find the specified playlist. + */ +public class PlaylistNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/jxmusic/model/util/SampleDataUtil.java b/src/main/java/seedu/jxmusic/model/util/SampleDataUtil.java new file mode 100644 index 000000000000..71d9b2ae45de --- /dev/null +++ b/src/main/java/seedu/jxmusic/model/util/SampleDataUtil.java @@ -0,0 +1,66 @@ +package seedu.jxmusic.model.util; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Name; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.ReadOnlyLibrary; +import seedu.jxmusic.model.Track; + +/** + * Contains utility methods for populating {@code Library} with sample data. + */ +public class SampleDataUtil { + public static Track[] getSampleTracks() { + return new Track[] { + new Track(new Name("aliez")), + new Track(new Name("acyort")), + new Track(new Name("scarborough fair")), + new Track(new Name("Ihojin no Yaiba")) + }; + } + + public static Playlist[] getSamplePlaylists() { + return new Playlist[] { + new Playlist(new Name("Aldnoah Zero"), getTrackList("aliez", "acyort")), + new Playlist(new Name("Favourites"), getTrackList("scarborough fair", "Ihojin no Yaiba")) + }; + } + + public static ReadOnlyLibrary getSampleLibrary() { + Library sampleAb = new Library(); + for (Playlist samplePlaylist : getSamplePlaylists()) { + sampleAb.addPlaylist(samplePlaylist); + } + for (Track sampleTrack : getSampleTracks()) { + sampleAb.addTrack(sampleTrack); + } + return sampleAb; + } + + /** + * Populates the library with sample playlists + * @param tracksOnlyLibrary library that only has tracks + * @return + */ + public static ReadOnlyLibrary populateSamplePlaylists(ReadOnlyLibrary tracksOnlyLibrary) { + Library tracksOnlyLib = ((Library) tracksOnlyLibrary); + for (Playlist samplePlaylist : getSamplePlaylists()) { + tracksOnlyLib.addPlaylist(samplePlaylist); + } + return tracksOnlyLib; + } + + /** + * Returns a tag set containing the list of strings given. + */ + public static List getTrackList(String... strings) { + return Arrays.stream(strings) + .map(Name::new) + .map(Track::new) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/seedu/jxmusic/player/Playable.java b/src/main/java/seedu/jxmusic/player/Playable.java new file mode 100644 index 000000000000..39b54aaee56e --- /dev/null +++ b/src/main/java/seedu/jxmusic/player/Playable.java @@ -0,0 +1,25 @@ +package seedu.jxmusic.player; + +import javafx.util.Duration; +import seedu.jxmusic.logic.commands.exceptions.CommandException; + +/** + * Playable defines functions that is used by Player + */ +public interface Playable { + /** + * Status of Playable + * {@code PLAYING}, {@code PAUSED}, {@code STOPPED} represents the respective MediaPlayer statuses + * {@code UNINITIALIZED} means player has yet to play any track/playlist + * {@code ERROR} represents other MediaPlayer statuses since they are unused in our app + */ + enum Status { + PLAYING, PAUSED, STOPPED, UNINITIALIZED, ERROR + } + Status getStatus(); + void play(boolean unpause); + void pause(); + void stop(); + void seek(Duration time) throws CommandException; + Duration getDuration(); +} diff --git a/src/main/java/seedu/jxmusic/player/PlayablePlaylist.java b/src/main/java/seedu/jxmusic/player/PlayablePlaylist.java new file mode 100644 index 000000000000..86b049c1adaa --- /dev/null +++ b/src/main/java/seedu/jxmusic/player/PlayablePlaylist.java @@ -0,0 +1,80 @@ +package seedu.jxmusic.player; + +import java.util.List; +import java.util.stream.Collectors; + +import javafx.util.Duration; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Playlist; + +/** + * Playlist1 structure used by Player + */ +public class PlayablePlaylist implements Playable { + private List playableTrackList; + private int currentIndex; + private PlayableTrack current; + private Playlist playlist; + public PlayablePlaylist(Playlist playlist) { + this.playlist = playlist; + // get list of tracks in Playlist1 parameter and construct PlayableTracks + // set playableTrackList with list of PlayableTrack + this.playableTrackList = playlist.getTracks().stream() + .map(PlayableTrack::new) + .collect(Collectors.toList()); + for (int i = 0; i < playableTrackList.size() - 1; i++) { // iterate all except last one + playableTrackList.get(i).setOnEndOfMedia(() -> { + next(); + }); + } + } + @Override + public void play(boolean unpause) { + if (current == null) { + currentIndex = 0; + current = playableTrackList.get(currentIndex); + } + current.play(unpause); + } + + @Override + public void pause() { + if (current == null) { + currentIndex = 0; + current = playableTrackList.get(currentIndex); + } + current.pause(); + } + + @Override + public void stop() { + current.stop(); + currentIndex = 0; + current = playableTrackList.get(currentIndex); + } + + @Override + public Status getStatus() { + return current.getStatus(); + } + + @Override + public void seek(Duration time) throws CommandException { + current.seek(time); + } + + @Override + public Duration getDuration() { + return current.getDuration(); + } + + /** + * Plays next track in the playlist + */ + public void next() { + stop(); + current = playableTrackList.get(currentIndex + 1); + current.play(false); + currentIndex++; + } +} diff --git a/src/main/java/seedu/jxmusic/player/PlayableTrack.java b/src/main/java/seedu/jxmusic/player/PlayableTrack.java new file mode 100644 index 000000000000..7886a3ee7d57 --- /dev/null +++ b/src/main/java/seedu/jxmusic/player/PlayableTrack.java @@ -0,0 +1,78 @@ +package seedu.jxmusic.player; + +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.util.Duration; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Track; + +/** + * Playlist1 structure used by Player + */ +public class PlayableTrack implements Playable { + private MediaPlayer mediaPlayer; + private Media media; + private String fileName; + private Track track; + + public PlayableTrack(Track track) { + this.track = track; + media = new Media(track.getFile().toURI().toString()); + mediaPlayer = new MediaPlayer(media); + } + + public String getTrackName() { + return track.getFileNameWithoutExtension(); + } + + @Override + public void play(boolean unpause) { + if (!unpause) { + mediaPlayer.setStartTime(new Duration(0)); + } + mediaPlayer.play(); + } + + @Override + public void stop() { + mediaPlayer.stop(); + } + + @Override + public void pause() { + mediaPlayer.pause(); + } + + @Override + public Status getStatus() { + switch (mediaPlayer.getStatus()) { + case PLAYING: return Status.PLAYING; + case PAUSED: return Status.PAUSED; + case STOPPED: return Status.STOPPED; + default: return Status.ERROR; + } + } + + @Override + public void seek(Duration time) throws CommandException { + Duration trackDuration = media.getDuration(); + if (time.compareTo(trackDuration) > 0) { + throw new CommandException("Required time is beyond track's duration"); + } + System.out.println("playabletrack seek to " + time.toSeconds() + " second(s)"); + mediaPlayer.seek(time); + } + + @Override + public Duration getDuration() { + return media.getDuration(); + } + + public void setOnEndOfMedia(Runnable runnable) { + mediaPlayer.setOnEndOfMedia(runnable); + } + + public MediaPlayer getMediaPlayer() { + return mediaPlayer; + } +} diff --git a/src/main/java/seedu/jxmusic/player/Player.java b/src/main/java/seedu/jxmusic/player/Player.java new file mode 100644 index 000000000000..10dd24ec20e6 --- /dev/null +++ b/src/main/java/seedu/jxmusic/player/Player.java @@ -0,0 +1,53 @@ +package seedu.jxmusic.player; + +import javafx.util.Duration; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * The API of the Player component. + */ +public interface Player { + /** + * Continues playing from a pause/stop + */ + void play(); + + /** + * Plays a playlist + */ + void play(Playlist playlist); + + /** + * Plays a track + */ + void play(Track track); + + /** + * Stops the playing playlist/track + */ + void stop(); + + + /** + * Pauses the playing track + */ + void pause(); + + + /** + * Seeks to the specified {@code time} in the playing track + */ + void seek(Duration time) throws CommandException; + + /** + * Returns the duration of the currently playing track + */ + Duration getDuration(); + + /** + * Returns the status of the player + */ + Playable.Status getStatus(); +} diff --git a/src/main/java/seedu/jxmusic/player/PlayerManager.java b/src/main/java/seedu/jxmusic/player/PlayerManager.java new file mode 100644 index 000000000000..cfb9a238d2ee --- /dev/null +++ b/src/main/java/seedu/jxmusic/player/PlayerManager.java @@ -0,0 +1,92 @@ +package seedu.jxmusic.player; + +import java.util.logging.Logger; + +import javafx.util.Duration; +import seedu.jxmusic.commons.core.ComponentManager; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * The actual implemented player to be used by Logic component + */ +public class PlayerManager extends ComponentManager implements Player { + private static final Logger logger = LogsCenter.getLogger(PlayerManager.class); + private static PlayerManager instance; + private static Playable currentlyPlaying; + + private PlayerManager() {} + + public static PlayerManager getInstance() { + if (instance == null) { + instance = new PlayerManager(); + } + return instance; + } + + @Override + public void play() { + logger.info("Continue from pause"); + if (currentlyPlaying != null) { + currentlyPlaying.play(true); + } + } + + @Override + public void play(Playlist playlist) { + logger.info("Play playlist " + playlist.getName()); + if (currentlyPlaying != null) { + currentlyPlaying.stop(); + } + currentlyPlaying = new PlayablePlaylist(playlist); + currentlyPlaying.play(false); + } + + @Override + public void play(Track track) { + logger.info("Play track " + track.getFileNameWithoutExtension()); + if (currentlyPlaying != null) { + currentlyPlaying.stop(); + } + currentlyPlaying = new PlayableTrack(track); + currentlyPlaying.play(false); + } + + @Override + public void pause() { + logger.info("Pause"); + if (currentlyPlaying != null) { + currentlyPlaying.pause(); + } + } + + @Override + public void stop() { + logger.info("Stop"); + if (currentlyPlaying != null) { + currentlyPlaying.stop(); + } + } + + @Override + public void seek(Duration time) throws CommandException { + logger.info("Seek to " + time.toSeconds() + " second(s)"); + currentlyPlaying.seek(time); + } + + @Override + public Duration getDuration() { + return currentlyPlaying.getDuration(); + } + + @Override + public Playable.Status getStatus() { + if (currentlyPlaying == null) { + return Playable.Status.UNINITIALIZED; + } + return currentlyPlaying.getStatus(); + } + +} diff --git a/src/main/java/seedu/jxmusic/storage/JsonFileStorage.java b/src/main/java/seedu/jxmusic/storage/JsonFileStorage.java new file mode 100644 index 000000000000..107256cf0d89 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/JsonFileStorage.java @@ -0,0 +1,67 @@ +package seedu.jxmusic.storage; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; + +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.storage.jsonserdes.LibraryDeserializer; +import seedu.jxmusic.storage.jsonserdes.LibrarySerializer; +import seedu.jxmusic.storage.jsonserdes.PlaylistDeserializer; +import seedu.jxmusic.storage.jsonserdes.PlaylistSerializer; + +/** + * Stores library data in an Json file + */ +public class JsonFileStorage { + /** + * Saves the given library data to the specified file. + */ + public static void saveDataToFile(Path file, Library library) + throws Exception { + final GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Library.class, new LibrarySerializer()); + gsonBuilder.registerTypeAdapter(Playlist.class, new PlaylistSerializer()); + gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + + final String json = gson.toJson(library); + try (PrintWriter out = new PrintWriter(file.toString())) { + out.println(json); + } catch (Exception e) { + throw new AssertionError("Unexpected exception " + e.getMessage(), e); + } + } + + /** + * Returns library in the file or an empty library + */ + public static Library loadDataFromFile(Path file) throws DataConversionException, FileNotFoundException { + // Configure Gson + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Library.class, new LibraryDeserializer()); + gsonBuilder.registerTypeAdapter(Playlist.class, new PlaylistDeserializer()); + Gson gson = gsonBuilder.create(); + // The JSON data + Reader reader = new FileReader(file.toString()); + Library library; + try { + // Parse JSON to Java + library = gson.fromJson(reader, Library.class); + } catch (JsonIOException | JsonSyntaxException | InvalidPathException e) { + throw new DataConversionException(e); + } + + return library; + } +} diff --git a/src/main/java/seedu/jxmusic/storage/JsonLibraryStorage.java b/src/main/java/seedu/jxmusic/storage/JsonLibraryStorage.java new file mode 100644 index 000000000000..c363b0482be0 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/JsonLibraryStorage.java @@ -0,0 +1,83 @@ +package seedu.jxmusic.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Logger; + +import javafx.collections.ObservableSet; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.commons.util.FileUtil; +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.ReadOnlyLibrary; +import seedu.jxmusic.model.Track; + +/** + * A class to access library data stored as an json file on the hard disk. + */ +public class JsonLibraryStorage implements LibraryStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonLibraryStorage.class); + + private Path filePath; + + public JsonLibraryStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getLibraryFilePath() { + return filePath; + } + + @Override + public ReadOnlyLibrary readLibrary() throws DataConversionException, IOException { + return readLibrary(filePath); + } + + /** + * Similar to {@link #readLibrary()} + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public ReadOnlyLibrary readLibrary(Path filePath) throws DataConversionException, + FileNotFoundException { + requireNonNull(filePath); + + Library loadedLibrary = new Library(); + ObservableSet trackSet = TracksScanner.scan(Paths.get(Library.LIBRARYDIR)); + if (Files.exists(filePath)) { + loadedLibrary = JsonFileStorage.loadDataFromFile(filePath); + } else { + logger.info("Library file " + filePath + " not found"); + } + loadedLibrary.setTracks(trackSet); + return loadedLibrary; + } + + @Override + public void saveLibrary(ReadOnlyLibrary library) throws IOException { + saveLibrary(library, filePath); + } + + /** + * Similar to {@link #saveLibrary(ReadOnlyLibrary)} + * @param filePath location of the data. Cannot be null + */ + public void saveLibrary(ReadOnlyLibrary library, Path filePath) throws IOException { + requireNonNull(library); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + try { + JsonFileStorage.saveDataToFile(filePath, new Library(library)); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/jxmusic/storage/JsonUserPrefsStorage.java similarity index 86% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/jxmusic/storage/JsonUserPrefsStorage.java index 2ab927023cc4..320c5520c9ec 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/jxmusic/storage/JsonUserPrefsStorage.java @@ -1,12 +1,12 @@ -package seedu.address.storage; +package seedu.jxmusic.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.UserPrefs; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.commons.util.JsonUtil; +import seedu.jxmusic.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/jxmusic/storage/LibraryStorage.java b/src/main/java/seedu/jxmusic/storage/LibraryStorage.java new file mode 100644 index 000000000000..ea1ea166264b --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/LibraryStorage.java @@ -0,0 +1,44 @@ +package seedu.jxmusic.storage; + +import java.io.IOException; +import java.nio.file.Path; + +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.model.ReadOnlyLibrary; + +/** + * Represents a storage for {@link seedu.jxmusic.model.Library}. + */ +public interface LibraryStorage { + + /** + * Returns the file path of the data file. + */ + Path getLibraryFilePath(); + + /** + * Returns Library data as a {@link ReadOnlyLibrary}. + * 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. + */ + ReadOnlyLibrary readLibrary() throws DataConversionException, IOException; + + /** + * @see #getLibraryFilePath() + */ + ReadOnlyLibrary readLibrary(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyLibrary} to the storage. + * @param library cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveLibrary(ReadOnlyLibrary library) throws IOException; + + /** + * @see #saveLibrary(ReadOnlyLibrary) + */ + void saveLibrary(ReadOnlyLibrary library, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/jxmusic/storage/Storage.java b/src/main/java/seedu/jxmusic/storage/Storage.java new file mode 100644 index 000000000000..e5e3fd484efc --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/Storage.java @@ -0,0 +1,39 @@ +package seedu.jxmusic.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.jxmusic.commons.events.model.LibraryChangedEvent; +import seedu.jxmusic.commons.events.storage.DataSavingExceptionEvent; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.model.ReadOnlyLibrary; +import seedu.jxmusic.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends LibraryStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(UserPrefs userPrefs) throws IOException; + + @Override + Path getLibraryFilePath(); + + @Override + ReadOnlyLibrary readLibrary() throws DataConversionException, IOException; + + @Override + void saveLibrary(ReadOnlyLibrary library) throws IOException; + + /** + * Saves the current version of the Library to the hard disk. + * Creates the data file if it is missing. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleLibraryChangedEvent(LibraryChangedEvent lce); +} diff --git a/src/main/java/seedu/jxmusic/storage/StorageManager.java b/src/main/java/seedu/jxmusic/storage/StorageManager.java new file mode 100644 index 000000000000..be0baa086c8f --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/StorageManager.java @@ -0,0 +1,93 @@ +package seedu.jxmusic.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import seedu.jxmusic.commons.core.ComponentManager; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.model.LibraryChangedEvent; +import seedu.jxmusic.commons.events.storage.DataSavingExceptionEvent; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.model.ReadOnlyLibrary; +import seedu.jxmusic.model.UserPrefs; + +/** + * Manages storage of Library data in local storage. + */ +public class StorageManager extends ComponentManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private LibraryStorage libraryStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(LibraryStorage libraryStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.libraryStorage = libraryStorage; + 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(UserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ Library methods ============================== + + @Override + public Path getLibraryFilePath() { + return libraryStorage.getLibraryFilePath(); + } + + @Override + public ReadOnlyLibrary readLibrary() throws DataConversionException, IOException { + return readLibrary(libraryStorage.getLibraryFilePath()); + } + + @Override + public ReadOnlyLibrary readLibrary(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return libraryStorage.readLibrary(filePath); + } + + @Override + public void saveLibrary(ReadOnlyLibrary library) throws IOException { + saveLibrary(library, libraryStorage.getLibraryFilePath()); + } + + @Override + public void saveLibrary(ReadOnlyLibrary library, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + libraryStorage.saveLibrary(library, filePath); + } + + + @Override + @Subscribe + public void handleLibraryChangedEvent(LibraryChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); + try { + saveLibrary(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + +} diff --git a/src/main/java/seedu/jxmusic/storage/TracksScanner.java b/src/main/java/seedu/jxmusic/storage/TracksScanner.java new file mode 100644 index 000000000000..63e34a170d43 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/TracksScanner.java @@ -0,0 +1,73 @@ +package seedu.jxmusic.storage; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.HashSet; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; +import seedu.jxmusic.MainApp; +import seedu.jxmusic.model.Track; + +/** + * Scan through the library directory to get all the MP3 files. + * If library directory doesn't exist, copy from resources/library/* to library directory + */ +public class TracksScanner { + /** + * Scans the library directory to get a set of tracks + * @return set of tracks in the library directory + */ + public static ObservableSet scan(Path libraryDir) { + File folder = libraryDir.toFile(); + if (!folder.exists() || folder.listFiles().length == 0) { + try { + final File jarFile = new File(MainApp.class + .getProtectionDomain() + .getCodeSource() + .getLocation() + .getPath()); + if (jarFile.isFile()) { + Runtime rt = Runtime.getRuntime(); + // runs command line: `unzip jxmusic.jar "library/*"` + Process pr = rt.exec("unzip " + jarFile.getName() + " \"library/*\""); + pr.waitFor(); + } else { + URL src = MainApp.class.getResource("/library/"); + libraryDir.toFile().mkdirs(); + File f = Paths.get(src.toURI()).toFile(); + Files.walk(Paths.get(f.getAbsolutePath())).filter(p -> p.toString().endsWith(".mp3")).forEach(a -> { + Path b = Paths.get(libraryDir.toString(), a.toString().substring(src.toString().length() - 6)); + try { + if (!a.toString().equals(src)) { + Files.copy(a, b, new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + } catch (IOException | URISyntaxException | InterruptedException e) { + //permission issue + e.printStackTrace(); + } + } + File[] trackFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(Track.MP3_EXTENSION)); + ObservableSet trackSet = FXCollections.observableSet(new HashSet<>()); + for (File trackFile : trackFiles) { + try { + trackSet.add(new Track(trackFile)); + } catch (IllegalArgumentException e) { + // do nothing, skip invalid track + } + } + return trackSet; + } +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/jxmusic/storage/UserPrefsStorage.java similarity index 76% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/jxmusic/storage/UserPrefsStorage.java index 877b0ee5c4f0..27010406d480 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/jxmusic/storage/UserPrefsStorage.java @@ -1,14 +1,14 @@ -package seedu.address.storage; +package seedu.jxmusic.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +import seedu.jxmusic.commons.exceptions.DataConversionException; +import seedu.jxmusic.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.jxmusic.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -26,7 +26,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * Saves the given {@link seedu.jxmusic.model.UserPrefs} 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/jxmusic/storage/jsonserdes/LibraryDeserializer.java b/src/main/java/seedu/jxmusic/storage/jsonserdes/LibraryDeserializer.java new file mode 100644 index 000000000000..24fe3770b8e3 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/jsonserdes/LibraryDeserializer.java @@ -0,0 +1,34 @@ +package seedu.jxmusic.storage.jsonserdes; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Playlist; + +/** + * This is a deserializer for library class + */ +public class LibraryDeserializer implements JsonDeserializer { + @Override + public Library deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) + throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + // Delegate the deserialization to the context + Playlist[] playlist = context.deserialize(jsonObject.get("playlists"), Playlist[].class); + List playListList = Arrays.asList(playlist); + final Library library = new Library(); + library.setPlaylists(playListList); + return library; + } +} + + + diff --git a/src/main/java/seedu/jxmusic/storage/jsonserdes/LibrarySerializer.java b/src/main/java/seedu/jxmusic/storage/jsonserdes/LibrarySerializer.java new file mode 100644 index 000000000000..f53cc5894449 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/jsonserdes/LibrarySerializer.java @@ -0,0 +1,26 @@ +package seedu.jxmusic.storage.jsonserdes; + +import java.lang.reflect.Type; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import seedu.jxmusic.model.Library; + +/** + * Serializes the library into json text. + */ +public class LibrarySerializer implements JsonSerializer { + + @Override + public JsonElement serialize(final Library library, final Type typeOfSrc, final JsonSerializationContext context) { + final JsonObject jsonObject = new JsonObject(); + + final JsonElement jsonPlaylists = context.serialize(library.getPlaylistList()); + jsonObject.add("playlists", jsonPlaylists); + + return jsonObject; + } +} diff --git a/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistDeserializer.java b/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistDeserializer.java new file mode 100644 index 000000000000..e6ab8a3a36cf --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistDeserializer.java @@ -0,0 +1,45 @@ +package seedu.jxmusic.storage.jsonserdes; + +import java.io.File; +import java.lang.reflect.Type; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import seedu.jxmusic.model.Library; +import seedu.jxmusic.model.Name; +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Deserializer for the playlist + */ +public class PlaylistDeserializer implements JsonDeserializer { + + @Override + public Playlist deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) + throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + + Name playlistname = new Name(jsonObject.get("name").getAsString()); + final Playlist playlist = new Playlist(playlistname); + final JsonArray jsonTracksArray = jsonObject.get("tracks").getAsJsonArray(); + final String[] tracks = new String[jsonTracksArray.size()]; + for (int i = 0; i < tracks.length; i++) { + final JsonElement jsonTrack = jsonTracksArray.get(i); + try { + // Name trackname = new Name(jsonTrack.getAsString()); + // Track track = new Track(trackname); + Track track = new Track(new File(Library.LIBRARYDIR + jsonTrack.getAsString())); + playlist.addTrack(track); + } catch (Exception e) { + e.printStackTrace(); + } + } + return playlist; + } +} diff --git a/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistSerializer.java b/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistSerializer.java new file mode 100644 index 000000000000..e8e872f936c3 --- /dev/null +++ b/src/main/java/seedu/jxmusic/storage/jsonserdes/PlaylistSerializer.java @@ -0,0 +1,40 @@ +package seedu.jxmusic.storage.jsonserdes; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import seedu.jxmusic.model.Playlist; +import seedu.jxmusic.model.Track; + +/** + * Serializes the playlists. + */ +public class PlaylistSerializer implements JsonSerializer { + @Override + public JsonElement serialize(final Playlist playlist, final Type typeOfSrc, + final JsonSerializationContext context) { + final JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("name", playlist.getName().nameString); + + final JsonArray jsonTracksArray = new JsonArray(); + List filenames = new ArrayList<>(); + for (Track t : playlist.getTracks()) { + filenames.add(t.getFileName()); + } + for (final String track : filenames) { + final JsonPrimitive jsonTrack = new JsonPrimitive(track); + jsonTracksArray.add(jsonTrack); + } + jsonObject.add("tracks", jsonTracksArray); + + return jsonObject; + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/jxmusic/ui/CommandBox.java similarity index 91% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/jxmusic/ui/CommandBox.java index 3d7aaded5640..7fb4817a52d6 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/jxmusic/ui/CommandBox.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.util.logging.Logger; @@ -7,13 +7,13 @@ import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.NewResultAvailableEvent; -import seedu.address.logic.ListElementPointer; -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; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.ui.NewResultAvailableEvent; +import seedu.jxmusic.logic.ListElementPointer; +import seedu.jxmusic.logic.Logic; +import seedu.jxmusic.logic.commands.CommandResult; +import seedu.jxmusic.logic.commands.exceptions.CommandException; +import seedu.jxmusic.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/jxmusic/ui/HelpWindow.java similarity index 96% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/jxmusic/ui/HelpWindow.java index 19de515ec8f0..445244a83d31 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/jxmusic/ui/HelpWindow.java @@ -1,11 +1,11 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.util.logging.Logger; import javafx.fxml.FXML; import javafx.scene.web.WebView; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import seedu.jxmusic.commons.core.LogsCenter; /** * Controller for a help page diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/jxmusic/ui/MainWindow.java similarity index 82% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/seedu/jxmusic/ui/MainWindow.java index 0e361a4d7baf..979f4ae33b7e 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/jxmusic/ui/MainWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.util.logging.Logger; @@ -12,13 +12,13 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; +import seedu.jxmusic.commons.core.Config; +import seedu.jxmusic.commons.core.GuiSettings; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.ui.ExitAppRequestEvent; +import seedu.jxmusic.commons.events.ui.ShowHelpRequestEvent; +import seedu.jxmusic.logic.Logic; +import seedu.jxmusic.model.UserPrefs; /** * The Main Window. Provides the basic application layout containing @@ -34,14 +34,14 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private PlaylistListPanel playlistListPanel; + private TrackListPanel trackListPanel; private Config config; private UserPrefs prefs; private HelpWindow helpWindow; - @FXML - private StackPane browserPlaceholder; + //@FXML + //private StackPane browserPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -50,7 +50,10 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane trackListPanelPlaceholder; + + @FXML + private StackPane playlistListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -119,16 +122,17 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + playlistListPanel = new PlaylistListPanel(logic.getFilteredPlaylistList()); + playlistListPanelPlaceholder.getChildren().add(playlistListPanel.getRoot()); + + trackListPanel = new TrackListPanel(logic.getFilteredTrackList()); + trackListPanelPlaceholder.getChildren().add(trackListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getLibraryFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -187,12 +191,12 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public PlaylistListPanel getPlaylistListPanel() { + return playlistListPanel; } - void releaseResources() { - browserPanel.freeResources(); + public TrackListPanel getTrackListPanel() { + return trackListPanel; } @Subscribe diff --git a/src/main/java/seedu/jxmusic/ui/PlaylistCard.java b/src/main/java/seedu/jxmusic/ui/PlaylistCard.java new file mode 100644 index 000000000000..e71eb2f8f89d --- /dev/null +++ b/src/main/java/seedu/jxmusic/ui/PlaylistCard.java @@ -0,0 +1,61 @@ +package seedu.jxmusic.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.jxmusic.model.Playlist; + +/** + * An UI component that displays information of a {@code Playlist}. + */ +public class PlaylistCard extends UiPart { + + private static final String FXML = "PlaylistListCard.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 Playlist playlist; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private FlowPane tracks; + + public PlaylistCard(Playlist playlist, int displayedIndex) { + super(FXML); + this.playlist = playlist; + id.setText(displayedIndex + ". "); + name.setText(playlist.getName().nameString); + playlist.getTracks().forEach(track -> + tracks.getChildren().add(new Label(track.getFileNameWithoutExtension()))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PlaylistCard)) { + return false; + } + + //name check + Playlist playlist1 = ((PlaylistCard) other).playlist; + return name.getText().equals(playlist1.getName().nameString); + } +} diff --git a/src/main/java/seedu/jxmusic/ui/PlaylistListPanel.java b/src/main/java/seedu/jxmusic/ui/PlaylistListPanel.java new file mode 100644 index 000000000000..cc65d6e6520e --- /dev/null +++ b/src/main/java/seedu/jxmusic/ui/PlaylistListPanel.java @@ -0,0 +1,83 @@ +package seedu.jxmusic.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.ui.JumpToListRequestEvent; +import seedu.jxmusic.commons.events.ui.PlaylistPanelSelectionChangedEvent; +import seedu.jxmusic.model.Playlist; + +/** + * Panel containing the list of playlists. + */ +public class PlaylistListPanel extends UiPart { + private static final String FXML = "PlaylistListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PlaylistListPanel.class); + + @FXML + private ListView playlistListView; + + public PlaylistListPanel(ObservableList playlists) { + super(FXML); + setConnections(playlists); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList playlists) { + playlistListView.setItems(playlists); + playlistListView.setCellFactory(listView -> new PlaylistsViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + playlistListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in playlist list panel changed to : '" + newValue + "'"); + raise(new PlaylistPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code PlaylistCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + playlistListView.scrollTo(index); + playlistListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Playlist} using a {@code PlaylistCard}. + */ + class PlaylistsViewCell extends ListCell { + @Override + protected void updateItem(Playlist playlist, boolean empty) { + super.updateItem(playlist, empty); + + if (empty || playlist == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PlaylistCard(playlist, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/jxmusic/ui/ResultDisplay.java similarity index 89% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/jxmusic/ui/ResultDisplay.java index d05536bbee96..a52edbefaa4b 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/jxmusic/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.util.logging.Logger; @@ -10,8 +10,8 @@ import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.NewResultAvailableEvent; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.ui.NewResultAvailableEvent; /** * A ui for the status bar that is displayed at the header of the application. diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/jxmusic/ui/StatusBarFooter.java similarity index 91% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/jxmusic/ui/StatusBarFooter.java index f6ba29502422..31e56b7bc86f 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/jxmusic/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,8 +13,8 @@ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.model.LibraryChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -74,7 +74,7 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleAddressBookChangedEvent(LibraryChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); diff --git a/src/main/java/seedu/jxmusic/ui/TrackCard.java b/src/main/java/seedu/jxmusic/ui/TrackCard.java new file mode 100644 index 000000000000..c8efee91e22a --- /dev/null +++ b/src/main/java/seedu/jxmusic/ui/TrackCard.java @@ -0,0 +1,56 @@ +package seedu.jxmusic.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.jxmusic.model.Track; + +/** + * An UI component that displays information of a {@code Track}. + */ +public class TrackCard extends UiPart { + private static final String FXML = "TrackCard.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 Track track; + + @FXML + private HBox trackCardPane; + @FXML + private Label name; + @FXML + private Label id; + + public TrackCard(Track track, int displayedIndex) { + super(FXML); + this.track = track; + id.setText(displayedIndex + "."); + name.setText(track.getFileNameWithoutExtension()); + } + + @Override + public boolean equals(Object other) { + //short circuit if same object + if (other == this) { + return true; + } + + //instanceof handles nulls + if (!(other instanceof TrackCard)) { + return false; + } + + //name check + Track track1 = ((TrackCard) other).track; + return name.getText().equals(track1.getFileNameWithoutExtension()); + } +} + diff --git a/src/main/java/seedu/jxmusic/ui/TrackListPanel.java b/src/main/java/seedu/jxmusic/ui/TrackListPanel.java new file mode 100644 index 000000000000..3083b221e471 --- /dev/null +++ b/src/main/java/seedu/jxmusic/ui/TrackListPanel.java @@ -0,0 +1,86 @@ +package seedu.jxmusic.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.ui.JumpToListRequestEvent; +import seedu.jxmusic.commons.events.ui.TrackListPanelSelectionChangedEvent; +import seedu.jxmusic.model.Track; + +/** + * Panel containing the list of tracks. + */ +public class TrackListPanel extends UiPart { + private static final String FXML = "TrackListPanel.fxml"; + private Logger logger = LogsCenter.getLogger(TrackListPanel.class); + + @FXML + private ListView trackListView; + + public TrackListPanel(ObservableList tracks) { + super(FXML); + setConnections(tracks); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList tracks) { + trackListView.setItems(tracks); + trackListView.setCellFactory(listView -> new TrackListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + trackListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in track list panel changed to : '" + newValue + "'"); + raise(new TrackListPanelSelectionChangedEvent(newValue)); + } + }); + } + + + /** + * Scrolls to the {@code TrackCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + trackListView.scrollTo(index); + trackListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Track} using a {@code TrackCard}. + */ + class TrackListViewCell extends ListCell { + @Override + protected void updateItem(Track track, boolean empty) { + super.updateItem(track, empty); + + if (empty || track == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TrackCard(track, getIndex() + 1).getRoot()); + } + } + } + +} + + diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/jxmusic/ui/Ui.java similarity index 88% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/jxmusic/ui/Ui.java index e6a67fe8c027..248075d45dd3 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/jxmusic/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/jxmusic/ui/UiManager.java similarity index 89% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/jxmusic/ui/UiManager.java index 3fd3c17be156..2b62d4badb91 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/jxmusic/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import java.util.logging.Logger; @@ -9,14 +9,14 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; +import seedu.jxmusic.MainApp; +import seedu.jxmusic.commons.core.ComponentManager; +import seedu.jxmusic.commons.core.Config; +import seedu.jxmusic.commons.core.LogsCenter; +import seedu.jxmusic.commons.events.storage.DataSavingExceptionEvent; +import seedu.jxmusic.commons.util.StringUtil; +import seedu.jxmusic.logic.Logic; +import seedu.jxmusic.model.UserPrefs; /** * The manager of the UI component. @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; 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/JxMusic.png"; private Logic logic; private Config config; @@ -66,7 +66,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/jxmusic/ui/UiPart.java similarity index 95% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/jxmusic/ui/UiPart.java index 5c237e57154b..6eb233560138 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/jxmusic/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.jxmusic.ui; import static java.util.Objects.requireNonNull; @@ -6,9 +6,9 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.BaseEvent; +import seedu.jxmusic.MainApp; +import seedu.jxmusic.commons.core.EventsCenter; +import seedu.jxmusic.commons.events.BaseEvent; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. diff --git a/src/main/resources/images/JxMusic.png b/src/main/resources/images/JxMusic.png new file mode 100644 index 000000000000..46c599833e2a Binary files /dev/null and b/src/main/resources/images/JxMusic.png differ diff --git a/src/main/resources/library/Haikei Goodbye Sayonara.mp3 b/src/main/resources/library/Haikei Goodbye Sayonara.mp3 new file mode 100644 index 000000000000..e8e5231ac6fb Binary files /dev/null and b/src/main/resources/library/Haikei Goodbye Sayonara.mp3 differ diff --git a/src/main/resources/library/Ihojin no Yaiba.mp3 b/src/main/resources/library/Ihojin no Yaiba.mp3 new file mode 100644 index 000000000000..822e43fc6bf5 Binary files /dev/null and b/src/main/resources/library/Ihojin no Yaiba.mp3 differ diff --git a/src/main/resources/library/Marbles.mp3 b/src/main/resources/library/Marbles.mp3 new file mode 100644 index 000000000000..2c916bc79010 Binary files /dev/null and b/src/main/resources/library/Marbles.mp3 differ diff --git a/src/main/resources/library/SOS Morse Code.mp3 b/src/main/resources/library/SOS Morse Code.mp3 new file mode 100644 index 000000000000..6e13b8403693 Binary files /dev/null and b/src/main/resources/library/SOS Morse Code.mp3 differ diff --git a/src/main/resources/library/Service Bell Help.mp3 b/src/main/resources/library/Service Bell Help.mp3 new file mode 100644 index 000000000000..75e3541c1832 Binary files /dev/null and b/src/main/resources/library/Service Bell Help.mp3 differ diff --git a/src/main/resources/library/acyort.mp3 b/src/main/resources/library/acyort.mp3 new file mode 100644 index 000000000000..58c02964814b Binary files /dev/null and b/src/main/resources/library/acyort.mp3 differ diff --git a/src/main/resources/library/aliez.mp3 b/src/main/resources/library/aliez.mp3 new file mode 100644 index 000000000000..a315bc0311ca Binary files /dev/null and b/src/main/resources/library/aliez.mp3 differ diff --git a/src/main/resources/library/scarborough fair.mp3 b/src/main/resources/library/scarborough fair.mp3 new file mode 100644 index 000000000000..de98fda9fa70 Binary files /dev/null and b/src/main/resources/library/scarborough fair.mp3 differ diff --git a/src/main/resources/library/unsupported.mp3 b/src/main/resources/library/unsupported.mp3 new file mode 100644 index 000000000000..78981922613b --- /dev/null +++ b/src/main/resources/library/unsupported.mp3 @@ -0,0 +1 @@ +a diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 70bd59ab3215..5bf6018a8349 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - - + + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..f517b1ba8705 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -108,11 +108,11 @@ } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #5E7367; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: #45A321; -fx-border-width: 1; } @@ -120,8 +120,39 @@ -fx-text-fill: white; } -.cell_big_label { - -fx-font-family: "Segoe UI Semibold"; +.cell_playlist_captain { + -fx-font-family: "Helvetica"; + -fx-font-size: 20px; + -fx-text-fill: #010504; +} + +.cell_track_captain { + -fx-font-family: "Helvetica"; + -fx-font-size: 20px; + -fx-text-fill: #010504; +} + +.cell_track_vbox { + -fx-background-color: #3AB22C; +} + +.cell_track_field_captain { + -fx-font-family: "Helvetica"; + -fx-font-size: 18px; + -fx-text-fill: #3AB22C; +} +.cell_track_field_anchor_pane { + -fx-background-color: #383838; +} + +.cell_playlist_label { + -fx-font-family: "Helvetica"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_track_label { + -fx-font-family: "Helvetica"; -fx-font-size: 16px; -fx-text-fill: #010504; } @@ -224,7 +255,7 @@ } .button:hover { - -fx-background-color: #3a3a3a; + -fx-background-color: #58D68D; } .button:pressed, .button:default:hover:pressed { @@ -313,7 +344,7 @@ #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #D68910; } #commandTextField { @@ -327,7 +358,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #playlistListPanel, #playlistWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } @@ -336,16 +367,17 @@ -fx-background-radius: 0; } -#tags { - -fx-hgap: 7; - -fx-vgap: 3; +#tracks { + -fx-hgap: 12; + -fx-vgap: 10; } -#tags .label { +#tracks .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #196F3E; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; - -fx-font-size: 11; + -fx-font-size: 13; + -fx-font-family: "Helvetica" } diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index daf386d8f5b8..35414943d038 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -14,7 +14,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -46,19 +46,19 @@ - - + + - + - + - - + - + - + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5d..000000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PlaylistListCard.fxml similarity index 78% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/PlaylistListCard.fxml index f08ea32ad558..668544ed2fd4 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PlaylistListCard.fxml @@ -19,7 +19,7 @@ - - -